На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
[!] Как относитесь к модерированию на этом форуме? Выскажите свое мнение здесь
Модераторы: Qraizer
  
> О статической линковке
    Приветствую!

    Задался вопросом как же правильно строить библиотеки для статической линковки. Вопрос возник после просмотра содержимого результирующего исполняемого файла. Дело в том, что после линковки некоторых библиотек в исполняемый файл попадает много незадействованного кода. Эксперимент оформил шелл-скриптом для *nix:

    Скрытый текст
    ExpandedWrap disabled
      #!/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`

    Результат работы скрипта таков:
    ExpandedWrap disabled
      Найдено функций в  1-м исполняемом файле: 20
      Найдено функций во 2-м исполняемом файле: 1
      Найдено методов в  3-м исполняемом файле: 20
      Найдено методов в  4-м исполняемом файле: 1



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

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

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

      И есть трюк, для выбрасывания некой сущности. Нужно создать в своём объектном модуле сущность с таким же названием и подсунуть его раньше подключения статических библиотек.

      А линкер, правомочно считает, что один объектный модуль, созданный из одной единици трансляции, разделять нельзя.

      Под visual c++ всё точно так же.

      А вообще, присоеденюсМне тоже интересно, как уговорить линкер, пихать в исполняемый файл, только то, что в этом файле нужно и не более.
      Сообщение отредактировано: Eric-S -
        Линкер обычно работает на уровне объектных модулей. Т.е. если приложению нужна пара функций из библиотеки, то линкер, обнаружив эти имена в конкретном модуле, извлекает его из библиотеки целиком и целиком подлинковывает к приложению. Если модуль содержит пятьдесят функций помимо нужных двух, то все эти лишние 48 тоже окажутся в приложении. Бывают оптимизирующие линкеры, которые умеют выдёргивать по отдельным функциям, бывают компиляторы, которые формируют для линкера специфически оформленный модуль с объектным кодом, позволяющий даже обычному линкеру получить такой же эффект, но всё это зависит от реализации, и зачастую и компилятор, и линкер должны уметь договариваться друг с другом.
        Для максимальной оптимизации при сохранении независимости от реализации можно просто не размещать в одной единице трансляции более одной функции. Тогда любой линкер сможет выдёргивать объектный код максимально эффективно по размеру целевого приложения. RTL обычно так и устроена.
          Цитата Qraizer @
          Бывают оптимизирующие линкеры, которые умеют выдёргивать по отдельным функциям, бывают компиляторы, которые формируют для линкера специфически оформленный модуль с объектным кодом, позволяющий даже обычному линкеру получить такой же эффект


          Можно примеры этого, уж больно заманчиво.
            Например, майкрософтовый компилер имеет /Gy (Enable Function-Level Linking), который разделяет функции, размещая каждую в отдельной секции. Соответственно линкер имеет /OPT с параметром REF, который удаляет сущности, на которые нет ссылок. К слову, опасная штука в общем случае, некоторые ссылки могут быть сильно неявные.
              Qraizer, да, для GCC/Clang тож нашел подобное:

              • Для компиляции модулей, включаемых в библиотеку, используем ключи: -ffunction-sections -fdata-sections -fvtable-gc
              • Для сборки исполняемого модуля используем ключи: -ffunction-sections -fdata-sections -fvtable-gc -Wl,--gc-sections

              По поводу "опасности" такого решения в инете почитал обратное - опасность крайне низкая, т.к. линкер параноидально оставляет большое количество неисполняемого кода. Существуют методики "допиливания", типа переноса при сборке явно неиспользуемого кода в секцию /DISCARD/. Но тут я "пробежался по верхам", операция многоступенчатая, с изучением -Wl,--trace-symbol=<symbol_name> и/или -Wl,-Map=output.map -Wl,--cref

              Тем не менее, сенкс, где копать - понятно :)

              ADD: Похоже флаг -fvtable-gc в поздних версиях GCC/Clang был удален, а его функционал стал "по-умолчанию"
              Сообщение отредактировано: JoeUser -
                Цитата Qraizer @
                Например, майкрософтовый компилер имеет /Gy (Enable Function-Level Linking), который разделяет функции, размещая каждую в отдельной секции. Соответственно линкер имеет /OPT с параметром REF, который удаляет сущности, на которые нет ссылок. К слову, опасная штука в общем случае, некоторые ссылки могут быть сильно неявные.

                Читал об этих параметрах, но тогда не проникся. Вот оказывается для чего это надо. Попробую поэксперементировать.
                  JoeUser, а оно надо? Проще иначе.
                    Цитата Qraizer @
                    Проще иначе.

                    Ну сказал "а", скажи уж и "б"))) Как?
                      Цитата Qraizer @
                      а оно надо? Проще иначе.

                      Субъективно моё имхо. Да, надо! Нефиг лишнему коду, делать в исполняемом файле.
                      "копейка рубль бережёт"
                      "курочка по зёрнышку клюёт"

                      А делать по другому, то есть руками формировать дополнительные объектные модули, это лишний геморой.
                        В GCC ещё есть LTO
                        Тоже помогает лишний код выбрасывать.
                        Ну и вообще более мощная штука, например инлайнить может на этапе сборки линкером.
                        https://gcc.gnu.org/wiki/LinkTimeOptimization
                          Цитата cppasm @
                          В GCC ещё есть LTO
                          Тоже помогает лишний код выбрасывать.

                          Это я сразу попробовал, слышал об этом. Но чет не исключало неиспользуемый код.
                          0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                          0 пользователей:


                          Рейтинг@Mail.ru
                          [ Script execution time: 0,0418 ]   [ 16 queries used ]   [ Generated: 28.03.24, 18:35 GMT ]