Интересно, что использование -fdata-sections
может сделать буквальные пулы ваших функций и, следовательно, самих ваших функций больше. Я заметил это, в частности, на ARM, но, вероятно, это будет справедливо в других местах. Бинарный файл, который я тестировал, вырос только на четверть процента, но вырос. Глядя на разборку измененных функций было понятно почему.
Если все записи BSS (или DATA) в вашем объектном файле выделены одному разделу, то компилятор может сохранить адрес этого раздела в пуле литералов функций и генерировать нагрузки с известными смещениями из этого адреса в функции чтобы получить доступ к вашим данным. Но если вы включите -fdata-sections
, он помещает каждый фрагмент данных BSS (или DATA) в свой собственный раздел, и, поскольку он не знает, какой из этих разделов может быть собран позже, или в каком порядке компоновщик разместит все эти разделы в конечном исполняемом образе, он больше не может загружать данные, используя смещения с одного адреса. Таким образом, вместо этого он должен выделить запись в буквальном пуле для используемых данных, и как только компоновщик выяснил, что входит в конечное изображение и куда, тогда он может пойти и исправить эти записи буквального пула с фактическим адресом данные.
Так что да, даже с -Wl,--gc-sections
результирующее изображение может быть больше, потому что фактический текст функции больше.
Ниже я добавил минимальный пример
Приведенного ниже кода достаточно, чтобы увидеть поведение, о котором я говорю. Пожалуйста, не поддавайтесь изменчивому объявлению и использованию глобальных переменных, которые в реальном коде сомнительны. Здесь они обеспечивают создание двух разделов данных при использовании -fdata-section.
static volatile int head;
static volatile int tail;
int queue_empty(void)
{
return head == tail;
}
Версия GCC, используемая для этого теста:
gcc version 6.1.1 20160526 (Arch Repository)
Во-первых, без -fdata-section мы получаем следующее.
> arm-none-eabi-gcc -march=armv6-m \
-mcpu=cortex-m0 \
-mthumb \
-Os \
-c \
-o test.o \
test.c
> arm-none-eabi-objdump -dr test.o
00000000 <queue_empty>:
0: 4b03 ldr r3, [pc, #12] ; (10 <queue_empty+0x10>)
2: 6818 ldr r0, [r3, #0]
4: 685b ldr r3, [r3, #4]
6: 1ac0 subs r0, r0, r3
8: 4243 negs r3, r0
a: 4158 adcs r0, r3
c: 4770 bx lr
e: 46c0 nop ; (mov r8, r8)
10: 00000000 .word 0x00000000
10: R_ARM_ABS32 .bss
> arm-none-eabi-nm -S test.o
00000000 00000004 b head
00000000 00000014 T queue_empty
00000004 00000004 b tail
Из arm-none-eabi-nm
мы видим, что queue_empty имеет длину 20 байтов (14 шестнадцатеричных), а вывод arm-none-eabi-objdump
показывает, что в конце функции есть одно слово перемещения, это адрес секции BSS ( раздел для неинициализированных данных). Первая инструкция в функции загружает это значение (адрес BSS) в r3. Следующие две инструкции загружаются относительно r3, смещаясь на 0 и 4 байта соответственно. Эти две нагрузки являются нагрузками значений головы и хвоста. Мы можем увидеть эти смещения в первом столбце выходных данных arm-none-eabi-nm
. nop
в конце функции - выравнивание по буквам адреса буквенного пула.
Далее посмотрим, что произойдет при добавлении -fdata-section.
arm-none-eabi-gcc -march=armv6-m \
-mcpu=cortex-m0 \
-mthumb \
-Os \
-fdata-sections \
-c \
-o test.o \
test.c
arm-none-eabi-objdump -dr test.o
00000000 <queue_empty>:
0: 4b03 ldr r3, [pc, #12] ; (10 <queue_empty+0x10>)
2: 6818 ldr r0, [r3, #0]
4: 4b03 ldr r3, [pc, #12] ; (14 <queue_empty+0x14>)
6: 681b ldr r3, [r3, #0]
8: 1ac0 subs r0, r0, r3
a: 4243 negs r3, r0
c: 4158 adcs r0, r3
e: 4770 bx lr
...
10: R_ARM_ABS32 .bss.head
14: R_ARM_ABS32 .bss.tail
arm-none-eabi-nm -S test.o
00000000 00000004 b head
00000000 00000018 T queue_empty
00000000 00000004 b tail
Сразу же мы видим, что длина queue_empty увеличилась на четыре байта до 24 байтов (18 шестнадцатеричных) и что теперь необходимо выполнить два перемещения в буквальном пуле queue_empty. Эти перемещения соответствуют адресам двух созданных разделов BSS, по одному для каждой глобальной переменной. Здесь должно быть два адреса, потому что компилятор не может знать относительную позицию, в которой компоновщик в конечном итоге поместит эти два раздела. Глядя на инструкции в начале queue_empty, мы видим, что есть дополнительная загрузка, компилятор должен генерировать отдельные пары нагрузки, чтобы получить адрес раздела и затем значение переменной в этом разделе. Дополнительная инструкция в этой версии queue_empty не удлиняет тело функции, она просто занимает место, которое ранее было nop, но в целом это не так.