К вопросу о gcc в разрезе RISCV

"Удивительное рядом, но оно запрещено"

Разбирался тут на досуге с (относительно) новыми МК фирмы WCH с ядром RISCV (CH32V307FBP6) с целью осветить данный прибор, скачал рекомендованную среду разработки (mounriver) и на первом же примере наткнулся на совершенно неожиданную вещь. Пример совершенно классический (нет, не мигание светодиодом и даже не приветствие миру) и посвящен использованию функции printf, вот его ключевая часть:

USART_Printf_Init(115200);
printf("SystemClk:%d\r\n"),SystemCoreClоck);
printf("This is printf example\r\n");

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

Файл листинга в дереве проекта был обнаружен довольно-таки быстро, поскольку китайская IDE основана на Eclipse, с которой я ранее неоднократно имел дело. Какого же было мое удивление, когда я обнаружил следующий фрагмент ассемблерного кода:

jal <Usart_Printf_Init>
....
jal <iprintf>
....
jal <puts>

Здесь оставлено самое интересное для данного поста для первого вызова демонстрируемой функции в строке 3 подставлен адрес функции iprintf (скорее всего, печать целого числа), а для второго вызова подставлен адрес функции puts (скорее всего, печать строки). Пнп: на самом деле в ассемблере есть еще много интересного в плане формирования параметров вызова, но об этом в другой раз. Хм, мне всегда казалось, что в С такие трюки невозможны. В С++ к нашим услугам и перегрузка и шаблоны и, наверное, что-нибудь еще (автор не считает себя знатоком данного языка), но в "чистом С" ... странно как то.

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

Гипотеза 1 - у нас на самом дела С++, легко опровергается - попытка создать описание класса отвергнута компилятором.

Гипотеза 2 - мы имеем дело с каким то собственным компилятором, опровергается путем изучения ключей компиляции и сообщений - имеется он выглядит, как обычный gcc. Конечно, остается возможность того, что кто-то замаскировал свой компилятор под стандартный, но вероятность такого события будем считать низкой.

Гипотеза 3 (по настоящему интересная и с нее я начал) - у нас какой то офигенно хитрый макрос вместо настоящей функции (хоть и с именем, записанным маленькими буквами), вызвала у меня нездоровое оживления Пнп: я всегда хотел делать именно так, причем исключительно средствами языка. Но в сырках лежит честный хидер функции, правда с добавочным атрибутом, который не производит впечатление установки перегрузки имени функции.

int printf (const char *_restrict, ...) _ATTRIBUTE ((__format__(__printf__, 1, 2))) 


Начинаю эксперименты и быстро устанавливаю, что iprintf - это просто алиас для собственно printf, а вовсе не функция печати исключительно целых чисел, и во всех случаях, кроме печати одиночной строки формата, подставляется вызов именно этой функции (независимо от типа выводимых данных, в том числе и для печати одинокой строки через формат %s). Ладно, смотрю, что это за атрибут и ничего, что бы намекало на полиморфизм (а ведь очень похоже) не нахожу, так что гипотеза опровергнута.

Есть еще гипотеза 4 - вариант с хитрыми ключами компиляции, хотя я их не вижу в строке вызова компилятора. Пытаюсь обнаружить данную оптимизацию (для архитектуры RISCV) на любимом сайте godbolt и не наблюдаю ее. Значит, или ключи как-то хитро скрыты в недрах среды программирования, или их просто нет, склоняюсь к последнему.

Вроде как получается по Холмсу - отбросьте все невозможные объяснения и оставшееся будет истинным, каким бы малоправдоподобным оно ни выглядело. В данном случае это вариант гипотезы 2 - модификация компилятора с целью локальной оптимизации. Возможно, это сделано только для одной архитектуры и тогда логично искать оптимизацию в генераторе кода, но, возможно, что оптимизация проводится на этапе генерации RTL кода и тогда она будет наличествовать всегда.

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

@GarryC
19.01.2024 13:15 UTC
Первоисточник

Комментарии

@PikNic
19.01.2024 08:23 UTC
+9

а вовсе не функция печати исключительно целых чисел

А на самом деле функция просто не поддерживает float-ы, достаточно привычная для МК штука. Я погуглил за вас, чтобы убедиться в этом касательно приставки i и riscv-newlib. Дело закрыто, статью в топку

@olartamonov
19.01.2024 10:21 UTC
+1

Да и RISC-V тут абсолютно ни при чём.

LDFLAGS += --specs=nano.specs

19.01.2024 11:22 UTC
+1

И чинится через -u _printf_float

@DungeonLords
19.01.2024 10:21 UTC
0

Самые частые грабли при использовании printf в программах под микроконтроллеры
По умолчанию не работает вывод float

@mpa4b
19.01.2024 10:16 UTC
0

Такую замену на puts gcc делает уже хрен знает сколько лет и на всех платформах.

@GarryC
22.01.2024 07:45 UTC
0

Хм, а на сайте godbolt почему-то не делает, что меня и смутило.

22.01.2024 08:55 UTC
0

https://godbolt.org/z/1jPcGTe54 самый ранний amd64 gcc на годболте вот уже делает.

22.01.2024 10:31 UTC
0

Ни фига не понимаю, Ваш вариант действительно дает puts, но мой упорно printf.

Нашел, это забавно, если у Вас убрать в строке печати "\n", то появляется printf.

Причем \n должен стоять последним символом, очень забавно.

22.01.2024 12:34 UTC
+1

Ну логично же, puts() добавляет перевод строки.

22.01.2024 12:57 UTC
0

Ага, и еще убирает конец строки в строковой константе, прикольно.

@DungeonLords
19.01.2024 10:45 UTC
0

Возможно вам также будут интересны
Анализ сишного Hello World
Как напечатать float