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

标签 c

我可以在稍后编译成共享对象的库中声明一个全局变量吗?通过将其声明为 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 <stdio.h>
// 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 <variable>
(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”在库代码中引用它们。但是,这样一来,库就无法拥有自己的变量。有关详细信息,请参阅此问题:getting incorrect address of a variable during a function call

最佳答案

共享库不是 C 语言的概念。不同操作系统和计算平台的共享库实现(如果存在)在形式和行为上表现出差异。

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

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


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

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

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

extern int foo;

extern强制性的 如果要在一个以上的源文件中使用 header - 并且缺少初始化程序确定无法解释声明作为定义。然后应该在一个源文件中有一个变量的定义,看起来像这样:

int foo = 0;

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

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

坏.h

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

关于我可以在共享库中声明一个全局变量吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39394971/

相关文章:

python - 捕获由 python 子进程创建的进程的运行时错误

c - binning/jack-knife c 程序中的段错误

c - 枚举当前加载的所有共享对象的所有 ELF 部分

c - 指针(树)如何在 2 条不同的行中返回 2 个不同的值?

c - 为什么十六进制格式说明符在 VS2013 中为 x86 产生错误的输出

c - pthread_join() 导致段错误

c - openMP 以及如何检查缓存行为并行性

Cygwin shell : $ make ex1 returns: "The system cannot find the specified file" on Windows 7

c - glibc,退出时关闭 FILE* 之间可能存在竞争条件?

objective-c - 如何在osx中​​创建静态库