Порядковая последовательность в C
В проекте микроконтроллера, написанном на C, мы определили следующие макросы для доступа к разным байтам многобайтовой переменной (4 байта). long
):
#define BYTE_0(var) (*((unsigned char*) &var))
#define BYTE_1(var) (*(((unsigned char*) &var) + 1))
#define BYTE_2(var) (*(((unsigned char*) &var) + 2))
BYTE_0()
обращается к младшему значащему байту и так далее. Это потому, что мы находим, что в случае, когда нам нужно обращаться к разным байтам многобайтовой переменной отдельно (микро в 8 битах), доступ к байтам с использованием приведенного выше кода приводит к меньшему количеству строк кода в сборке. Так как объем памяти кода составляет всего 15 КБ, иногда полезно несколько байтов.
Микро, которое мы используем, имеет младший порядок. Мне интересно, если мы перенесем код на другой микро, который является архитектурой с прямым порядком байтов, будет ли работать код выше? Другими словами, стандарт C гарантирует, что (*((unsigned char*) &var))
даст наименее значимый байт var
?
5 ответов
Ваш макрос не работает, он подразумевает архитектуру с прямым порядком байтов. Стандарт C ничего не гарантирует в случае вашего кода. Независимый от байтов код обычно пишется с помощью побитовых операторов, поскольку они ведут себя одинаково независимо от того, где размещен байт ls.
some_long & 0xFF
гарантируется стандартом C, чтобы дать вам байт ls независимо от порядка байтов, в то время как (uint8_t*)&some_long
является порядком байтов
Эта ссылка подробно отвечает на ваш вопрос: http://www.ibm.com/developerworks/aix/library/au-endianc/ Сделайте макрос, аналогичный приведенному в листинге 12, с побитовым сдвигом и побитовым AND, и он будет переносимым,
Нет, это то, что означает порядок байтов. Ваш код не будет работать на машинах с обратным порядком байтов.
И я даже не совсем уверен, что такая ручная оптимизация имеет значение. Возможно, лучший компилятор оптимизировал бы лучше...
Нет
#define BYTE_0(var) (*((unsigned char*) &var))
даст вам байт, связанный с природой процессора / контроллера памяти, необязательно даже байта из переменной var. В идеале, если бы var был 0x12345678, вы бы надеялись увидеть 0x12 в некоторых системах и 0x78 в других.
#define BYTE_0(var) (var&0xFF)
дает вам младший байт переменной для любой системы. Предполагается, что переменная одинакова для всех систем.
чтобы завершить список.
#define BYTE_0(var) ((var>> 0)&0xFF)
#define BYTE_1(var) ((var>> 8)&0xFF)
#define BYTE_2(var) ((var>> 16)&0xFF)
#define BYTE_3(var) ((var>> 24)&0xFF)
НЕ используйте битовые поля вместо сдвига и маскирования, битовые поля не переносятся из компилятора в компилятор, порядковые номера одинаковые или разные. битовые поля "определены реализацией", и все возможные запутанные вещи можно найти в компиляторах.
будьте осторожны, пытаясь перевернуть ваш вопрос и построить переменную из байтов:
var = (b3<<24)|(b2<<16)|(b1<<8)|(b0<<0);
если b3,b2,b1,b0 определены как 8-битные переменные, компилятору не нужно повышать их до 32-битных перед сдвигом. В некоторых системах / компиляторах приведенный выше код дает желаемый эффект размещения четырех байтов в 32-битной переменной. Но в других системах var = b0 говорит то, что говорит приведенный выше код, потому что b1 - это сдвиг 8-битной переменной, он оставил 8, и у вас остались нули, аналогично сдвиг b2 16 и b3 24, и вы в конечном итоге получили
var = 0 | 0 | 0 | b0;
я предпочитаю
var = 0;
var <<= 8; var |= b3;
var <<= 8; var |= b2;
var <<= 8; var |= b1;
var <<= 8; var |= b0;
или же
var = b3;
var <<= 8; var |= b2;
var <<= 8; var |= b1;
var <<= 8; var |= b0;
какой порт довольно красиво. И оптимизатор должен дать вам такой же / тот же код, что и одна строка C с typedefs или решением с битовым полем.
Нет, не будет. Вы поразили причину, по которой с прямым порядком байтов лучше работать, чем с большим порядком байтов.
Также учтите, что приведение к разным целочисленным размерам не требует арифметики указателей.
Также неправильно предполагать, что long
всегда будет 4 байта. К сожалению, стандарт для x64, например, является LP64, то есть int
был оставлен как 4-х байтовое целое число.
Если вам нужен портативный способ доступа к байтам 4-байтового целого числа, вы можете использовать #define для изменения макросов в зависимости от архитектуры,
#ifdef LITTLE_ENDIAN
#define OFFSET_0 0
#define OFFSET_1 1
#define OFFSET_2 2
#else
#define OFFSET_0 3
#define OFFSET_1 2
#define OFFSET_2 1
#endif
#define BYTE_0(var) (*(((unsigned char*) &var) + OFFSET_0))
#define BYTE_1(var) (*(((unsigned char*) &var) + OFFSET_1))
#define BYTE_2(var) (*(((unsigned char*) &var) + OFFSET_2))
или, и это предпочтительнее IMO, если ваша архитектура поддерживает сдвиги более 1 бита.
#define BYTE_0(var) ((var) & 0xFF)
#define BYTE_1(var) (((var) >> 1 * CHAR_BIT) & 0xFF)
#define BYTE_2(var) (((var) >> 2 * CHAR_BIT) & 0xFF)
который работает даже для не lvars (например, результаты выражений, литералов) и является переносимым.