Як видалити невикористані символи C / C ++ за допомогою GCC та ld?


110

Мені потрібно жорстко оптимізувати розмір мого виконуваного файлу ( ARMрозробка), і я помітив, що в моїй поточній схемі збірки ( gcc+ ld) невикористані символи не знімаються.

Використання arm-strip --strip-unneededдля отриманих виконуваних файлів / бібліотек не змінює розмір виводу виконуваного файлу (я не маю поняття, чому, можливо, він просто не може) .

Яким би був спосіб (якщо він існує) змінити мій конвеєр будівлі, щоб невикористані символи були вилучені з отриманого файлу?


Я б навіть не думати про це, але моєї поточної впроваджений середовище не дуже «сильна» і збереженні навіть 500Kз 2Mрезультатів в дуже хорошому прирості продуктивності завантаження.

Оновлення:

На жаль, поточна gccверсія, яку я використовую, не має цього -dead-stripваріанту, а -ffunction-sections... + --gc-sectionsfor ldне дає суттєвої різниці для отриманого результату.

Я вражений тим, що це навіть стало проблемою, тому що я був впевнений, що він gcc + ldповинен автоматично знімати невикористані символи (чому вони взагалі повинні їх зберігати?).


Як ви знаєте, що символи не використовуються?
zvrba

Ніде не посилається => не використовується в кінцевій програмі. Я припускаю, що побудувати графік викликів під час компіляції / з'єднання не повинно бути дуже важким.
Yippie-Ki-Yay

1
Ви намагаєтесь зменшити розмір файлу .o, видаливши мертві символи , або ви намагаєтесь зменшити розмір фактичного сліду коду, щойно завантажений у виконувану пам'ять? Те, що ви говорите "вбудовані", натякає на останнє; питання, яке ви задаєте, здається зосередженим на першому.
Іра Бакстер

@Ira Я намагаюся зменшити розмір вихідного виконуваного файлу, оскільки (як приклад), якщо я намагаюся перенести деякі існуючі програми, які використовують boostбібліотеки, отриманий .exeфайл містить багато невикористаних об'єктних файлів і завдяки специфікаціям мого поточного вбудованого режиму виконання , запуск 10mbпрограми займає набагато більше часу, ніж, наприклад, запуск 500kпрограми.
Yippie-Ki-Yay

8
@Yippie: Ви хочете позбутися коду, щоб мінімізувати час завантаження; код, від якого ви хочете позбутися - це невикористані методи / тощо. з бібліотек. Так, для цього вам потрібно побудувати графік викликів. Це не так просто; він повинен бути глобальним графіком викликів, він повинен бути консервативним (не може видалити щось, що може звикнути) і повинен бути точним (щоб у вас був максимально наближений до ідеального графік викликів, щоб ви дійсно знали, що не є б / в). Великою проблемою є глобальний, точний графік виклику. Не знаю багатьох компіляторів, які роблять це, не кажучи вже про лінкери.
Іра Бакстер

Відповіді:


131

Для GCC це здійснюється у два етапи:

Спочатку зберіть дані, але скажіть компілятору розділити код на окремі розділи в блоці перекладу. Це буде зроблено для функцій, класів та зовнішніх змінних за допомогою наступних двох прапорів компілятора:

-fdata-sections -ffunction-sections

Зв’яжіть одиниці перекладу разом, використовуючи прапор оптимізації лінкера (це призводить до того, що лінкер відкидає нерозділені розділи):

-Wl,--gc-sections

Отже, якщо у вас був один файл під назвою test.cpp, у якому були задекларовані дві функції, але одна з них була невикористаною, ви можете опустити невикористаний, виконавши наступну команду gcc (g ++):

gcc -Os -fdata-sections -ffunction-sections test.cpp -o test -Wl,--gc-sections

(Зауважте, -Os - це додатковий прапор компілятора, який повідомляє GCC оптимізувати для розміру)


3
Зверніть увагу, це призведе до уповільнення виконання файлу згідно описів опцій GCC (я тестував).
метаморфоза

1
З mingwцим не спрацьовує при з'єднанні статично статично libstdc ++ та libgcc з прапором -static. Параметр linker -strip-allдопомагає зовсім небагато, але все-таки створений виконуваний файл (або dll) приблизно в 4 рази більший, ніж те, що генерує Visual Studio. Справа в тому, що я не контролюю, як libstdc++було складено. Має бути ldєдиний варіант.
Фабіо

34

Якщо вірити цьому потоку , вам потрібно поставити -ffunction-sectionsі-fdata-sections до gcc, який помістить кожну функцію та об’єкт даних у свій розділ. Потім ви даєте і --gc-sectionsGNU ld для видалення невикористаних секцій.


