У меня есть некоторое недопонимание относительно поведения компиляции MCU G CC в отношении функции, возвращающей другие вещи, имеющие 32-битное значение.
MCU: STM32 L0 Series (STM32L083)
GCC : gcc version 7.3.1 20180622 (release) [ARM/embedded-7-branch revision 261907] (GNU Tools for Arm Embedded Processors 7-2018-q2-update)
Мой код оптимизирован для размера (с опцией -Os
). Насколько я понимаю, это позволит g cc использовать неявный -fshort-enums
для упаковки перечислений.
У меня есть два enum var шириной 1 байт:
enum eRadioMode radio_mode // (@ 0x20003200)
enum eRadioFunction radio_func // (@ 0x20003201)
И функция:
enum eRadioMode radio_get_mode(enum eRadioFunction _radio_func);
Когда я вызываю эту кучу кода:
radio_mode = radio_get_mode(radio_func);
Она генерирует эту кучу ASM во время компиляции:
; At this point :
; r4 value is 0x20003201 (Address of radio_func)
7820 ldrb r0, [r4, #0] ; GCC treat correctly r4 as a pointer to 1 byte wide var, no problem here
f7ff ffcd bl 80098a8 <radio_get_mode> ; Call to radio_get_mode()
4d1e ldr r5, [pc, #120] ; r5 is loaded with 0x20003200 (Address of radio_mode)
6028 str r0, [r5, #0] ; Why GCC use 'str' and not 'strb' at this point ?
Последняя строка здесь - проблема: значение r0
, возвращаемое значение radio_get_mode()
, сохраняется по адресу, указанному r5
, как 32-битное значение. Поскольку radio_func
находится на 1 байт после radio_mode
, его значение перезаписывается вторым байтом r0
(это всегда 0x00, поскольку enum имеет ширину всего 1 байт).
В качестве моей функции radio_get_mode
объявлен как возвращающий 1 байт, почему G CC не использует инструкцию strb
, чтобы сохранить этот единственный байт по адресу, указанному r5
?
Я пробовал:
radio_get_mode()
при возврате uint8_t
: uint8_t radio_get_mode(enum eRadioFunction _radio_func);
- Принудительное приведение к
uint8_t
: radio_mode = (uint8_t)radio_get_mode(radio_func);
- Передача третьей переменной (но G CC отменить этот бесполезный ход при компиляции - не так уж и глупо):
uint32_t r = radio_get_mode(radio_func);
radio_mode = (uint8_t) r;
Но ни одно из этих решений не работает.
Поскольку сначала требуется оптимизация размера (-Os) зрение, чтобы уменьшить использование ROM (а не ram - в это время моего проекта -) Я обнаружил, что обходной путь g cc option -fno-short-enums
позволит компилятору использовать 4 байта по перечислению, отбрасывая, кстати, любую перекрывающуюся память в это случай.
Но, на мой взгляд, это грязный способ скрыть здесь настоящую проблему:
- Может ли G CC правильно обрабатывать другой размер возврата, кроме 32-битного?
- Есть правильный способ сделать это?
Заранее спасибо.
EDIT:
- Я НЕ использовал
-f-short-enums
в любой момент. - Я уверен, что это перечисление не имеет значения больше чем 0xFF
- Я пытался объявить
radio_mode
и radio_func
как uint8_t
(он же unsigned char
): проблема та же. - При компиляции с
-Os
, Output.map выглядит следующим образом:
Common symbol size file
...
radio_mode 0x1 src/radio/radio.o
radio_func 0x1 src/radio/radio.o
...
...
...
Section address label
0x2000319c radio_state
0x20003200 radio_mode
0x20003201 radio_func
0x20003202 radio_protocol
...
Вывод файла карты ясно показывает, что radio_mode
и radio_func
имеют ширину 1 байт и по следующему адресу.
- При компиляции без
-Os
, Output.map ясно показывает, что перечисления становятся шириной 4 байта (с дополнением адреса до 4). - При компиляции с
-Os
и -fno-short-enums
выполните те же действия, что и без -Os
для всех перечислений (вот почему я думаю, что -Os
подразумевает неявное -f-short-enums
) - Я постараюсь предоставить минимально воспроизводимый пример
- Мой анализ th Проблема в том, что я почти уверен, что это ошибка компилятора. Для меня это явно наложение воспоминаний. Мой вопрос больше о том, что лучше всего сделать, чтобы этого избежать - в соответствии с "передовой практикой".
РЕДАКТИРОВАТЬ 2
Это мой плохо, у меня есть повторный тестер, меняющий всю подпись на uint8_t
(он же unsigned char
), и он работает хорошо.
@ Питер Кордес, похоже, нашел здесь проблему: при его использовании -Os
частично включение -fshort-enums
, получение некоторых частей G CC для обработки его как размера 1 и других частей для обработки его как размера 4.
Код ASM с использованием только uint8_t
:
; Same position than before
7820 ldrb r0, [r4, #0]
f7ff ffcd bl 80098a8 <radio_get_mode>
4d1e ldr r5, [pc, #120]
7028 strb r0, [r5, #0] ; Yes ! GCC use 'strb' and not 'str' like before !
Чтобы уточнить:
- Кажется, есть ошибка компилятора при использовании
-Os
и перечислений. Плохая удача, что два перечисления находятся в последовательных адресах, которые перекрываются. - Использование
-fno-short-enums
в сочетании с -Os
кажется хорошим обходным путем IMO, поскольку проблема касается только перечисления, а не всех 1 byte var вообще.
Еще раз спасибо.