Могу ли я объявить глобальную переменную в общей библиотеке?

Можно ли объявить глобальную переменную внутри библиотеки, которая позже скомпилирована в общий объект? Безопасно ли ссылаться на него из других библиотек или основного кода приложения, объявляя его как extern?

В теории это работает:

[[email protected] 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;
}
[[email protected] snippets]$ gcc -g -fPIC -c libcode.c 
[[email protected] snippets]$ gcc -o libcode.so -shared libcode.o
[[email protected] 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);
}
[[email protected] snippets]$ gcc -g -o app -L./ -lcode appcode.c 
[[email protected] snippets]$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./
[[email protected] snippets]$ ./app
var=44
var_copy=33
[[email protected] snippets]$ 

Давайте рассмотрим его с помощью отладчика:

[[email protected] 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" для ссылки на них в коде библиотеки. Однако, таким образом, библиотека не могла иметь переменных самостоятельно. См. Этот вопрос для деталей: получение неправильного адреса переменной во время вызова функции

Ответ 1

Общие библиотеки не являются концепцией C. Разделенные библиотеки различных операционных систем и вычислительных платформ, где они существуют, демонстрируют различия в форме и поведении.

Тем не менее, да, все распространенные реализации библиотек, о которых я знаю, поддерживают переменные с точки зрения C, статической продолжительности хранения и внешней связи, которые, как я предполагаю, вы понимаете под "глобальными". Поскольку вы, кажется, используете Linux, ваши общие библиотеки будут иметь аромат ELF. В этом случае каждый процесс, который динамически связывает общую библиотеку, получит свою собственную копию таких переменных.

Большой адрес переменной, который вы описываете, не имеет особого значения. Общие библиотеки ELF не должны загружаться по какому-либо конкретному адресу, и на самом деле Linux реализует ASLR, который активно меняет адреса загрузки библиотеки. Ваша разделяемая библиотека может быть загружена более или менее в любом месте в 64-разрядном виртуальном адресном пространстве вашей системы, поэтому вы действительно не можете много узнать о том, что адрес переменной численно большой.


Что касается описанной вами ошибки, я склонен думать, что она возникла из-за плохого кода, а не (напрямую) от участия в общей библиотеке. Из вашего описания, я подозреваю, что вы оказались с несколькими переменными с тем же именем, все с внешней связью. Это ошибка со статической привязкой, но в этом случае компилятор может (и по умолчанию, GCC) объединять повторяющиеся переменные вместо отказа от кода.

С ELF, с другой стороны, это нормально для того же символа, который должен быть определен в нескольких общих объектах, связанных с одним и тем же процессом, и различные определения могут ссылаться на разные точки в общем процессе. Поскольку общая библиотека скомпилирована отдельно от основной программы, компилятор не имеет возможности объединять символы, и не очевидно, что это должно даже если бы это было возможно. Но множественные объявления данного символа - возможность не необходимость. Если это произойдет, возможно, потому, что ваши заголовки ошибочно объявляют переменную.

В программе может быть много объявлений любой заданной переменной, но должно быть точно одно определение. Объявления обычно поступают из файла заголовка, и они должны выглядеть следующим образом:

extern int foo;

extern обязательный, если заголовок должен использоваться в более чем одном исходном файле - это и отсутствие инициализатора устанавливают, что объявление не может быть интерпретировано как определение. Тогда должно быть определение переменной только в одном исходном файле, выглядя примерно так:

int foo = 0;

Наличие инициализатора устанавливает, что объявление также является определением. Это может быть определение, если инициализатор был опущен до тех пор, пока квалификатор extern не будет включен, но если вы не хотите изучать все детали, тогда безопасно просто предоставить инициализатор.

Проблема, которую вы описываете, возникнет, если в нескольких общих объектах есть определения foo. Это произойдет, если, например, файл заголовка содержит объявление любой из этих форм:

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 */

Ответ 2

Да, библиотеки могут содержать глобальные переменные.

Но в зависимости от параметров компилятора/компоновщика они могут быть не видны. Например. общепринятой практикой является то, что библиотеки построены с помощью -fvisibility=hidden и что экспортируются только определенные символы (по карте компоновщика или явным тегам __attribute__((__visibility__))).

Ваш "большой" проект, вероятно, был построен таким образом.

Высокий адрес может также указывать, что variable - это какой-то другой символ (функция) в другой библиотеке.