6
@MSalters: Це не за замовчуванням, оскільки він порушує стандарти C і C ++. Раптом глобальної ініціалізації не відбувається, що призводить до дуже здивованих програмістів.
Ben Voigt

1
@MSalters: Тільки якщо ви передасте нестандартні параметри порушення поведінки, які ви запропонували зробити типовими.
Ben Voigt

1
@MSalters: Якщо ви можете зробити виправлення, яке запускає статичні ініціалізатори, якщо і лише якщо необхідні побічні ефекти для правильної роботи програми, це було б приголомшливо. На жаль, я думаю, що це робити ідеально часто вимагає вирішення проблеми зупинки, тому вам, ймовірно, доведеться помилятися на стороні, включаючи додаткові символи часом. Що в основному - це те, що говорить Іра у коментарях до цього питання. (BTW: "не потрібне правильне функціонування програми" - це інше визначення "невикористаного", ніж те, як цей термін використовується в стандартах)
Бен Войгт

2
@BenVoigt в C глобальна ініціалізація не може мати побічних ефектів (ініціалізатори повинні бути постійними виразами)
MM

2
@Matt: Але це не вірно в C ++ ... і вони поділяють той самий лінкер.
Бен Войгт

25

Ви хочете перевірити свої документи на свою версію gcc & ld:

Однак для мене (OS X gcc 4.0.1) я знаходжу їх для ld

-dead_strip

Видаліть функції та дані, які недоступні для точки входу чи експортованих символів.

-dead_strip_dylibs

Видаліть діліб, який недоступний для точки входу або експортованих символів. Тобто, пригнічує генерацію команд завантаження команд для dylibs, які не надавали символів під час посилання. Цю опцію не слід застосовувати під час посилання на диліб, який необхідний під час виконання з певної непрямої причини, наприклад, що диліб має важливий ініціалізатор.

І це корисний варіант

-why_live symbol_name

Журнали ланцюга посилань на ім'я символу. Застосовується лише з -dead_strip. Це може допомогти налагодити, чому те, що, на вашу думку, повинно бути мертвою смугою, не видаляється.

У людині gcc / g ++ є також примітка, що певні види усунення мертвих кодів виконуються лише в тому випадку, якщо під час компіляції включена оптимізація.

Хоча ці параметри / умови можуть не відповідати вашому компілятору, я пропоную вам знайти щось подібне у своїх документах.


Здається, це нічого не робить mingw.
Фабіо

-dead_stripне є gccваріантом.
ar2015

20

Звички програмування теж можуть допомогти; наприклад, додати staticдо функцій, до яких немає доступу за межами певного файлу; використовувати короткі назви символів (може допомогти трохи, швидше за все, не надто); використовувати const char x[]де можливо; ... цей документ , хоч і говорить про динамічні спільні об'єкти, може містити пропозиції, які, якщо дотримуватися, можуть допомогти зменшити розмір остаточного бінарного виводу (якщо ваша мета - ELF).


4
Як це допомагає вибрати короткі назви символів?
фуз

1
якщо символи не знімаються, ça va sans жахливий - але, здається, це потрібно сказати зараз.
ShinTakezou

@fuz У статті йдеться про динамічні спільні об'єкти (наприклад, .soна Linux), тому імена символів потрібно зберігати, щоб такі API, як ctypesмодуль FFI Python, могли використовувати їх для пошуку символів за назвою під час виконання.
ssokolow

18

Відповідь така -flto. Ви повинні передати його як до компіляції, так і до посилань, інакше це нічого не робить.

Насправді це працює дуже добре - зменшив розмір програми мікроконтролера, яку я написав, менше ніж на 50% від її попереднього розміру!

На жаль, це здавалося трохи баггі - у мене були випадки, коли речі будуються неправильно. Можливо, це було пов’язано із системою збірки, яку я використовую (QBS; це дуже нова), але в будь-якому випадку я рекомендую вам увімкнути її лише для остаточної збірки, якщо це можливо, і протестувати її.


1
"-Wl, - gc-section" не працює на MinGW-W64, "-flto" працює для мене. Спасибі
rhbc73

Вихідна збірка дуже дивна, -fltoя не розумію, що це робить за сценою.
ar2015

Я вважаю, що -fltoвін не компілює кожен файл до збірки, він компілює їх у LLVM IR, а потім остаточне посилання компілює їх так, ніби вони всі в одному блоці компіляції. Це означає, що він може усунути невикористані функції та вбудовані не- staticті, а також, ймовірно, й інші речі. Дивіться llvm.org/docs/LinkTimeOptimization.html
Timmmm

13

