Ответ 1
При использовании этих параметров компилятора вы можете добавить опцию компоновщика --gc-sections
(может быть неправильное имя), которая удалит весь неиспользуемый код.
Ниже перечислены страницы GCC для разделов функций и разделов разделов данных:
-ffunction-sections -fdata-sections
Поместите каждую функцию или элемент данных в свой раздел в выходной файл, если цель поддерживает произвольные разделы. Имя функции или имя элемента данных определяет имя раздела в выходном файле. Используйте эти параметры в системах, где компоновщик может выполнять оптимизацию, чтобы улучшить локальность ссылки в пространстве команд. Большинство систем, использующих формат объекта ELF, и процессоры SPARC, работающие под управлением Solaris 2, имеют компоновщики с такими оптимизациями. AIX может иметь такую оптимизацию в будущем.
Используйте эти параметры только тогда, когда есть значительные преимущества от этого. Когда вы укажете эти параметры, ассемблер и компоновщик создадут более крупные объекты и исполняемые файлы, а также будут медленнее. Вы не сможете использовать gprof для всех систем, если вы укажете эту опцию, и у вас может быть проблемы с отладкой, если вы укажете оба параметра и -g.
У меня создалось впечатление, что эти параметры помогут уменьшить размер исполняемого файла. Почему эта страница говорит, что она создаст более крупные исполняемые файлы? Я что-то пропустил?
При использовании этих параметров компилятора вы можете добавить опцию компоновщика --gc-sections
(может быть неправильное имя), которая удалит весь неиспользуемый код.
Интересно, что использование -fdata-sections
может создавать литеральные пулы ваших функций, и, следовательно, ваши функции сами по себе больше. Я заметил это на ARM в частности, но это, вероятно, будет верно в других местах. Бинарный я тестировал только на четверть процента, но он вырос. Глядя на разборку измененных функций, было ясно, почему.
Если все записи BSS (или DATA) в вашем объектном файле распределяются по одному разделу, то компилятор может сохранить адрес этого раздела в пуле литералов функций и генерировать нагрузки с известными смещениями из этого адреса в функции для доступа к вашим данным. Но если вы включите -fdata-sections
, он помещает каждый фрагмент данных BSS (или DATA) в свой раздел и, поскольку он не знает, какой из этих разделов может быть собран позже, или в каком порядке компоновщик поместит все эти секций в конечное исполняемое изображение, он больше не может загружать данные с помощью смещений из одного адреса. Таким образом, вместо этого он должен выделять запись в литеральном пуле за используемые данные, и как только компоновщик выяснил, что происходит в конечном изображении, и где он может пойти и исправить эти литерные записи пула с фактическим адресом данные.
Итак, даже при -Wl,--gc-sections
результирующее изображение может быть больше, потому что фактический текст функции больше.
Ниже я добавил минимальный пример
Коду ниже достаточно, чтобы увидеть поведение, о котором я говорю. Пожалуйста, не отбрасывайте декларацию volatile и используйте глобальные переменные, оба из которых сомнительны в реальном коде. Здесь они обеспечивают создание двух разделов данных при использовании -fdata-sections.
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 мы получаем следующее.
> 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.
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 hex) и что теперь есть две перестановки в пуле queue_empty literal. Эти перестановки соответствуют адресам двух разделов BSS, которые были созданы, по одному для каждой глобальной переменной. Здесь должно быть два адреса, потому что компилятор не может знать относительную позицию, в которой компоновщик в конечном итоге помещает эти два раздела. Рассматривая инструкции в начале queue_empty, мы видим, что есть дополнительная загрузка, компилятор должен генерировать отдельные пары нагрузки, чтобы получить адрес раздела, а затем значение переменной в этом разделе. Дополнительная инструкция в этой версии queue_empty не делает тело функции более длинной, она просто занимает пятно, которое ранее было nop, но в общем случае этого не будет.
Вы можете использовать -ffunction-sections
и -fdata-sections
в статических библиотеках, что увеличит размер статической библиотеки, так как каждая функция и глобальная переменная данных будут помещены в отдельный раздел.
И затем используйте -Wl,--gc-sections
в программе, связанной с этой статической библиотекой, которая удалит неиспользуемые разделы.
Таким образом, окончательный двоичный файл будет меньше, чем без этих флагов.
Будьте осторожны, поскольку -Wl,--gc-sections
может сломать вещи.
Я получаю лучшие результаты, добавляя дополнительный шаг и создавая архив .a
:
-ffunction-sections
-fdata-sections
flags.o
объекты помещаются в архив .a
с ar rcs file.a *.o
-Wl,-gc-sections,-u,main
опциями-Os
.Я попробовал некоторое время назад и, глядя на результаты, кажется, что увеличение размера происходит от порядка объектов с различным выравниванием. Нормальный компоновщик сортирует объекты, чтобы сохранить отступы между ними небольшими, но похоже, что это работает только в секции, а не в отдельных разделах. Таким образом, вы часто получаете дополнительное дополнение между разделами данных для каждой функции, увеличивая общее пространство.
Для статического lib с -Wl, -gc-секциями удаление неиспользуемого раздела, скорее всего, сделает больше, чем для небольшого увеличения.