В некоторых российских организациях есть внутренний стандарт оформления исходных кодов на языке программирования Си. В частности и у нас тоже есть много требований к внешнему виду кода.
У меня уже был текст Синхронизация порядка объявлений и определений глобальных функций. Однако аналогичная история также актуальна для static функций. Среди 463х правил внутреннего code style у нас есть и такое обязательное требование к оформлению кода:
Последовательность объявления static функций должна совпадать с последовательностью определения static функций в конце *.с файла.
Я подозреваю, всем здесь понятно, что, если бы static си-функции были определены в начале си-файла, то этого наркоманского правила бы вообще не существовало в принципе. Однако здесь вам не тут! У нас также есть ещё одно нелепое правило
Все static функции должны быть определены в самом низу *.с файла.
В связи с этим, чтобы код вообще собирался теперь приходится ещё и искусственно объявлять повторно прототипы этих самых static функций в самом начале *.с файла. Нормально так да? А раз так, то пусть тогда и порядок совпадает. Цирк с конями, да и только... Это что?
Это ярчайший пример того, как буквально на пустом месте можно создать проблем себе и коллегам в колёса на много-много лет вперёд. Это классический пример ничем не обоснованной IT муштры.
Дело ясное, что поведение кода будет одним и тем же, что отсортированы static прототипы что не отсортированы, погоду это не изменит. Компилятору это всё равно. Модульные тесты (если они есть) в любом случае будут проходить одинаково.
Можно подумать, что нам тут ну просто больше заняться не чем, кроме как перетасовывать местами определения функций. Бантики какие-то привязывать, вместо реальной работы.
У нас нет ни одного модульного теста. Мы собирает код в Arduino-образной IDE, У нас в репозитории одна единственная сборка-франкенштейн сразу для трех разных электронных плат, у нас нет UART-CLI, у нас в прошивке нет загрузчика, у нас в прошивке нет NVRAM. Зато мы заботимся о том, чтобы пресловутый прядок объявления static функций совпадал с последовательностью определения static функций в конце *.с файла. Это как?
Как там говорят: "Один ноль в пользу Байдона"?
Если исключить теорию заговора и подрывные диверсии сверху, то тогда это получается обыкновенная глупость. Те, кто придумывает такого рода требования к коду просто не понимает причинно-следственную связь в программировании. Не понимает, какие усилия приведут к результату, а какие нет.
Однако правило такое тут существует, и чтобы фиксации с кодом прошили цензуру на Gerrit(е) и попали в общак, как ни крути, надо это требование исполнять.
В чем проблема?
Проблема ещё и в том, что тот интеллектуальный сотрудник, который нафантазировал все эти правила, поленился накропать утилиты для автоматического контроля всего своего этого законотворчества. Наверное в случае разработки утилит-надзирателей его пыл бы быстро подугас.
Однако правило обязательное и с этим, как ни крути, приходится жить.
Любая разработка начинается только тогда, когда появляются полноценные средства для отладки.
Очевидно же, что вручную выслеживать эти нарушения порядка объявлений и определений — это работа адовая! Очень утомительно, рутинно, долго и дорого. Поэтому программисты у нас 12 лет лелеяли мечту на появление волшебной палочки: консольной программы, которая автоматически находит эти пресловутые нарушения порядка static объявлений.
И, как водится, такой утилиты в открытом доступе до сегодняшнего дня не существовало в природе, подобно тому как чистого плутония не было в земной коре до появления первых рукотворных атомных реакторов.
Поэтому я и написал такую утилиту-локатор. Назвал её prototype_check с ключом csp. Пришлю утилиту всем коллегам по несчастью.
Что надо из софтвера?
Вам надо будет установить CygWin чтобы извлечь из него вот эти утилиты:
# | Название утилиты | Для чего эта утилита |
1 | awk / gawk | анализатор и обработчик csv строчек |
2 | winMerge | Сравнение текстовых файлов |
3 | sed | удаление и замена текста в файле |
4* | ctags | извлекатор текстовых токенов из исходников в разных языках программирования |
5 | cmp | утилита сравнения файлов |
Каков план?
Я предлагаю реализовать вот такой программный конвейер. Всего 4 стадии. Фронт работ такой:
Реализация
Ядром всего этого решения является старинная утилита ctags. У ctags тут главная роль. Поясним, те опции утилиты ctags, которые нам понадобятся в этом решении здесь и сейчас.
Опция ctags | Расшифровка |
-x --c-types=f | извлечь из *.с файла только функции |
-x --c-kinds=p | извлечь из *.с файла прототипы функций |
--sort=no | не сортировать в отчёте найденные токены (функций) |
-fcTagReport.txt | сохранить отчет в файл сTagReport.txt |
А теперь пояснения к алгоритму работы утилиты. Итак, танцуем от печки...
Фаза 1: Показать все функции в *.c файле
Чтобы показать только функции надо написать
ctags.exe --sort=no -fcTagReport.txt -x --c-types=f i2c_drv.c
Кристаллизовался вот такой сырой лог
Скрытый текст
I2C_Init function 783 C:\projects\source\I2C\i2c_drv.c STD_RESULT I2C_Init(void)
I2C_DeInit function 896 C:\projects\source\I2C\i2c_drv.c STD_RESULT I2C_DeInit(void)
I2C_StartReading function 968 C:\projects\source\I2C\i2c_drv.c STD_RESULT I2C_StartReading(const U8 nBus,
I2C_HighLevel_RX_ISR function 1758 C:\projects\source\I2C\i2c_drv.c void I2C_HighLevel_RX_ISR(const U8 nBus)
I2C_HighLevel_TX_ISR function 1775 C:\projects\source\I2C\i2c_drv.c void I2C_HighLevel_TX_ISR(const U8 nBus)
I2C_HighLevel_Error_ISR function 1932 C:\projects\source\I2C\i2c_drv.c void I2C_HighLevel_Error_ISR(const U8 nBus)
I2C_LL_GPIOInit function 2048 C:\projects\source\I2C\i2c_drv.c static void I2C_LL_GPIOInit(const U8 nArgBus)
I2C_LL_GPIODeInit function 2144 C:\projects\source\I2C\i2c_drv.c static void I2C_LL_GPIODeInit(const U8 nArgBus)
I2C_SetConfigTransfer function 2246 C:\projects\source\I2C\i2c_drv.c static STD_RESULT I2C_SetConfigTransfer(const I2C_BUS nArgBus,
I2C_SendData function 2417 C:\projects\source\I2C\i2c_drv.c static STD_RESULT I2C_SendData(const I2C_BUS nArgBus,
I2C_ReceiveData function 2470 C:\projects\source\I2C\i2c_drv.c static STD_RESULT I2C_ReceiveData(const I2C_BUS nArgBus,
I2C_ResetCtrl2Register function 2527 C:\projects\source\I2C\i2c_drv.c static STD_RESULT I2C_ResetCtrl2Register(const I2C_BUS nArgBus)
I2C_ClearFlags function 2593 C:\projects\source\I2C\i2c_drv.c static STD_RESULT I2C_ClearFlags(const I2C_BUS nArgBus,
I2C_CheckFlag function 2661 C:\projects\source\I2C\i2c_drv.c static STD_RESULT I2C_CheckFlag(const U8 nBus,
I2C_SetInterruptState function 2728 C:\projects\source\I2C\i2c_drv.c static STD_RESULT I2C_SetInterruptState(const U8 nArgBus,
I2C_RefreshTxdtRegister function 2783 C:\projects\source\I2C\i2c_drv.c static void I2C_RefreshTxdtRegister(I2C_tag* const pArgHw)
I2C_ConversionCfgIndex function 2810 C:\projects\source\I2C\i2c_drv.c static STD_RESULT I2C_ConversionCfgIndex(const U8 nArgBus,
Фаза 2: Удалить все не static строчки
В отчете ctags надо оставить только static функции. Или надо удалить все строки, которые не содержат ключевого слова static. Это можно сделать консольной утилитой sed
sed -i '/ static /!d' cTagReport.txt
Остались только определения static функций. Получается вот такой лог
Скрытый текст
I2C_LL_GPIOInit function 2048 C:\projects\source\third_party\I2C\i2c_drv.c static void I2C_LL_GPIOInit(const U8 nArgBus)
I2C_LL_GPIODeInit function 2144 C:\projects\source\third_party\I2C\i2c_drv.c static void I2C_LL_GPIODeInit(const U8 nArgBus)
I2C_SetConfigTransfer function 2246 C:\projects\source\third_party\I2C\i2c_drv.c static STD_RESULT I2C_SetConfigTransfer(const I2C_BUS nArgBus,
I2C_SendData function 2417 C:\projects\source\third_party\I2C\i2c_drv.c static STD_RESULT I2C_SendData(const I2C_BUS nArgBus,
I2C_ReceiveData function 2470 C:\projects\source\third_party\I2C\i2c_drv.c static STD_RESULT I2C_ReceiveData(const I2C_BUS nArgBus,
I2C_ResetCtrl2Register function 2527 C:\projects\source\third_party\I2C\i2c_drv.c static STD_RESULT I2C_ResetCtrl2Register(const I2C_BUS nArgBus)
I2C_ClearFlags function 2593 C:\projects\source\third_party\I2C\i2c_drv.c static STD_RESULT I2C_ClearFlags(const I2C_BUS nArgBus,
I2C_CheckFlag function 2661 C:\projects\source\third_party\I2C\i2c_drv.c static STD_RESULT I2C_CheckFlag(const U8 nBus,
I2C_SetInterruptState function 2728 C:\projects\source\third_party\I2C\i2c_drv.c static STD_RESULT I2C_SetInterruptState(const U8 nArgBus,
I2C_RefreshTxdtRegister function 2783 C:\projects\source\third_party\I2C\i2c_drv.c static void I2C_RefreshTxdtRegister(I2C_tag* const pArgHw)
I2C_ConversionCfgIndex function 2810 C:\projects\source\third_party\I2C\i2c_drv.c static STD_RESULT I2C_ConversionCfgIndex(const U8 nArgBus,
Фаза 3: Из отчёта надо выделить только первую колонку
Выделять колонки из текстовых файлов можно утилитой awk. Вот такой командой.
gawk '{print $1}' cTagReport.txt > definitions.txt
Фаза 4: Извлечь прототипы функций
Чтобы извлечь из src.c в cTagReport.txt только прототипы надо выполнить вот такую команду
ctags --sort=no -fcTagReport.txt -x --c-kinds=p src.c
Получится вот такой файл со списком прототипов.
Скрытый текст
I2C_LL_GPIOInit prototype 719 C:\projects\code_base_firmware\source\I2C\i2c_drv.c static void I2C_LL_GPIOInit(const U8 nArgBus);
I2C_LL_GPIODeInit prototype 722 C:\projects\code_base_firmware\source\I2C\i2c_drv.c static void I2C_LL_GPIODeInit(const U8 nArgBus);
I2C_SetConfigTransfer prototype 725 C:\projects\code_base_firmware\source\I2C\i2c_drv.c static STD_RESULT I2C_SetConfigTransfer(const I2C_BUS nArgBus,
I2C_SendData prototype 730 C:\projects\code_base_firmware\source\I2C\i2c_drv.c static STD_RESULT I2C_SendData(const I2C_BUS nArgBus,
I2C_ReceiveData prototype 734 C:\projects\code_base_firmware\source\I2C\i2c_drv.c static STD_RESULT I2C_ReceiveData(const I2C_BUS nArgBus,
I2C_ResetCtrl2Register prototype 738 C:\projects\code_base_firmware\source\I2C\i2c_drv.c static STD_RESULT I2C_ResetCtrl2Register(const I2C_BUS nArgBus);
I2C_ClearFlags prototype 741 C:\projects\code_base_firmware\source\I2C\i2c_drv.c static STD_RESULT I2C_ClearFlags(const I2C_BUS nArgBus,
I2C_CheckFlag prototype 745 C:\projects\code_base_firmware\source\I2C\i2c_drv.c static STD_RESULT I2C_CheckFlag(const U8 nBus,
I2C_SetInterruptState prototype 750 C:\projects\code_base_firmware\source\I2C\i2c_drv.c static STD_RESULT I2C_SetInterruptState(const U8 nArgBus,
I2C_RefreshTxdtRegister prototype 755 C:\projects\code_base_firmware\source\I2C\i2c_drv.c static void I2C_RefreshTxdtRegister(I2C_tag* const pArgHw);
I2C_ConversionCfgIndex prototype 758 C:\projects\code_base_firmware\source\I2C\i2c_drv.c static STD_RESULT I2C_ConversionCfgIndex(const U8 nArgBus,
Фаза 5: Из отчета прототипов выделить только имена функций
Теперь надо выделить только имена функций. Это по сути первый столбец. Такие задачи решаются культовой утилитой awk
gawk '{print $1}' cTagReport.txt > declarations.txt
Внутри текстового фaйла declarations.txt кристаллизовался список функций так, как он представлен в части, где происходит объявления прототипов static функций.
Фаза 6: сравнить списки функций
На этом этапе задача свелась к обыкновенному сравнению двух текстовых файлов. По идее они должны быть одинаковые, как две капли воды. Сравнивать текстовые файлы можно утилитой cmp
cmp -s declarations.txt definitions.txt
Результат сравнения и покажет аномалии.
Вот полный скрипт который делает проверку
cls
echo off
set srcFile=some_driver.c
set cTagFile=cTagReport.txt
set StativRepDef=StativRepDef.txt
set StativRepDec=StativRepDec.txt
rm -f %cTagFile%
rm -f %StativRepDef%
rm -f %StativRepDec%
set options=--sort=no
set options=%options% -f%cTagFile%
set options=%options% -x --c-types=f
ctags.exe %options% %srcFile%
sed -i '/ static /!d' %cTagFile%
gawk '{print $1}' %cTagFile% > %StativRepDef%
rm -f %cTagFile%
set options=--sort=no
set options=%options% -f%cTagFile%
set options=%options% -x --c-kinds=p
ctags %options% %srcFile%
gawk '{print $1}' %cTagFile% > %StativRepDec%
cmp -s %StativRepDec% %StativRepDef%
echo errorlevel=%errorlevel%
if "%errorlevel%"=="0" (echo same) else (echo diff)
Так как скрипт нужен очень часто, то я инкапсулировал его в консольную утилиту prototype_check. Эта утилита некоторый аналог утилиты BusyBox. По ключу вызываются другие утилиты. Скрипт проверки static прототипов будет вызываться по ключу csp (check static prototype).
Отладка
Вот так выглядит лог утилиты в случае успеха
А вот такой лог будет в случае отрицательного успеха
Утилита prototype_check производит тщательный контроль создает два текстовых файла, чтобы увидеть недочёт. Показывает какие именно static функции обитают не на своём месте. Tool(а) в самом деле обнаружила рассинхрон в определениях. Это наглядно видно утилитой WinMerge.
Таким образом программист получает целеуказание на те static функции, которые надо так или иначе подвинуть. Теперь понятно что делать.
Итоги
В сухом остатке, мне удалось разработать уникальную утилиту-надзиратель, которая как лакмусовая бумажка автоматически показывает потери синхронизации между порядком объявления и порядком определения static функций в *.с файле на языке программирования Си.
Любо братцы жить
С нашей утилитой не приходится тужить
2x
Надеюсь, что утилита prototype_check или её версия в виде скрипта помогут другим программистам микроконтроллеров, которые тоже работают в условиях диктатуры внутреннего стандарта оформления программных текстов.
Если нужна эта утилита обращайтесь.
Словарь
акроним | Расшифровка |
CSV | Comma-separated values |
csp | Check Static Prototyes |