Я заполняю память следующим образом:
char buf[8] = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
А затем по очереди помещаем беззнаковый длинный указатель на первые 5 байтов и выводим результат:
char *c_ptr;
unsigned long *u_ptr;
c_ptr = buf;
for (int i=0;i<5;i++)
{
u_ptr = (unsigned long *)c_ptr;
printf("%X\n",*u_ptr);
c_ptr++;
}
Когда я выполняю этот код на моей платформе x64, я получаю то, что ожидал:
44332211
55443322
66554433
77665544
88776655
Но когда я выполняю тот же код на платформе ARM, я получаю следующее:
44332211
11443322
22114433
33221144
88776655
То есть он связывается каждые 4 байта и разыменовывает только 4 байта в этих пределах.
Поэтому я хочу спросить, если это поведение (когда pointer_value%4 != 0
) ошибочный или специфичный для реализации?
UPD:
Я знаю о Endiannes, я хочу знать, это правильно, что я получаю
11443322
вместо
55443322
Т.е. когда у меня есть указатель например 0x10000001
Это делает unsigned long из байтов с адресами 0x10000001
, 0x10000002
, 0x10000003
и тогда 0x10000000
, вместо 0x10000005
,
Заподозрив выравнивание памяти я сделал быстрый гугл =)
http://awayitworks.blogspot.co.nz/2010/02/arm-memory-alignment.html
Заявлено в этой статье:
До архитектуры ARMv4 предполагается, что адрес указан для извлечения
содержимое выровнено по памяти … 32-битная выборка данных должна иметь адрес
выровнен по 32-битному и так далее. Как угадал, проблема только
для 32-битной и 16-битной выборки данных. ARM игнорирует младшие 2 бита
адрес, если выборка данных 32-битная, и игнорирует младший 1-битный, если данные
выборка 16-битная. Таким образом, во всех, если адрес не выровнен
тогда выборка данных будет ошибочной.
Обратите внимание на последнее предложение =)
Если вам требуется поведение, ожидаемое на x86, вам придется явно собирать целые числа из символов, т.е. (при условии, что в порядке байтов):
// Endian-specific
inline unsigned long ulong_at( const char *p ) {
return ((unsigned long)p[0])
| (((unsigned long)p[1]) << 8)
| (((unsigned long)p[2]) << 16)
| (((unsigned long)p[3]) << 24);
}
Или возможно:
// Architecture-specific
inline unsigned long ulong_at( const char *p ) {
unsigned long val;
char *v = (char*)&val;
v[0] = p[0];
v[1] = p[1];
v[2] = p[2];
v[3] = p[3];
return val;
}
Если вы хотите извлечь четырехбайтовое слово из памяти, адрес должен быть кратным четырем.
Несовместимый доступ, как правило, является плохой идеей для любой архитектуры. Некоторые выдают SEGFAULT, другие прозрачно обрабатывают ошибку и — очень медленно — синтезируют правильное значение, выбирая два слова, содержащие желаемое значение, и соединяя их вместе. Кажется (хотя я не эксперт), что ARM выбирает четырехбайтовый слот, который занимает указатель, и поворачивает результат так, чтобы младший бит регистра совпадал с указателем.
Проблема в том, что вы разыменовываете невыровненный указатель, который, в зависимости от оборудования, может быть неопределенным. Многие архитектуры предполагают, что long*
будет выровнен до 32 бит памяти, то есть будет делиться на 4. Если это не так, результат не определен.
В общем, C не гарантирует, что происходит, когда вы приводите один тип указателя к другому.
Endianness не объясняет это поведение. Кажется, процессор ARM не разрешает доступ к четырехбайтовой памяти, не выровненный четырехбайтовой границей, и выходные данные указывают, что процессор считывает память, как если бы он был подвергнут битовое вращение вправо 8 битов на байт, доступ к которым осуществляется за пределами четырехбайтовой границы. Увидеть эта статья в Википедии для получения дополнительной информации о выравнивании памяти.
На самом деле, на некоторых архитектурах ошибка шины может произойти, если вы попытаетесь выполнить доступ к памяти без выравнивания такого рода.