Хоча не строго щодо символів, якщо ви хочете за розміром - завжди компілюйте -Osі -sпрапори. -Osоптимізує отриманий код для мінімального розміру виконуваного файлу та -sвидаляє таблицю символів та інформацію про переміщення із виконуваного файлу.

Іноді - якщо бажано невеликого розміру - розігрування з різними прапорами оптимізації може мати або не може мати значення. Наприклад, перемикання -ffast-mathта / або часом -fomit-frame-pointerможе заощадити навіть десятки байтів.


Більшість налаштувань оптимізації все ще дасть правильний код, якщо ви дотримуєтесь мовного стандарту, але я зазнав -ffast-mathпотворності повністю кодового стандарту C ++, тому я ніколи не рекомендував би його.
Raptor007

11

Мені здається, що відповідь, яку надав Немо, є правильною. Якщо ці інструкції не працюють, проблема може бути пов’язана з версією gcc / ld, яку ви використовуєте, як вправу я склав приклад програми, використовуючи тут інструкції, детальні

#include <stdio.h>
void deadcode() { printf("This is d dead codez\n"); }
int main(void) { printf("This is main\n"); return 0 ; }

Потім я скомпілював код за допомогою прогресивно більш агресивних комутаторів видалення мертвого коду:

gcc -Os test.c -o test.elf
gcc -Os -fdata-sections -ffunction-sections test.c -o test.elf -Wl,--gc-sections
gcc -Os -fdata-sections -ffunction-sections test.c -o test.elf -Wl,--gc-sections -Wl,--strip-all

Ці параметри компіляції та зв'язування давали виконувані файли розміром 8457, 8164 та 6160 байт відповідно, найбільш суттєвий внесок, що надходить з декларації "смуга всіх". Якщо ви не можете створити подібні скорочення на своїй платформі, можливо, ваша версія gcc не підтримує цю функціональність. Я використовую gcc (4.5.2-8ubuntu4), ld (2.21.0.20110327) на Linux Mint 2.6.38-8-generic x86_64


8

strip --strip-unneededпрацює лише в таблиці символів вашого виконуваного файлу. Він фактично не видаляє будь-який виконуваний код.

Стандартні бібліотеки досягають результату, якого ви шукаєте, розділяючи всі їх функції на окремі об’єктні файли, які поєднуються за допомогою ar. Якщо потім зв’язати отриманий архів як бібліотеку (тобто дати опцію-l your_library ld), то ld буде містити лише об'єктні файли, а отже, і символи, які фактично використовуються.

Ви також можете знайти деякі відповіді на подібне питання використання.


2
Окремі файли об’єктів у бібліотеці є актуальними лише при виконанні статичного посилання. Із загальнодоступними бібліотеками вся бібліотека завантажується, але, звичайно, не включається у виконуваний файл.
Джонатан Леффлер

4

Я не знаю, чи це допоможе у вашому поточному становищі, оскільки це нещодавня особливість, але ви можете визначити видимість символів у глобальному масштабі. Проходження -fvisibility=hidden -fvisibility-inlines-hiddenпри компіляції може допомогти лінкеру згодом позбутися від зайвих символів. Якщо ви створюєте виконуваний файл (на відміну від спільної бібліотеки) більше нічого робити.

Більше інформації (та тонкодослідного підходу, наприклад, для бібліотек) доступно на вікі GCC .


4

З посібника GCC 4.2.1, розділ -fwhole-program:

Припустимо, що поточний блок компіляції представляє всю програму, яка складається. Усі загальнодоступні функції та змінні, за винятком mainта об'єднаних за атрибутом, externally_visibleстають статичними функціями, а вплив впливає на більш агресивну оптимізацію міжпроцедурними оптимізаторами. Хоча ця опція еквівалентна правильному використанню staticключового слова для програм, що складаються з одного файлу, у поєднанні з опцією --combineцей прапор може використовуватися для компіляції більшості менших масштабних програм C, оскільки функції та змінні стають локальними для всього комбінованого блоку компіляції, а не для сам файл-джерело.


Так, але це, мабуть, не працює з будь-яким інкрементним складанням, і, ймовірно, буде трохи повільним.
Timmmm

@Timmmm: Я підозрюю, що ти думаєш -flto.
Бен Войгт

Так! Згодом я виявив це (чому це не одна з відповідей?). На жаль, це здавалося трохи баггі, тому я лише рекомендував би його для остаточної збірки, а потім тестую, що будує багато!
Timmmm

-1

Ви можете використати смужку двійкових файлів на об’єктному файлі (наприклад, виконуваний файл), щоб зняти з нього всі символи.

Примітка: він змінює сам файл і не створює копію.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.