Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[18.226.251.22] |
|
Сообщ.
#1
,
|
|
|
Приветствую!
Задался вопросом как же правильно строить библиотеки для статической линковки. Вопрос возник после просмотра содержимого результирующего исполняемого файла. Дело в том, что после линковки некоторых библиотек в исполняемый файл попадает много незадействованного кода. Эксперимент оформил шелл-скриптом для *nix: Скрытый текст #!/bin/sh if [ `whoami` = "root" ]; then echo "Под учеткой рута работать отказываюсь." exit fi # если clang не установлен - нужно заменить на gcc и g++ соответственно CC="clang -Wall -pedantic -O3 -c" CXX="clang++ -Wall -pedantic -O3 -c" LINK1="clang -static -O3" LINK2="clang++ -static -O3" # генерация библиотеки 1 ================================== # # все функции включаются в одну единицу трансляции # # ========================================================= printf "#include <stdio.h>\n\n" > testo-1.c for i in $(seq 20) do printf "void SomeFunc_$i() {\n" >> testo-1.c printf " printf(\"SomeFunc_$i\\\n\");\n" >> testo-1.c printf "}\n\n" >> testo-1.c done $CC testo-1.c ar rcs libtesto-1.a testo-1.o rm -f *.c *.o # генерация библиотеки 2 ================================== # # каждая функция включается в отдельную единицу трансляции # # ========================================================= for i in $(seq 20) do printf "#include <stdio.h>\n\n" > testo-2-$i.c printf "void SomeFunc_$i() {\n" >> testo-2-$i.c printf " printf(\"SomeFunc_$i\\\n\");\n" >> testo-2-$i.c printf "}\n\n" >> testo-2-$i.c done $CC testo-2-*.c ar rcs libtesto-2.a `ls testo-2-*.o` rm -f *.c *.o # генерация библиотеки 3 ================================== # # создается класс, где реализация методов включается в одну # единицу трансляции # # ========================================================= printf "#pragma once\n\n" > testo-3.h printf "class GodClass {\n" >> testo-3.h printf " public:\n" >> testo-3.h for i in $(seq 20) do printf " void SomeMethod_$i();\n" >> testo-3.h done printf "};\n" >> testo-3.h printf "#include <iostream>\n" > testo-3.cpp printf "#include \"testo-3.h\"\n\n" >> testo-3.cpp for i in $(seq 20) do printf "void GodClass::SomeMethod_$i() {\n" >> testo-3.cpp printf " std::cout << \"SomeMethod_$i\" << std::endl;\n" >> testo-3.cpp printf "}\n\n" >> testo-3.cpp done $CXX testo-3.cpp ar rcs libtesto-3.a testo-3.o rm -f *.cpp *.o # генерация библиотеки 4 ================================== # # создается класс, где реализация методов разносится по # разным единицам трансляции # # ========================================================= printf "#pragma once\n\n" > testo-4.h printf "class GodClass {\n" >> testo-4.h printf " public:\n" >> testo-4.h for i in $(seq 20) do printf " void SomeMethod_$i();\n" >> testo-4.h done printf "};\n" >> testo-4.h for i in $(seq 20) do printf "#include <iostream>\n" > testo-4-$i.cpp printf "#include \"testo-4.h\"\n\n" >> testo-4-$i.cpp printf "void GodClass::SomeMethod_$i() {\n" >> testo-4-$i.cpp printf " std::cout << \"SomeMethod_$i\" << std::endl;\n" >> testo-4-$i.cpp printf "}\n\n" >> testo-4-$i.cpp done $CXX testo-4-*.cpp ar rcs libtesto-4.a `ls testo-4-*.o` rm -f *.cpp *.o # линкуем исполняемые файлы =============================== printf "void SomeFunc_1();\n\n" > testo-1.c printf "int main() {\n" >> testo-1.c printf " SomeFunc_1();\n" >> testo-1.c printf "}\n" >> testo-1.c cp testo-1.c testo-2.c $LINK1 testo-1.c -L. -ltesto-1 -o testo-1 $LINK1 testo-2.c -L. -ltesto-2 -o testo-2 printf "#include \"testo.h\"\n\n" > testo-3.cpp printf "int main() {\n" >> testo-3.cpp printf " GodClass G;\n" >> testo-3.cpp printf " G.SomeMethod_1();\n" >> testo-3.cpp printf "}\n" >> testo-3.cpp cp testo-3.cpp testo-4.cpp mv testo-3.h testo.h $LINK2 testo-3.cpp -L. -ltesto-3 -o testo-3 $LINK2 testo-4.cpp -L. -ltesto-4 -o testo-4 rm -f *.c* *.h # анализируем что попало в исполняемые файлы ============== clear echo "Найдено функций в 1-м исполняемом файле: "`nm testo-1 | grep SomeFunc | wc -l` echo "Найдено функций во 2-м исполняемом файле: "`nm testo-2 | grep SomeFunc | wc -l` echo "Найдено методов в 3-м исполняемом файле: "`nm testo-3 | grep SomeMethod | wc -l` echo "Найдено методов в 4-м исполняемом файле: "`nm testo-4 | grep SomeMethod | wc -l` Результат работы скрипта таков: Найдено функций в 1-м исполняемом файле: 20 Найдено функций во 2-м исполняемом файле: 1 Найдено методов в 3-м исполняемом файле: 20 Найдено методов в 4-м исполняемом файле: 1 Как видно, что если разносить функции и методы в разные единицы трансляции, и результат собирать в библиотеку - то "ненужное" при линковке в исполняемый модуль не попадает. Соответственно пара вопросов: 1) Можно ли как-то линкер "упросить" не заносить ненужное в исполняемый файл не используя выше означенный подход? 2) На сколько применим выше означенный подход для С++ (касаемо удаления реализации неиспользуемых методов класса)? В данной теме прошу не поднимать вопросы проектирования, вопрос именно по линковке. |
Сообщ.
#2
,
|
|
|
Насколько я разумею, то это стандартное поведение линкера.
И есть трюк, для выбрасывания некой сущности. Нужно создать в своём объектном модуле сущность с таким же названием и подсунуть его раньше подключения статических библиотек. А линкер, правомочно считает, что один объектный модуль, созданный из одной единици трансляции, разделять нельзя. Под visual c++ всё точно так же. А вообще, присоеденюсМне тоже интересно, как уговорить линкер, пихать в исполняемый файл, только то, что в этом файле нужно и не более. |
Сообщ.
#3
,
|
|
|
Линкер обычно работает на уровне объектных модулей. Т.е. если приложению нужна пара функций из библиотеки, то линкер, обнаружив эти имена в конкретном модуле, извлекает его из библиотеки целиком и целиком подлинковывает к приложению. Если модуль содержит пятьдесят функций помимо нужных двух, то все эти лишние 48 тоже окажутся в приложении. Бывают оптимизирующие линкеры, которые умеют выдёргивать по отдельным функциям, бывают компиляторы, которые формируют для линкера специфически оформленный модуль с объектным кодом, позволяющий даже обычному линкеру получить такой же эффект, но всё это зависит от реализации, и зачастую и компилятор, и линкер должны уметь договариваться друг с другом.
Для максимальной оптимизации при сохранении независимости от реализации можно просто не размещать в одной единице трансляции более одной функции. Тогда любой линкер сможет выдёргивать объектный код максимально эффективно по размеру целевого приложения. RTL обычно так и устроена. |
Сообщ.
#4
,
|
|
|
Цитата Qraizer @ Бывают оптимизирующие линкеры, которые умеют выдёргивать по отдельным функциям, бывают компиляторы, которые формируют для линкера специфически оформленный модуль с объектным кодом, позволяющий даже обычному линкеру получить такой же эффект Можно примеры этого, уж больно заманчиво. |
Сообщ.
#5
,
|
|
|
Например, майкрософтовый компилер имеет /Gy (Enable Function-Level Linking), который разделяет функции, размещая каждую в отдельной секции. Соответственно линкер имеет /OPT с параметром REF, который удаляет сущности, на которые нет ссылок. К слову, опасная штука в общем случае, некоторые ссылки могут быть сильно неявные.
|
Сообщ.
#6
,
|
|
|
Qraizer, да, для GCC/Clang тож нашел подобное:
По поводу "опасности" такого решения в инете почитал обратное - опасность крайне низкая, т.к. линкер параноидально оставляет большое количество неисполняемого кода. Существуют методики "допиливания", типа переноса при сборке явно неиспользуемого кода в секцию /DISCARD/. Но тут я "пробежался по верхам", операция многоступенчатая, с изучением -Wl,--trace-symbol=<symbol_name> и/или -Wl,-Map=output.map -Wl,--cref Тем не менее, сенкс, где копать - понятно ADD: Похоже флаг -fvtable-gc в поздних версиях GCC/Clang был удален, а его функционал стал "по-умолчанию" |
Сообщ.
#7
,
|
|
|
Цитата Qraizer @ Например, майкрософтовый компилер имеет /Gy (Enable Function-Level Linking), который разделяет функции, размещая каждую в отдельной секции. Соответственно линкер имеет /OPT с параметром REF, который удаляет сущности, на которые нет ссылок. К слову, опасная штука в общем случае, некоторые ссылки могут быть сильно неявные. Читал об этих параметрах, но тогда не проникся. Вот оказывается для чего это надо. Попробую поэксперементировать. |
Сообщ.
#8
,
|
|
|
JoeUser, а оно надо? Проще иначе.
|
Сообщ.
#9
,
|
|
|
Цитата Qraizer @ Проще иначе. Ну сказал "а", скажи уж и "б"))) Как? |
Сообщ.
#10
,
|
|
|
Цитата Qraizer @ а оно надо? Проще иначе. Субъективно моё имхо. Да, надо! Нефиг лишнему коду, делать в исполняемом файле. "копейка рубль бережёт" "курочка по зёрнышку клюёт" А делать по другому, то есть руками формировать дополнительные объектные модули, это лишний геморой. |
Сообщ.
#11
,
|
|
|
В GCC ещё есть LTO
Тоже помогает лишний код выбрасывать. Ну и вообще более мощная штука, например инлайнить может на этапе сборки линкером. https://gcc.gnu.org/wiki/LinkTimeOptimization |
Сообщ.
#12
,
|
|
|
Цитата cppasm @ В GCC ещё есть LTO Тоже помогает лишний код выбрасывать. Это я сразу попробовал, слышал об этом. Но чет не исключало неиспользуемый код. |