在C语言中初始化一个全局结构体

25

在C语言中,最佳的实现以下任务的方法是什么?

#include <stdio.h>

struct A
{
    int x;
};

struct A createA(int x)
{
    struct A a;
    a.x = x;
    return a;
}

struct A a = createA(42);

int main(int argc, char** argv)
{
    printf("%d\n", a.x);
    return 0;
}

尝试编译上述代码时,编译器报告以下错误:

"initializer element is not constant"

错误出现在以下行:

struct A a = createA(42);

有人能解释一下是什么问题吗?我在C语言方面经验不是很丰富。谢谢!

5个回答

24
struct A a = { .x = 42 };

更多成员:

struct Y {
    int r;
    int s;
    int t;
};

struct Y y = { .r = 1, .s = 2, .t = 3 };

您还可以这样做:
struct Y y = { 1, 2, 3 };

对于联合体,同样适用,并且您不必包含所有成员,甚至不必按正确顺序放置它们。


1
如果 ystruct Y y [2],该怎么做? - user2284570

20

为什么不使用静态初始化?

struct A a = { 42 };

2
另外,您不能调用函数来静态初始化数据。这就是编译器错误试图告诉您的内容。 - BjoernD
没问题,但如果我需要嵌套初始化怎么办?比如说结构体B里面有一个A。我能用静态初始化的A来静态初始化B吗?例如:struct B b = createB(createA(42)) - Scott
2
啊,没事了。我已经想通了。显然我可以这样做:struct B b = {{42}};太好了! - Scott

6
这里的问题在于C语言中的全局/文件静态变量必须具有在编译时已知的值。这意味着您不能使用用户定义的函数来初始化该值,它必须是一个常量表达式。

1
为什么全局变量必须在编译时具有已知的值? - Mehdi Charife
@MehdiCharife 全局范围内的变量在main()被调用之前就会被初始化,因此您无法使用运行时变量对其进行初始化。 - Jelle Bleeker
但是,您无法使用运行时变量初始化它们的事实意味着它们必须在编译时具有已知值,这是怎么回事? - Mehdi Charife

3

对于那些同时使用MSVC的好奇心人:

C语言中,可以像C++一样在main函数之前运行初始化函数(当然可以,如果C不能做到这点,C++又怎么能实现呢),但如果你没有读过运行库的工作原理,则可能会有些困惑。

长话短说:

#pragma section(".CRT$XIU",long,read)

int
init_func ()
{
// initialization

return 0; // return 0 is mandatory
}

__declspec(allocate(".CRT$XIU"))
int (*global_initializer)() = init_func;

所以,它的源代码不像C++那样紧凑,但是可以做到。在使用之前,建议先了解PE格式,然后阅读MSVC安装目录下的crt\src\crt0.c和crt\src\crt0dat.c(在两个文件中搜索_cinit),以便了解正在发生的事情。


有没有类似于gcc的东西? - rkellerm
@rkellerm 简单的答案是:我认为不行。复杂的答案是:gcc有不止一个libc。我认为找到答案最简单的方法是在你的libc源代码库中查找"_start"。这是elf中调用的第一个函数。通常包含此符号的文件名为crt0something.c。例如,newlib没有这样的初始化,它设置argc、argv并进入main。但是添加一些代码来实现这一点并不困难。libc的编译通常是相当麻烦的。 - Pyjong
@rkellerm 好的,伙计。今天我实际上需要这个功能,所以我在 glibc 2.4 中进行了嗅探。结果发现它比 MS 编译器还要容易。如果你仍然想知道 - 只需将函数声明为 void attribute((constructor)) initializer_fn(),那么你的 C premain 构造函数就出来了 ;) - Pyjong
嗨,@Pyjong。你能否解释一下你的代码是做什么的,以及为什么它能够工作? - Mehdi Charife
1
嗨@MehdiCharife,第一行创建了一个名为.CRT$XIU的新程序段,该程序段将出现在生成的目标文件或二进制文件中。init_func只是一个普通函数,在那里没有什么特别的。最后两行创建了一个函数指针,初始化为指向init_func(),它将存储在先前创建的.CRT$XIU部分中。Libc库默认添加到每个C程序中。当您运行程序时,它不会从main()开始。它从libc库开始。Libc库查看.CRT部分并运行找到的所有指针,然后调用main()。 - Pyjong

1

你不能像那样在静态初始化中调用函数。在你的例子中,你可以简单地使用:

struct A a = {42};

如果你有一个更复杂的设置,你需要提供一个库构造和库销毁函数,强制用户调用你的库(假设你想要可移植性),或者你将不得不使用C++并利用构造函数/析构函数,或者你将不得不利用非标准和非可移植的__attribute__((constructor))来创建一个在启动时运行的函数来初始化它。

如果你有更复杂的设置,我强烈建议你使用C++:

class A
{
   A(){
      // 可以在构造函数中进行初始化
   }
   // ...
};
A a;

然而,如果你需要坚持使用纯C,可移植的做法是使用类似于以下内容:

typedef void* mylibrary_attr_t;
typedef void* mylibrary_t;
#ifdef __cplusplus # define EXTERNC extern "C" #else # define EXTERNC #endif
EXTERNC int mylibrary_attr_init(mylibrary_attr_t*); EXTERNC int mylibrary_attr_setparam1(mylibrary_attr_t,int); EXTERNC int mylibrary_attr_setparam2(mylibrary_attr_t,double); // .. more functions for various attributes used by library EXTERNC void mylibrary_attr_destroy(mylibrary_attr_t*);
EXTERNC int mylibrary_init(mylibrary_t*,mylibrary_attr_t); EXTERNC void mylibrary_destroy(mylibrary_t*);
// functions that use mylibrary_t // ...

以上的程序代码中,使用mylibrary_init来初始化库,并使用mylibrary_destroy释放库。使用库的函数需要一个已初始化的mylibrary_t实例,因此创建主函数的人需要负责调用mylibrary_init。最好是让初始化函数依赖于“属性”参数,该参数可以用0或NULL替换为默认值。这样,如果要扩展库并需要接受配置选项,则可以使用它。不过,这更多是设计而非技术方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接