C инициализация глобальной статической переменной выполняется компоновщиком?

Допустим, у нас есть:

f1.c

#include  static int x = 10; void f1() { printf("f1.c : %d\n", x); } 

main.c

 extern void f1(); int main(int argc, char **argv) { f1(); return 0; } 

мы скомпилируем и прочитаем два символьных символа ELF (rel. ELF и exec ELF):

 $> gcc -c *.c $> readelf -s f1.o | grep x Num: Value Size Type Bind Vis Ndx Name 5: 0000000000000000 4 OBJECT LOCAL DEFAULT 3 x $> gcc *.o $> readelf -s a.out | grep x Num: Value Size Type Bind Vis Ndx Name 38: 0000000000601038 4 OBJECT LOCAL DEFAULT 25 x 

Я вижу, что Value (также называемое адресом), в котором глобальная статическая переменная x составляет 0000000000000000 от чтения перемещаемого объектного файла f1.o
Это означает, что мы еще не инициализировали его, поскольку он все еще является rel. Объект ELF и компоновщик позаботятся об этом.

Итак, мой вопрос заключается в том, если компоновщик – это тот, который устанавливает x в значение 10 по известному адресу после ссылки 0000000000601038 , как это сделать? Где линкер получает информацию, чтобы установить значение 10 и кто дает эту информацию ( f1.o ?)?

Значение 0000000000000000 (в объектном файле f1.o ) является относительным адресом (статической переменной), равно как и смещение, и этот файл также содержит связанные с ним директивы о перемещении . Код для получения аргумента x для печати также имеет некоторое перемещение на нем (в некоторой команде загрузки машины).

В этом объектном файле, вероятно, есть раздел .data . Этот раздел должен начинаться со слова (с 0 смещением, которое вы наблюдали в f1.o ), содержащим 10.

Узнайте больше о линкерах (я рекомендую книгу Линкеров и загрузчиков Левина). Процесс связывания (для получения исполняемого файла ELF ) обрабатывает директивы перемещения. Читайте также больше о формате ELF, начиная с эльфа (5) (прочитав ELF wikipage). Изучите также спецификации ABI (для Linux x86-64 см. Здесь из этого ответа), в котором подробно описаны директивы о перемещении.

Вы можете захотеть скомпилировать ваш f1.c с помощью gcc -Wall -S -fverbose-asm -O1 f1.c затем просмотреть испущенный файл ассемблера f1.s

Вы также можете просмотреть объектный файл f1.o и исполняемый файл ELF с помощью различных инструментов, таких как readelf (1) и objdump (1) . Оба принимают множество опций (в частности, параметр -r для objdump для отображения директив перемещения).

Динамическое связывание (в стандартной библиотеке libc.*.so ) вводит некоторую дополнительную сложность в исполняемом элементе ELF. См. Также ld-linux (8) (который выполняет некоторое задание связывания в начале выполнения) и vdso (7) . Вы также можете прочитать статью Drepper How To Write Shared Libraries .

Свободно ansible учебник Операционные системы: три простых пьесы также могут быть полезны для чтения (он объясняет, что такое процесс и как его исполнение продолжается).

Этот сегмент, где хранятся переменные продолжительности статического хранения с определенными значениями, известен как .data (это имя используется стандартом ELF, но другие линкеры имеют тенденцию использовать одно и то же имя).

Как эти переменные установлены, зависит от целевой системы.

  • В системах на базе RAM (таких как ПК) весь сегмент .data инициализируется заранее как часть исполняемого файла и загружается в ОЗУ вместе с программой.
  • В системах на базе ROM (таких как микроcontrollerы со вспышкой) .data не могут быть инициализированы заранее. Вместо этого он копируется с ПЗУ в ОЗУ, прежде чем main () вызывается некоторым кодом запуска («CRT»). Таким образом, он фактически устанавливается во время выполнения, что означает, что всегда есть задержка при запуске программы в таких системах. Чтобы избавиться от задержки, часто используется нестандартная альтернатива запуска («минимальная»), которая полностью игнорирует инициализацию статических переменных продолжительности хранения.