c - приведение поведения uint8_t * к uint32_t * - PullRequest
2 голосов
/ 27 марта 2020

Я прочитал этот вопрос: Как работает приведение uint8 * к uint32 *? , но я не уверен в ответе.

Я новичок ie встроенный C программист, работающий над проектом, использующим G CC, и я проводил рефакторинг кусков кода, чтобы уменьшить использование памяти. Например, я изменил типы данных некоторых переменных с uint32_t на типы меньшего размера:

uint8_t colour;
uint16_t count;
uint16_t pixel;

func((uint32_t*)&colour);
func((uint32_t*)&count);
func((uint32_t*)&pixel);

Где func(uint32_t* ptr) изменяет значение, передаваемое в данные, которые он получает на подчиненном порту. Я сталкиваюсь с проблемой, когда приведенный выше код работает неправильно, когда включена оптимизация -O1, -O2, -O3 или -Os. Например, когда значения 1 5 1 получены на порт связи и функцию соответственно, значение, которое установлено для переменных, равно 0 0 1. Когда оптимизация не включена, значения установлены правильно.

Код работает правильно, если я изменяю типы данных обратно на uint32_t. Я не понимаю, почему я не получаю никаких предупреждений от компилятора (у меня включены дополнительные предупреждения). Причиной, по которой это происходит, является endianess / allignment?

Каковы подводные камни при переходе с указателя uint8_t на указатель uint32_t?

1 Ответ

3 голосов
/ 27 марта 2020

TLDR

Выполнение чего-либо вроде передачи адреса uint8_t чему-то ожидающему адрес uint32_t может привести к повреждению памяти, неизвестным результатам, незначительным ошибкам и просто код взрывается.

ДЕТАЛИ

Сначала, если функция объявлена ​​как

void func( uint32_t * arg );

и изменяет данные arg указывает на то, что передача ему адреса uint8_t или uint16_t приведет к неопределенному поведению и вероятности повреждения данных - если он вообще будет работать (продолжайте читать ...). Функция будет изменять данные, которые на самом деле не являются частью объекта, на который ссылается указатель, переданный в функцию.

Функция, как ожидается, будет иметь доступ к четырем байтам uint32_t, но вы дали ей адрес только один uint8_t байтов. Где остальные три байта go? Скорее всего, они нападают на что-то другое.

И даже если функция только читает память и не изменяет ее, вы не знаете, что находится в памяти, а не в реальном объекте, поэтому функция может вести себя непредсказуемо , И чтение может вообще не работать (продолжайте читать снова ...).

Кроме того, приведение адреса uint8_t является строгим нарушением псевдонимов. См. Что такое строгое правило псевдонимов? . Подводя итог, можно сказать, что в C вы не можете безопасно ссылаться на объект как на то, чем он не является, за исключением того, что вы можете ссылаться на любой объект, как если бы он состоял из правильного числа [signed|unsigned] char байтов.

Но приведение uint8_t адреса к uint32 * означает, что вы пытаетесь получить доступ к набору из четырех unsigned char значений (предполагая, что uint8_t на самом деле unsigned char, что почти наверняка верно в настоящее время) как один uint32_t объект, и это строгое нарушение псевдонимов, неопределенное поведение и небезопасное.

Симптомы, которые вы видите при нарушении правила строгого псевдонима, могут быть тонкими и действительно трудно найти и исправить. См. g cc, строгие псевдонимы и ужасные истории для некоторых, ну, ужасных историй.

Кроме того, если вы ссылаетесь на объект как на нечто нет, вы можете столкнуться с 6.3.2.3 указателями , параграф 7 стандарта C (C11) :

Указатель на объект Тип может быть преобразован в указатель на другой тип объекта. Если результирующий указатель неправильно выровнен для ссылочного типа, поведение не определено.

Это небезопасно даже на x86, независимо от того, что кто-то может рассказать вам о системах на базе x86.

Если вы когда-нибудь услышите, как кто-то скажет: «Ну, это работает, так что все не так.», Ну, они очень, очень неправы.

Они просто не наблюдали этого не удается.

пока.

...