Pull to refresh

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

Reading time1 min
Views13K
Допустим у нас есть функция, которая принимает в себя указатель. Мы знаем, что в указателе лежит нуль-терминальная строка, а за ней 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+strlen(str)+1, sizeof(int)); //копируем в нее данные
  printf("%s %d", str, value);  //выводим данные
}


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

UPD: Исправил очевидную ошибку.
Tags:
Hubs:
Total votes 51: ↑29 and ↓22+7
Comments55

Articles