我可以在共享库中声明一个全局变量吗?

我可以在库中声明一个全局变量,以后编译成共享对象吗? 通过将其声明为extern从其他库或主应用程序代码引用它是否安全?

理论上它起作用:

[niko@dev1 snippets]$ cat libcode.c int variable; // <-- this is a global variable declared in a Library void set_var(int value) { variable=value; } int get_var(void) { return variable; } [niko@dev1 snippets]$ gcc -g -fPIC -c libcode.c [niko@dev1 snippets]$ gcc -o libcode.so -shared libcode.o [niko@dev1 snippets]$ cat appcode.c #include  // simplified .h declarations: extern int variable; void set_var(int value); int get_var(void); void main(void) { set_var(44); printf("var=%d\n",variable); variable=33; int var_copy=get_var(); printf("var_copy=%d\n",var_copy); } [niko@dev1 snippets]$ gcc -g -o app -L./ -lcode appcode.c [niko@dev1 snippets]$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./ [niko@dev1 snippets]$ ./app var=44 var_copy=33 [niko@dev1 snippets]$ 

让我们用调试器来检查它:

 [niko@dev1 snippets]$ gdb ./app ..... (gdb) break main Breakpoint 1 at 0x40077e: file appcode.c, line 9. (gdb) run Starting program: /home/deptrack/depserv/snippets/app Missing separate debuginfos, use: dnf debuginfo-install glibc-2.22-16.fc23.x86_64 Breakpoint 1, main () at appcode.c:9 9 set_var(44); (gdb) print &variable $1 = (int *) 0x601044  (gdb) s set_var (value=44) at libcode.c:4 4 variable=value; (gdb) s 5 } (gdb) s main () at appcode.c:10 10 printf("var=%d\n",variable); (gdb) s var=44 11 variable=33; (gdb) s 12 int var_copy=get_var(); (gdb) s get_var () at libcode.c:7 7 return variable; (gdb) s 8 } (gdb) s main () at appcode.c:13 13 printf("var_copy=%d\n",var_copy); (gdb) s var_copy=33 14 } (gdb) s 0x00007ffff7839580 in __libc_start_main () from /lib64/libc.so.6 (gdb) s Single stepping until exit from function __libc_start_main, which has no line number information. [Inferior 1 (process 28380) exited with code 014] (gdb) 

我说“在理论上”它是有效的,因为当在一个大型项目中使用这种方法时,我遇到了一个错误,其中引用这样的变量给了我意想不到的结果。 变量的地址非常高(0x7ffff767c640),唯一的解决方法是在主应用程序代码中声明所有全局变量,并使用’extern’在库的代码中引用它们。 但是,这样,库本身就没有变量。 有关详细信息,请参阅此问题: 在函数调用期间获取变量的错误地址

共享库不是C概念。 存在的不同操作系统和计算平台的共享库实现表现出forms和行为的差异。

不过,是的,我所知道的所有共享库实现都支持变量,从C角度来看,静态存储持续时间和外部链接,我假设你是“全局”的意思。 因为您似乎使用Linux,您的共享库将具有ELF风格。 在这种情况下,动态链接共享库的每个进程都将获得自己的此类变量副本。

您描述的大变量地址没有特别的后果。 ELF共享库不必在任何特定地址加载,实际上Linux实现了ASLR,它主动使库加载地址变化。 您的共享库可以在系统的64位虚拟地址空间中的任何位置或多或少地加载,因此您实际上无法读取变量的地址在数字上很大的事实。


至于你描述的错误,我倾向于认为它源于错误的代码,而不是(直接)来自共享库的参与。 从您的描述中,我怀疑您最终得到了多个具有相同名称的变量,所有变量都具有外部链接。 这是静态链接的错误,但在这种情况下,编译器可以(并且默认情况下,GCC会)合并重复的变量而不是拒绝代码。

另一方面,使用ELF,可以在链接到同一进程的多个共享对象中定义相同的符号,并且可以从整个过程中的不同点引用不同的定义。 由于共享库是与主程序分开编译的,因此编译器没有合并符号的机会,即使它可以,它也不是很明显。 但是,给定符号的多个声明可能不是必需的 。 如果它发生,可能是因为你的标题错误地声明了变量。

程序中的任何给定变量可能有许多声明,但必须只有一个定义。 声明通常来自头文件,它们应如下所示:

 extern int foo; 

如果要在多个源文件中使用标头,那么extern必需的 – 并且缺少初始化程序会确定声明不能被解释为定义。 然后应该在一个源文件中定义变量,看起来像这样:

 int foo = 0; 

初始化程序的存在确定声明也是一个定义。 如果初始化器被省略,它可能仍然是一个定义,只要不包括extern限定符,但如果你不想学习所有的细节,那么只提供一个初始化器是安全的。

如果在多个共享对象中存在foo定义,则会出现如您所描述的问题。 例如,如果头文件包含以下任一forms的声明,则会发生这种情况:

bad.h

 int foo; /* WRONG - without extern, this is a tentative definition */ extern int bar = 0; /* WRONG - because of the initializer, this is a definition */ 

是的,库可以包含全局变量。

但是根据编译器/链接器选项,它们可能不可见。 例如,通常的做法是使用-fvisibility=hidden构建库,并且仅导出某些符号(通过链接器映射或显式__attribute__((__visibility__))标记)。

您的“大型”项目可能是以这种方式构建的。

高地址也可能表示该variable是另一个库中的某种其他符号(函数)。