我可以在库中声明一个全局变量,以后编译成共享对象吗?通过将其声明为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
答案 0 :(得分:8)
共享库不是C概念。存在的不同操作系统和计算平台的共享库实现表现出形式和行为的差异。
然而,是的,我所知道的所有共享库实现都支持变量,从C角度来看,静态存储持续时间和外部链接,我假设你是“全局”的意思。因为您似乎使用的是Linux,所以您的共享库将具有ELF风格。在这种情况下,动态链接共享库的每个进程都将获得自己的变量副本。
您描述的大变量地址没有特别的后果。 ELF共享库不必在任何特定地址加载,实际上Linux实现了ASLR,它主动使库加载地址变化。您的共享库可以在系统的64位虚拟地址空间中的任何地方或多或少地加载,因此您实际上无法读取变量的地址在数字上很大的事实。
至于你描述的错误,我倾向于认为它源于错误的代码,而不是(直接)来自共享库的参与。从您的描述中,我怀疑您最终得到了具有相同名称的多个变量,所有变量都具有外部链接。这是静态链接的错误,但在这种情况下,编译器可以(并且默认情况下,GCC会)合并重复的变量而不是拒绝代码。
另一方面,使用ELF,可以在链接到同一进程的多个共享对象中定义相同的符号,并且可以从整个过程中的不同点引用不同的定义。由于共享库是与主程序分开编译的,因此编译器没有合并符号的机会,即使它可以,它也不是很明显。但是给定符号的多个声明是可能性而不是必需。如果它发生,可能是因为你的标题错误地声明了变量。
程序中的任何给定变量可能有许多声明,但必须只有一个定义。声明通常来自头文件,它们应如下所示:
extern int foo;
如果要在多个源文件中使用标头,那么extern
是必需 - 并且缺少初始化程序会确定声明不能被解释为定义。然后应该在一个源文件中定义变量,看起来像这样:
int foo = 0;
初始化程序的存在确定声明也是一个定义。如果省略初始化器,它可能仍然是一个定义,只要不包括extern
限定符,但如果您不想学习所有细节,那么只提供初始化器是安全的。
如果多个共享对象中存在foo
的定义,则会出现您所描述的问题。例如,如果头文件包含以下任何一种形式的声明:
int foo; /* WRONG - without extern, this is a tentative definition */
extern int bar = 0; /* WRONG - because of the initializer, this is a definition */
答案 1 :(得分:3)
是的,库可以包含全局变量。
但是根据编译器/链接器选项,它们可能不可见。例如。通常的做法是,库使用-fvisibility=hidden
构建,并且只导出某些符号(通过链接器映射或显式__attribute__((__visibility__))
标记)。
你的&#34;大&#34;项目可能是以这种方式建造的。
高地址也可能表示variable
是另一个库中的某种其他符号(函数)。