В среде IAR Embedded Workbench для ARM (как и в любом стандартном C компиляторе), отсутствие прототипов функций — это очень плохая практика, которая может привести к трудноуловимым ошибкам.
Исторически в стандарте C89/C90, если компилятор встречал вызов функции без предварительного объявления (прототипа), он делал неявное объявление (implicit declaration).
Вот список конкретных проблем, которые возникают из-за этого механизма в контексте ARM и IAR:
1. Предположение о возвращаемом типе int
Если прототипа нет, компилятор считает, что функция возвращает тип int.
Проблема: Если ваша функция на самом деле возвращает float, double, структуру или void, то вызывающий код будет интерпретировать результат неправильно.
Пример: Функция возвращает float (значение 3.14). Компилятор думает, что это int. В памяти представление float (IEEE 754) и int совершенно разное. Вы получите "мусорное" огромное целое число вместо 3.14.
2. Неправильная передача аргументов (ABI и регистры)
ARM использует соглашение о вызовах AAPCS (Procedure Call Standard for the Arm Architecture). Без прототипа компилятор не знает, какие типы данных ожидает функция, и применяет стандартные правила продвижения типов (default argument promotions):
float преобразуется в double.
char и short преобразуются в int.
Сценарий катастрофы:
Допустим, у вас есть функция: void motor_set_speed(float speed);
Вы вызываете её без прототипа: motor_set_speed(10.5f);
Вызывающий код (без прототипа): Видит аргумент float. По правилам varargs (так как типы неизвестны) продвигает его до double (8 байт). Кладет это значение в пару регистров (например, R0 и R1) или в регистры FPU для double (D0).
Вызываемая функция (реализация): Ожидает float (4 байта). Она будет читать значение из регистра для float (S0) или, если FPU выключен, только из R0.
Итог: Функция получит совершенно неверные данные, мотор может включиться на полную мощность или вести себя непредсказуемо.
3. Проблемы с использованием FPU (Floating Point Unit)
На процессорах Cortex-M4F, M7 и т.д. (где есть Hard FPU), передача чисел с плавающей точкой зависит от того, знает ли компилятор типы аргументов.
С прототипом: float передается через специальные регистры FPU (S0, S1...).
Без прототипа: Компилятор может передать значения через регистры общего назначения (Core registers R0-R3), так как он предполагает совместимость со старым кодом или variadic функциями.
Результат: Функция ищет данные в регистре FPU (S0), а вы положили их в обычный регистр (R0). Программа не упадет, но вычисления будут с мусором.
4. Несовпадение количества аргументов
Без прототипа компилятор не может проверить, передали ли вы правильное количество аргументов.
Функция: void update_lcd(int x, int y, int color);
Вызов: update_lcd(10, 20); // Забыли цвет
Что произойдет: Функция возьмет значение для color из регистра R2 (по стандарту ARM). В R2 в этот момент лежит случайное значение, оставшееся от предыдущих операций. Экран зальется случайным цветом или программа уйдет в HardFault, если этот мусор будет использован как указатель.
5. Риск при переходе на 64-битные архитектуры (ARM64)
Хотя Cortex-M обычно 32-битные, если вы пишете под ARM64 (Aarch64), проблема усугубляется.
Если функция возвращает указатель (void*), а компилятор думает, что она возвращает int (обычно 32 бита), то старшие 32 бита адреса будут отсечены. Это гарантированный Segmentation Fault (или HardFault) при попытке разыменования такого указателя.
6. C99 и новее
Начиная со стандарта C99, неявные объявления функций запрещены.
Если в настройках IAR стоит галочка "Require prototypes" (или выбран строгий режим C99/C11/C18), код просто не скомпилируется.
Если настройки "расслабленные" (по умолчанию IAR часто допускает расширения), вы получите только Warning. Игнорирование этого варнинга — частая причина багов в Embedded.
Резюме
В Embedded разработке (IAR/ARM) отсутствие прототипов приводит к рассинхронизации того, куда вызывающий кладет данные (какие регистры R0-R3, S0-S31, стек), и откуда вызываемый их забирает.
Рекомендация:
Всегда используйте заголовочные файлы (.h) с прототипами.
В настройках IAR включите опцию:
Project -> Options -> C/C++ Compiler -> Language 1 -> Require prototypes (Требовать прототипы). Это превратит потенциальные баги времени выполнения в ошибки компиляции.