Есть ли способ автоматически удалить из библиотеки все неиспользуемые части и определения?

Скажем, вы хотите исследовать часть используемого вами кода из довольно большой библиотеки, но она скрыта между всеми видами #define, классами и т. д., которые вы никогда не будете использовать в своем коде. Есть ли инструмент для удаления этих неиспользуемых/ненужных частей (например, (пре-)компилятор) без преобразования его в машинный язык? Это значительно облегчило бы понимание того, что происходит в коде и какой код что делает. Имхо, это был бы отличный инструмент для обучения.

, 👍1

Обсуждение

Не совсем ответ на ваш вопрос, но... [команда avr-nm может сказать вам, сколько флэш-памяти используется каждой функцией или методом](https://arduinoprosto.ru/q/12684/7508 )., @Edgar Bonet

Хорошо знать! Я проверю список памяти из своего кода. Показывает ли он неиспользуемые функции, которые не занимают места после компиляции? Там тоже видел отсылку к grep, накатывает ностальгия... Блин, однажды я неплохо разобрался с grep и sed. Возможно, таким образом можно было бы разделить определения., @Gaai

avr-nm работает с выводом компилятора: он не может видеть то, что было удалено или оптимизировано компилятором. Встроенные функции также не будут показаны, так как они становятся частью встроенных функций (обычно setup() и loop() встраиваются в main()). Вы можете получить более релевантный вывод, если настроите параметры компилятора, чтобы удалить оптимизацию времени компоновки (-flto), так как это делает компилятор очень агрессивным при встраивании., @Edgar Bonet

вы блокируете параметр -E компилятора, который создает исходный код C++ после препроцессора?, @Juraj

@Юрай Может быть. Это больше похоже на то., @Gaai

Вы ищете инструмент, который «организует» все источники, чтобы вы могли быстро перемещаться по нему? Тогда, конечно, Arduino IDE не подходит. Возможно, вы захотите попробовать другие IDE, возможно, дополненные специальными плагинами Arduino или специально разработанными инструментами. И, конечно, вам нужны все исходные файлы рассматриваемой библиотеки, что не является обычным случаем., @the busybee

Я попробую код Visual Studio, посмотрим, что смогу найти. Получил уже для сайтов и javascript и там довольно много вариантов., @Gaai

Да! Я нашел классный инструмент для удаления #ifdef и #define вещей: [Coan](https://coan2.sourceforge.net/index.php?page=about) *бензопила препроцессора C*. Пошел разбираться ;), @Gaai


3 ответа


1

Компилятор вызывается в режиме оптимизации (можно даже указать, насколько агрессивно он будет оптимизировать). Он удалит все переменные и функции, которые не используются в вашем коде. Также он может заменить переменные, которые никогда не меняются, литералами, чтобы сделать код меньше. Это уже происходит, и вам не нужно об этом беспокоиться.

О #define: все, что начинается с #, интерпретируется препроцессором. Это означает, что эти вещи выполняются еще до того, как компилятор увидит код. Допустим, вы определяете номер контакта с помощью #define pin 6, тогда препроцессор заменит любое упоминание pin на 6 в коде, прежде чем он передает результат компилятору. Компилятор никогда не увидит никаких определений.

Я не могу вам сказать, как компилятор оптимизирует код. Наверное, это очень сложно. Хотя компилятор avr-gcc имеет открытый исходный код, поэтому, если вы действительно хотите узнать, что он делает, вы всегда можете проанализировать его исходный код.


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

Хотя я мало знаю о том, как работает оптимизация, я не думаю, что она происходит в таком порядке. Я считаю очень маловероятным, что компилятор оптимизирует код C++. Хотя это может быть сделано на уровне ассемблера (поскольку avr-gcc сначала преобразует код C++ в ассемблер, а затем компилирует в машинный код). Поиск в коде на ассемблере, эквивалентном коду C++, тоже не вариант.

Возможно, вы застряли с функцией полнотекстового поиска в выбранной вами программе. Большинство ОС также предоставляют эту функцию из коробки. Я знаю, что вы можете искать все файлы каталога по термину из проводника Windows. В таких системах, как Ubuntu/Debian/CentOS и подобных, вы можете использовать команду grep в рекурсивном режиме для поиска термина. На Mac вы, вероятно, также можете использовать grep.

,

По поводу «_постпроцессора_»: вы имеете в виду «препроцессор»., @Edgar Bonet

@EdgarBonet О, спасибо. В моей рекламе определенно был препроцессор , когда я писал это XD, @chrisl

Спасибо, что нашли время ответить. Я знал, что компилятор позаботится обо всем этом, но я хотел бы иметь возможность «быстро» просмотреть определенный блок кода (все еще C++, а не код компилятора), не разбрасывая его по библиотеке других функций и методов. Я подумал, что если компилятор может быть достаточно умен, чтобы удалить все неиспользуемые переменные и функции и преобразовать то, что осталось, в машинный язык, возможно, есть также инструмент, который только удаляет, но не преобразует. Было бы легче узнать, как работают определенные части таким образом. Возможно, немного фантазии ;), @Gaai

