Прошло некоторое время с тех пор, как я работал с ELF. Но я думаю, что все еще помню этот материал. Нет, он физически не содержит этих нулей. Если вы загляните в программный заголовок файла ELF, то увидите, что у каждого заголовка есть два числа: один - это размер файла. И еще один размер, который имеет раздел при выделении в виртуальной памяти (readelf -l ./a.out
):
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000034 0x08048034 0x08048034 0x000e0 0x000e0 R E 0x4
INTERP 0x000114 0x08048114 0x08048114 0x00013 0x00013 R 0x1
[Requesting program interpreter: /lib/ld-linux.so.2]
LOAD 0x000000 0x08048000 0x08048000 0x00454 0x00454 R E 0x1000
LOAD 0x000454 0x08049454 0x08049454 0x00104 0x61bac RW 0x1000
DYNAMIC 0x000468 0x08049468 0x08049468 0x000d0 0x000d0 RW 0x4
NOTE 0x000128 0x08048128 0x08048128 0x00020 0x00020 R 0x4
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4
Заголовки типа LOAD
- это те, которые копируются в виртуальную память, когда файл загружается для выполнения. Другие заголовки содержат другую информацию, например, необходимые общие библиотеки. Как видите, FileSize
и MemSiz
значительно отличаются для заголовка, который содержит раздел bss
(второй LOAD
один):
0x00104 (file-size) 0x61bac (mem-size)
Для этого примера кода:
int a[100000];
int main() { }
Спецификация ELF гласит, что часть сегмента, размер которой больше размера файла, чем размер файла, просто заполняется нулями в виртуальной памяти. Отображение сегмента в раздел второго заголовка LOAD
выглядит следующим образом:
03 .ctors .dtors .jcr .dynamic .got .got.plt .data .bss
Так что там есть и другие разделы. Для C ++ конструктор / деструкторы. То же самое для Java. Затем он содержит копию раздела .dynamic
и другие вещи, полезные для динамического связывания (я считаю, что это место, где содержатся необходимые общие библиотеки среди прочего). После этого раздел .data
, который содержит инициализированные глобальные переменные и локальные статические переменные. В конце появляется раздел .bss
, который заполняется нулями во время загрузки, поскольку размер файла не покрывает его.
Кстати, вы можете увидеть, в какой выходной раздел будет помещен конкретный символ, с помощью опции компоновщика -M
. Для gcc вы используете -Wl,-M
, чтобы передать параметр компоновщику. Приведенный выше пример показывает, что a
выделяется в пределах .bss
. Это может помочь вам убедиться, что ваши неинициализированные объекты действительно оказываются в .bss
, а не в другом месте:
.bss 0x08049560 0x61aa0
[many input .o files...]
*(COMMON)
*fill* 0x08049568 0x18 00
COMMON 0x08049580 0x61a80 /tmp/cc2GT6nS.o
0x08049580 a
0x080ab000 . = ALIGN ((. != 0x0)?0x4:0x1)
0x080ab000 . = ALIGN (0x4)
0x080ab000 . = ALIGN (0x4)
0x080ab000 _end = .
GCC по умолчанию хранит неинициализированные глобальные переменные в разделе COMMON для совместимости со старыми компиляторами, которые позволяют дважды определять глобальные переменные в программе без множественных ошибок определения. Используйте -fno-common
, чтобы заставить GCC использовать секции .bss для объектных файлов (не имеет значения для конечного связанного исполняемого файла, потому что, как вы видите, он все равно попадет в секцию вывода .bss. Это контролируется скрипт компоновщика . Показать его с помощью ld -verbose
). Но это не должно вас пугать, это просто внутренняя деталь. Смотрите страницу руководства gcc.