Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

can I declare a global variable in a shared library?

Tags:

c

Can I declare a global variable inside a library that is later compiled into a shared object ? Is it safe to reference it from other libraries or the main application code by declaring it as extern?

In theory it works:

[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]$ 

Lets review it with the debugger:

[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) 

I say "in theory" it works because when using this approach in a large project I ran into a bug where the referencing such variable gave me unexpected results. The address of the variable was unusually high (0x7ffff767c640) and the only fix was to do declare all global variables inside main application code and use 'extern' to reference them in the code of the library. However, this way, the library could not have variables on its own. See this question for details: getting incorrect address of a variable during a function call

like image 607
Nulik Avatar asked Sep 08 '16 15:09

Nulik


2 Answers

Shared libraries are not a C concept. The shared library implementations of different operating systems and computing platforms, where they exist, exhibit differences in form and behavior.

Nevertheless, yes, all shared library implementations of which I am aware do support variables with, from a C perspective, static storage duration and external linkage, which I assume is what you mean by "global". Inasmuch as you appear to be using Linux, your shared libraries will be of the ELF flavor. In that case, each process that dynamically links the shared library will get its own copy of such variables.

The large variable address you describe is of no particular consequence. ELF shared libraries do not have to be loaded at any particular address, and in fact Linux implements ASLR, which actively makes library load addresses vary. Your shared library can be loaded more or less anywhere in your system's 64-bit virtual address space, so you really can't read much into the fact that the variable's address is numerically large.


As for the bug you describe, I'm inclined to think it arose from bad code, not (directly) from the involvement of a shared library. From your description, I suspect that you ended up with multiple variables with the same name, all with external linkage. That's an error with static linking, but in that case the compiler can (and by default, GCC does) merge the duplicate variables instead of rejecting the code.

With ELF, on the other hand, it's ok for the same symbol to be defined in multiple shared objects linked into the same process, and different definitions can be referenced from different points in the overall process. Since a shared library is compiled separately from the main program, the compiler does not have an opportunity to merge symbols, and it's not obvious that it should even if it could. But multiple declarations of a given symbol is a possibility not a necessity. If it happens, it's probably because your headers declare the variable wrongly.

There may be many declarations of any given variable in a program, but there must be exactly one definition. The declarations typically come from a header file, and they should look like this:

extern int foo;

The extern is mandatory if the header is to be used in more than one source file -- that and the absence of an initializer establish that the declaration cannot be interpreted as a definition. There should then be a definition of the variable in exactly one source file, looking something like this:

int foo = 0;

The presence of an initializer establishes that the declaration is also a definition. It might still be a definition if the initializer were omitted, as long as the extern qualifier is not included, but if you don't want to learn all the details then it's safe to just provide an initializer.

A problem such as you describe will arise if there are definitions of foo in multiple shared objects. That would happen if, for example, a header file contained a declaration of either of these 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 */
like image 185
John Bollinger Avatar answered Oct 20 '22 10:10

John Bollinger


Yes, libraries can contain global variables.

But depending on compiler/linker options, they might be not visible. E.g. it is common practice, that libraries are built with -fvisibility=hidden and that only certain symbols are exported (by linker map or explicit __attribute__((__visibility__)) tags).

Your "large" project has been probably built in such a way.

The high address might also indicate, that variable is some kind of other symbol (function) in another library.

like image 20
ensc Avatar answered Oct 20 '22 12:10

ensc