Ах хорошо. Теперь я понимаю лучше. Я добавил к своему ответу, хотя мой ответ на эту часть в основном просто нет, @chrisl

Я уточнил свой первоначальный вопрос, спасибо., @Gaai


0

Компилятор работает не совсем так. Он компилирует каждый отдельный файл .cpp в объектный файл (файл .ino становится файлом .cpp после небольшой предварительной обработки). У меня есть ответ о точном процессе, который использует компилятор.

Библиотеки также представляют собой наборы файлов .cpp, которые также превращаются в объектные файлы.

В этот момент произойдет оптимизация неиспользуемых переменных. Другими словами, компилятор может оптимизировать одну единицу компиляции (один исходный файл), поскольку он может видеть, какие части используются, а какие нет.

Затем начинается фаза компоновки, которая объединяет все объектные файлы вместе, ища места, где вызывается функция в библиотеке. Так, например, если вы где-нибудь вызовете Serial.begin(), компоновщик попытается найти объектный файл, реализующий это.

Компоновщик достаточно умен, чтобы не включать код, который не требуется, поэтому, например, если вы никогда не вызывали Serial.begin(), он не будет копировать в эту часть библиотеки Serial.

При этом он устраняет "прыжки" и вызовы подпрограмм, чтобы ваши вызовы библиотечных функций в конечном итоге указывали на то место в памяти программы, куда они были помещены.

Поскольку это делается на позднем этапе (после того, как исходный код был скомпилирован), у вас никогда не будет копии исходного кода "в виде ссылки".

Тем не менее, что вы можете сделать, так это разобрать объектный файл, который даст вам машинный код, фактически превратившийся в окончательный исполняемый файл. При правильном выполнении он также будет включать строки исходного (C++) кода, которые генерировали каждый пакет строк на ассемблере.

Если вы включите "подробную компиляцию" вы должны увидеть в конце компиляции вывод "ELF" файл, и затем вы можете отправить его в avr-objdump, чтобы превратить его обратно в ассемблер:

avr-objdump -S xxx.elf

Это не очень хорошо, если вы не привыкли к ассемблеру, но это дает вам представление о том, что и где попало в исполняемый файл. Для одной строки кода C++ вы можете увидеть что-то вроде этого:

    reg = portModeRegister(port);
 310:   90 e0           ldi r25, 0x00   ; 0
 312:   88 0f           add r24, r24
 314:   99 1f           adc r25, r25
 316:   fc 01           movw    r30, r24
 318:   e8 59           subi    r30, 0x98   ; 152
 31a:   ff 4f           sbci    r31, 0xFF   ; 255
 31c:   a5 91           lpm r26, Z+
 31e:   b4 91           lpm r27, Z+
,

0

Команда -E для gcc создает предварительно обработанный исходный код C и CPP.

Я не нашел способа интегрировать его в процесс сборки Arduino. Единственное, что я мог сделать, это отредактировать platform.txt и изменить команду компиляции, чтобы она генерировала предварительно обработанный cpp модуля компиляции, состоящего из файлов .o. Тогда связь, конечно же, не удалась.

По моему мнению, созданный cpp был менее удобочитаемым, чем оригинал, потому что он содержал много сгенерированных комментариев и исходных инструкций по номерам строк.

измененный рецепт cpp в txt платформы:

recipe.cpp.o.pattern="{compiler.path}{compiler.cpp.cmd}" {compiler.cpp.flags} -mmcu={build.mcu} -DF_CPU={build.f_cpu} -DARDUINO={runtime.ide.version} -DARDUINO_{build.board} -DARDUINO_ARCH_{build.arch} {compiler.cpp.extra_flags} {build.extra_flags} -E {includes} "{source_file}" -o "{object_file}.cpp"

Я добавил флаг -E и расширение файла .cpp для значения параметра -o

Вы можете увидеть результат во временной папке сборки. Вы можете узнать расположение этой папки из консоли IDE при создании скетча с помощью Verify. (может потребоваться включить подробную компиляцию в настройках).

,

Спасибо, мне нужно хорошенько все это посмотреть, чтобы увидеть, смогу ли я понять это. Не делал этого раньше. Это все командная строка? В настоящее время у меня есть Mac (пфф, все еще привыкаю к нему), еще не прикасался к терминалу., @Gaai

нет командной строки. это команда, используемая IDE для создания скетча для загрузки. вам нужно изменить его в файле platform.txt, и он будет генерировать предварительно обработанный cpp вместо создания шестнадцатеричного кода для загрузки., @Juraj

Прохладный. Скоро проверю., @Gaai