О выравнивании памяти на ARM процессорах на простом примере

в 18:01, , рубрики: C, выравнивание памяти, ошибки, программирование микроконтроллеров, С++

Допустим у нас есть функция, которая принимает в себя указатель. Мы знаем, что в указателе лежит нуль-терминальная строка, а за ней 4-байтное целое. Задача — вывести в консоль строку и целое. Решить можно вот так:

void foo(void* data_ptr)
{
  //Ставим указатель на строку на начало данных
  char* str = (char*)data_ptr;
  //А указатель на целое смещаем на длину строки и еще один байт
  int* value = (int*)(str+strlen(str)+1);
  //и выводим содержимое указателей
  printf("%s %d", str, *value);
}

Довольно тривиальная задача, не так ли? Проверяем на компе (x86), все ОК. Загружаем на борду с ARM. И, не успев выстрелить себе в ногу, наступаем на грабли. В зависимости от содержания строки, целое значение выводится то нормальным, то кривым. Поверяем указатели, проверяем память, на которые они указывают. Все в норме.

Подмечаем, что целое выводится ровно, когда длина строки равна 3, 7, 11, ..., 4*n-1. Ага. По внимательней смотрим на память и на вывод в «кривых» случаях. Например, если память выглядит так:

Адрес:

|0x00|0x01|0x02|0x03|0x04|0x05|0x06|0x07|0x08|

Данные:

|0x31|0x31|0x31|0x31|0x00|0x01|0x00|0x00|0x00|

На выходе мы получаем строку «1111» и целое 0x00000100 вместо 0x00000001.

Вывод: Несмотря на то, что выраджением *value мы обращаемся по указателю 0x05, данные нам возвращаются как-будто обращение происходит по указателю 0x04 (или другому кратному 4).

Так как правильно решить такую задачу? А вот так:


void foo(void* data_ptr)
{
  int value; //Выделяем переменную на стеке
  char* str = (char*)data_ptr; 
  memcpy(&value, str, str+strlen(str)+1)); //копируем в нее данные
  printf("%s %d", str, value);  //выводим данные
}

В таком случае все всегда на своих местах.
Спасибо за внимание!

Автор: CasualLinux

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js