Правильная статическая линковка

333
13 мая 2017, 21:12

Задался вопросом как же правильно строить библиотеки для статической линковки. Вопрос возник после просмотра содержимого результирующего исполняемого файла. Дело в том, что после линковки некоторых библиотек в исполняемый файл попадает много незадействованного кода. Эксперимент оформил шелл-скриптом для *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
$CC -ffunction-sections -fdata-sections -o testo-1f.o testo-1.c
ar rcs libtesto-1.a testo-1.o
ar rcs libtesto-1f.a testo-1f.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
$CXX -ffunction-sections -fdata-sections -o testo-3f.o testo-3.cpp
ar rcs libtesto-3.a testo-3.o
ar rcs libtesto-3f.a testo-3f.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-1.c -ffunction-sections -fdata-sections -Wl,--gc-sections -L. -ltesto-1f -o testo-1f
$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-3.cpp -ffunction-sections -fdata-sections -Wl,--gc-sections -L. -ltesto-3f -o testo-3f
$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`
echo "Найдено функций в  1f-м исполняемом файле: "`nm testo-1f | grep SomeFunc | wc -l`
echo "Найдено методов в  3f-м исполняемом файле: "`nm testo-3f | grep SomeMethod | wc -l`

Результат работы скрипта таков:

Найдено функций в   1-м исполняемом файле: 20
Найдено функций во  2-м исполняемом файле: 1
Найдено методов в   3-м исполняемом файле: 20
Найдено методов в   4-м исполняемом файле: 1
Найдено функций в  1f-м исполняемом файле: 1
Найдено методов в  3f-м исполняемом файле: 1

Как видно, что если разносить функции и методы в разные единицы трансляции, и результат собирать в библиотеку - то "ненужное" при линковке в исполняемый модуль не попадает.

Соответственно пара вопросов:

  1. Можно ли как-то линкер "упросить" не заносить ненужное в исполняемый файл не используя выше означенный подход?

  2. На сколько применим выше означенный подход для С++ (касаемо удаления реализации неиспользуемых методов класса)?

В данной теме прошу не поднимать вопросы проектирования, вопрос именно по линковке.

Answer 1

В большей мере с проблемой разобрался, отвечаю сам себе - может кому еще пригодится.

В случае, когда в библиотеку включается набор функций/методов, скомпилированных в одну единицу трансляции, можно использовать т.н. "function-level linking".

Для GCC/Clang это решается следующим образом:

  • Модуль(и) компилируем с флагами -ffunction-sections -fdata-sections
  • Собираем в библиотеку
  • Компилируем и линкуем результирующий исполняемый файл с флагами -ffunction-sections -fdata-sections -Wl,--gc-sections

Тестовый скрипт в вопросе изменил, действительно работает как должно.

Для VC++ протестировать нет возможности, решается так же флагами:

  • Для компилятора /Gy (Enable Function-Level Linking)
  • Для линкера /OPT:REF
READ ALSO
Как сделать завершение цикла?

Как сделать завершение цикла?

Мне нужно сделать выход по нажатию определенной кнопки, как сделать?

381
Как создать шаблонный алиас?

Как создать шаблонный алиас?

Хочу сделать проверку типа итератора каким-то таким способом:

386
Последовательность от a до zzz

Последовательность от a до zzz

Как перебрать последовательность букв в цикле? каждую последовательность нужно иметь возможность получить, зная номер позиции

320
Непонятная битовая операция

Непонятная битовая операция

Что может означать операция i = i & (i+1) в реализации дерева отрезков?

291