2016-04-27 4 views
3

Я пытаюсь преобразовать 2-байтовый массив в одно 16-битное значение. По какой-то причине, когда я передавал массив как 16-разрядный указатель и затем разыменовывал его, порядок байтов значения менялся местами.Кастинг uint8_t массив в значение uint16_t в C

Например,

#include <stdint.h> 
#include <stdio.h> 

main() 
{ 
    uint8_t a[2] = {0x15, 0xaa}; 

    uint16_t b = *(uint16_t*)a; 
    printf("%x\n", (unsigned int)b); 
    return 0; 
} 

печатает aa15 вместо 15aa (что я бы ожидать).

В чем причина этого, и есть ли легкое решение?

Мне известно, что я могу сделать что-то вроде uint16_t b = a[0] << 8 | a[1]; (это работает очень хорошо), но я чувствую, что эта проблема должна быть легко разрешена с помощью кастинга, и я не уверен, что вызывает здесь проблему.

+6

Это из-за [endianness] (https://en.wikipedia.org/wiki/Endianness). Вы не решите его, выполнив кастинг: ваш shift-and-add (или) хорош. –

+0

http://stackoverflow.com/questions/22030657/little-endian-vs-big-endian – fukanchik

ответ

6

Как упоминалось в комментариях, это связано с endianness.

Ваша машина имеет малоконечный язык, который (среди прочего) означает, что многобайтовые целочисленные значения имеют младший старший байт.

Если вы скомпилировали и запустили этот код на большой машине (например, на солнце), вы получите ожидаемый результат.

Поскольку ваш массив настроен как big-endian, что также является сетевым порядком байтов, вы можете обойти это, используя ntohs и htons. Эти функции преобразования 16-битное значение из сетевого порядка байт (большой байтов) на порядок байт хоста и наоборот:

uint16_t b = ntohs(*(uint16_t*)a); 

Есть аналогичные функции, вызываемые ntohl и htonl, которые работают на 32-битных значений.

+0

Этот код нарушает строгое правило псевдонимов –

-1

Это может помочь визуализировать вещи. Когда вы создаете массив, у вас есть два байта по порядку. Когда вы печатаете его, вы получаете человеко-читаемое шестнадцатеричное значение, которое является противоположностью маленького конца, которое оно хранило. Значение 1 в небольшой Endian как тип uint16_t хранится следующим образом, где a0 является ниже, чем адрес a1 ...

a0  a1 
|10000000|00000000 

Обратите внимание, младший байт первый, но, когда мы выводим значение в шестнадцатеричном его младший байт появляется справа, что мы обычно ожидаем на любой машине.

Эта программа печатает немного Endian и большой обратный порядок байт 1 в двоичном, начиная с наименее значащий байт ...

#include <stdint.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <arpa/inet.h> 

void print_bin(uint64_t num, size_t bytes) { 
    int i = 0; 
    for(i = bytes * 8; i > 0; i--) { 
    (i % 8 == 0) ? printf("|") : 1; 
    (num & 1) ? printf("1") : printf("0"); 
    num >>= 1; 
    } 
    printf("\n"); 
} 
int main(void) { 
    uint8_t a[2] = {0x15, 0xaa}; 
    uint16_t b = *(uint16_t*)a; 
    uint16_t le = 1; 
    uint16_t be = htons(le); 

    printf("Little Endian 1\n"); 
    print_bin(le, 2); 
    printf("Big Endian 1 on little endian machine\n"); 
    print_bin(be, 2); 
    printf("0xaa15 as little endian\n"); 
    print_bin(b, 2); 
    return 0; 
} 

Это выход (это наименее значимый байт первым)

Little Endian 1 
|10000000|00000000 
Big Endian 1 on little endian machine 
|00000000|10000000 
0xaa15 as little endian 
|10101000|01010101 
+0

Что имеет 'uint64_t' для решения вопроса или двухбайтового массива? Похоже, что это слишком сложная проблема. –

+0

Извините, мой DV, потому что '1' никогда не хранится как' 10000000' –

+0

Я не понимаю. Я не сказал, что он был сохранен как «100000001». Я использовал два байта, чтобы продемонстрировать, что он был сохранен с первым байтом, появляющимся слева. – Harry

0

Это из-за энтузиазма вашей машины.

Для того, чтобы сделать ваш код независимым от машины рассмотрим следующую функцию:

#define LITTLE_ENDIAN 0 
#define BIG_ENDIAN 1 

int endian() { 
    int i = 1; 
    char *p = (char *)&i; 

    if (p[0] == 1) 
     return LITTLE_ENDIAN; 
    else 
     return BIG_ENDIAN; 
} 

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

0

Вы не можете ничего сделать, как *(uint16_t*)a из-за strict aliasing rule. Даже если код пока работает, он может быть поврежден позже в другой версии компилятора.

Правильный вариант кода может быть:

b = ((uint16_t)a[0] << CHAR_BIT) + a[1]; 

версия предлагается в вашем вопросе с участием a[0] << 8 неверен потому, что в системе с 16-бит int, это может привести к подписанному целочисленного переполнения: a[0] способствует int, и << 8 означает * 256.

Смежные вопросы