Как преобразовать значения с прямым порядком байтов и байтов с обратным порядком байтов в C ++? - PullRequest
179 голосов
/ 20 сентября 2008

Как преобразовать значения с прямым и младшим порядком в C ++?

РЕДАКТИРОВАТЬ: Для ясности мне нужно перевести двоичные данные (значения с плавающей запятой двойной точности и 32-разрядные и 64-разрядные целые числа) из одной архитектуры ЦП в другую. Это не связано с сетью, поэтому ntoh () и подобные функции здесь не будут работать.

РЕДАКТИРОВАТЬ # 2: Ответ, который я принял, относится непосредственно к компиляторам, на которые я нацеливаюсь (именно поэтому я выбрал его). Однако есть и другие очень хорошие, более портативные ответы.

Ответы [ 30 ]

1 голос
/ 11 мая 2017

Я действительно удивлен, что никто не упомянул функции htobeXX и betohXX. Они определены в endian.h и очень похожи на сетевые функции htonXX.

1 голос
/ 17 сентября 2016

С помощью приведенных ниже кодов вы можете легко переключаться между BigEndian и LittleEndian

#define uint32_t unsigned 
#define uint16_t unsigned short

#define swap16(x) ((((uint16_t)(x) & 0x00ff)<<8)| \
(((uint16_t)(x) & 0xff00)>>8))

#define swap32(x) ((((uint32_t)(x) & 0x000000ff)<<24)| \
(((uint32_t)(x) & 0x0000ff00)<<8)| \
(((uint32_t)(x) & 0x00ff0000)>>8)| \
(((uint32_t)(x) & 0xff000000)>>24))
0 голосов
/ 19 апреля 2019

Похоже, что безопасный способ - использовать htons для каждого слова. Итак, если у вас есть ...

std::vector<uint16_t> storage(n);  // where n is the number to be converted

// the following would do the trick
std::transform(word_storage.cbegin(), word_storage.cend()
  , word_storage.begin(), [](const uint16_t input)->uint16_t {
  return htons(input); });

Вышеприведенное было бы недопустимо, если бы вы работали в системе с прямым порядком байтов, поэтому я бы искал все, что ваша платформа использует в качестве условия времени компиляции, чтобы решить, является ли htons неактивным. В конце концов, это O (n). На Mac это было бы что-то вроде ...

#if (__DARWIN_BYTE_ORDER != __DARWIN_BIG_ENDIAN)
std::transform(word_storage.cbegin(), word_storage.cend()
  , word_storage.begin(), [](const uint16_t input)->uint16_t {
  return htons(input); });
#endif
0 голосов
/ 11 апреля 2019

Перестановка байтов с помощью старого трехшагового трюка вокруг стержня в шаблонной функции дает гибкое, быстрое решение O (ln2), которое не требует библиотеки, стиль здесь также отклоняет типы 1 байта:

template<typename T>void swap(T &t){
    for(uint8_t pivot = 0; pivot < sizeof(t)/2; pivot ++){
        *((uint8_t *)&t + pivot) ^= *((uint8_t *)&t+sizeof(t)-1- pivot);
        *((uint8_t *)&t+sizeof(t)-1- pivot) ^= *((uint8_t *)&t + pivot);
        *((uint8_t *)&t + pivot) ^= *((uint8_t *)&t+sizeof(t)-1- pivot);
    }
}
0 голосов
/ 23 мая 2017

Ниже описано, как читать двойные данные, хранящиеся в 64-разрядном формате IEEE 754, даже если ваш хост-компьютер использует другую систему.

/*
* read a double from a stream in ieee754 format regardless of host
*  encoding.
*  fp - the stream
*  bigendian - set to if big bytes first, clear for little bytes
*              first
*
*/
double freadieee754(FILE *fp, int bigendian)
{
    unsigned char buff[8];
    int i;
    double fnorm = 0.0;
    unsigned char temp;
    int sign;
    int exponent;
    double bitval;
    int maski, mask;
    int expbits = 11;
    int significandbits = 52;
    int shift;
    double answer;

    /* read the data */
    for (i = 0; i < 8; i++)
        buff[i] = fgetc(fp);
    /* just reverse if not big-endian*/
    if (!bigendian)
    {
        for (i = 0; i < 4; i++)
        {
            temp = buff[i];
            buff[i] = buff[8 - i - 1];
            buff[8 - i - 1] = temp;
        }
    }
    sign = buff[0] & 0x80 ? -1 : 1;
    /* exponet in raw format*/
    exponent = ((buff[0] & 0x7F) << 4) | ((buff[1] & 0xF0) >> 4);

    /* read inthe mantissa. Top bit is 0.5, the successive bits half*/
    bitval = 0.5;
    maski = 1;
    mask = 0x08;
    for (i = 0; i < significandbits; i++)
    {
        if (buff[maski] & mask)
            fnorm += bitval;

        bitval /= 2.0;
        mask >>= 1;
        if (mask == 0)
        {
            mask = 0x80;
            maski++;
        }
    }
    /* handle zero specially */
    if (exponent == 0 && fnorm == 0)
        return 0.0;

    shift = exponent - ((1 << (expbits - 1)) - 1); /* exponent = shift + bias */
    /* nans have exp 1024 and non-zero mantissa */
    if (shift == 1024 && fnorm != 0)
        return sqrt(-1.0);
    /*infinity*/
    if (shift == 1024 && fnorm == 0)
    {

#ifdef INFINITY
        return sign == 1 ? INFINITY : -INFINITY;
#endif
        return  (sign * 1.0) / 0.0;
    }
    if (shift > -1023)
    {
        answer = ldexp(fnorm + 1.0, shift);
        return answer * sign;
    }
    else
    {
        /* denormalised numbers */
        if (fnorm == 0.0)
            return 0.0;
        shift = -1022;
        while (fnorm < 1.0)
        {
            fnorm *= 2;
            shift--;
        }
        answer = ldexp(fnorm, shift);
        return answer * sign;
    }
}

Для остальной части набора функций, включая процедуры записи и целочисленного типа, см. Мой проект github

https://github.com/MalcolmMcLean/ieee754

0 голосов
/ 23 мая 2017

Попробуйте Boost::endian, и НЕ ОСУЩЕСТВЛЯЙТЕ ЕГО СЕБЯ!

Вот ссылка

0 голосов
/ 17 декабря 2016

Я недавно написал макрос для этого в C, но он одинаково действителен в C ++:

#define REVERSE_BYTES(...) do for(size_t REVERSE_BYTES=0; REVERSE_BYTES<sizeof(__VA_ARGS__)>>1; ++REVERSE_BYTES)\
    ((unsigned char*)&(__VA_ARGS__))[REVERSE_BYTES] ^= ((unsigned char*)&(__VA_ARGS__))[sizeof(__VA_ARGS__)-1-REVERSE_BYTES],\
    ((unsigned char*)&(__VA_ARGS__))[sizeof(__VA_ARGS__)-1-REVERSE_BYTES] ^= ((unsigned char*)&(__VA_ARGS__))[REVERSE_BYTES],\
    ((unsigned char*)&(__VA_ARGS__))[REVERSE_BYTES] ^= ((unsigned char*)&(__VA_ARGS__))[sizeof(__VA_ARGS__)-1-REVERSE_BYTES];\
while(0)

Он принимает любой тип и переворачивает байты в переданном аргументе. Пример использования:

int main(){
    unsigned long long x = 0xABCDEF0123456789;
    printf("Before: %llX\n",x);
    REVERSE_BYTES(x);
    printf("After : %llX\n",x);

    char c[7]="nametag";
    printf("Before: %c%c%c%c%c%c%c\n",c[0],c[1],c[2],c[3],c[4],c[5],c[6]);
    REVERSE_BYTES(c);
    printf("After : %c%c%c%c%c%c%c\n",c[0],c[1],c[2],c[3],c[4],c[5],c[6]);
}

Какие отпечатки:

Before: ABCDEF0123456789
After : 8967452301EFCDAB
Before: nametag
After : gateman

Вышесказанное прекрасно подходит для копирования / вставки, но здесь многое происходит, поэтому я расскажу, как это работает по частям:

Первое, что следует отметить, это то, что весь макрос заключен в блок do while(0). Это общая идиома , разрешающая нормальное использование точки с запятой после макроса.

Далее следует использовать переменную с именем REVERSE_BYTES в качестве счетчика цикла for. Имя самого макроса используется в качестве имени переменной, чтобы гарантировать, что он не конфликтует с любыми другими символами, которые могут находиться в области видимости везде, где используется макрос. Поскольку имя используется в расширении макроса, оно не будет расширено при использовании в качестве имени переменной здесь.

В цикле for есть два байта, на которые ссылаются, и XOR swapped (поэтому временное имя переменной не требуется):

((unsigned char*)&(__VA_ARGS__))[REVERSE_BYTES]
((unsigned char*)&(__VA_ARGS__))[sizeof(__VA_ARGS__)-1-REVERSE_BYTES]

__VA_ARGS__ представляет все, что было дано макросу, и используется для увеличения гибкости того, что может быть передано (хотя и ненамного). Адрес этого аргумента затем берется и приводится к указателю unsigned char, чтобы разрешить обмен его байтов с помощью массива [] с подпиской.

Последняя особенность - отсутствие {} скобок. В них нет необходимости, поскольку все шаги в каждом свопе объединяются с помощью оператора запятой , что делает их одним оператором.

Наконец, стоит отметить, что это не идеальный подход, если скорость является главным приоритетом. Если это важный фактор, некоторые из макросов, специфичных для типа, или директив, специфичных для платформы, на которые есть ссылки в других ответах, вероятно, являются лучшим вариантом. Однако этот подход переносим на все типы, на все основные платформы и на языки C и C ++.

0 голосов
/ 23 мая 2016

Переносная технология для реализации дружественных к оптимизатору невыровненных неконтактных методов доступа. Они работают на каждом компиляторе, каждом выравнивании границ и каждом порядке байтов. Эти невыровненные подпрограммы дополняются или обсуждаются в зависимости от собственного порядка байтов и выравнивания. Частичное перечисление, но вы поняли идею. BO * - постоянные значения, основанные на собственном порядке байтов.

uint32_t sw_get_uint32_1234(pu32)
uint32_1234 *pu32;
{
  union {
    uint32_1234 u32_1234;
    uint32_t u32;
  } bou32;
  bou32.u32_1234[0] = (*pu32)[BO32_0];
  bou32.u32_1234[1] = (*pu32)[BO32_1];
  bou32.u32_1234[2] = (*pu32)[BO32_2];
  bou32.u32_1234[3] = (*pu32)[BO32_3];
  return(bou32.u32);
}

void sw_set_uint32_1234(pu32, u32)
uint32_1234 *pu32;
uint32_t u32;
{
  union {
    uint32_1234 u32_1234;
    uint32_t u32;
  } bou32;
  bou32.u32 = u32;
  (*pu32)[BO32_0] = bou32.u32_1234[0];
  (*pu32)[BO32_1] = bou32.u32_1234[1];
  (*pu32)[BO32_2] = bou32.u32_1234[2];
  (*pu32)[BO32_3] = bou32.u32_1234[3];
}

#if HAS_SW_INT64
int64 sw_get_int64_12345678(pi64)
int64_12345678 *pi64;
{
  union {
    int64_12345678 i64_12345678;
    int64 i64;
  } boi64;
  boi64.i64_12345678[0] = (*pi64)[BO64_0];
  boi64.i64_12345678[1] = (*pi64)[BO64_1];
  boi64.i64_12345678[2] = (*pi64)[BO64_2];
  boi64.i64_12345678[3] = (*pi64)[BO64_3];
  boi64.i64_12345678[4] = (*pi64)[BO64_4];
  boi64.i64_12345678[5] = (*pi64)[BO64_5];
  boi64.i64_12345678[6] = (*pi64)[BO64_6];
  boi64.i64_12345678[7] = (*pi64)[BO64_7];
  return(boi64.i64);
}
#endif

int32_t sw_get_int32_3412(pi32)
int32_3412 *pi32;
{
  union {
    int32_3412 i32_3412;
    int32_t i32;
  } boi32;
  boi32.i32_3412[2] = (*pi32)[BO32_0];
  boi32.i32_3412[3] = (*pi32)[BO32_1];
  boi32.i32_3412[0] = (*pi32)[BO32_2];
  boi32.i32_3412[1] = (*pi32)[BO32_3];
  return(boi32.i32);
}

void sw_set_int32_3412(pi32, i32)
int32_3412 *pi32;
int32_t i32;
{
  union {
    int32_3412 i32_3412;
    int32_t i32;
  } boi32;
  boi32.i32 = i32;
  (*pi32)[BO32_0] = boi32.i32_3412[2];
  (*pi32)[BO32_1] = boi32.i32_3412[3];
  (*pi32)[BO32_2] = boi32.i32_3412[0];
  (*pi32)[BO32_3] = boi32.i32_3412[1];
}

uint32_t sw_get_uint32_3412(pu32)
uint32_3412 *pu32;
{
  union {
    uint32_3412 u32_3412;
    uint32_t u32;
  } bou32;
  bou32.u32_3412[2] = (*pu32)[BO32_0];
  bou32.u32_3412[3] = (*pu32)[BO32_1];
  bou32.u32_3412[0] = (*pu32)[BO32_2];
  bou32.u32_3412[1] = (*pu32)[BO32_3];
  return(bou32.u32);
}

void sw_set_uint32_3412(pu32, u32)
uint32_3412 *pu32;
uint32_t u32;
{
  union {
    uint32_3412 u32_3412;
    uint32_t u32;
  } bou32;
  bou32.u32 = u32;
  (*pu32)[BO32_0] = bou32.u32_3412[2];
  (*pu32)[BO32_1] = bou32.u32_3412[3];
  (*pu32)[BO32_2] = bou32.u32_3412[0];
  (*pu32)[BO32_3] = bou32.u32_3412[1];
}

float sw_get_float_1234(pf)
float_1234 *pf;
{
  union {
    float_1234 f_1234;
    float f;
  } bof;
  bof.f_1234[0] = (*pf)[BO32_0];
  bof.f_1234[1] = (*pf)[BO32_1];
  bof.f_1234[2] = (*pf)[BO32_2];
  bof.f_1234[3] = (*pf)[BO32_3];
  return(bof.f);
}

void sw_set_float_1234(pf, f)
float_1234 *pf;
float f;
{
  union {
    float_1234 f_1234;
    float f;
  } bof;
  bof.f = (float)f;
  (*pf)[BO32_0] = bof.f_1234[0];
  (*pf)[BO32_1] = bof.f_1234[1];
  (*pf)[BO32_2] = bof.f_1234[2];
  (*pf)[BO32_3] = bof.f_1234[3];
}

double sw_get_double_12345678(pd)
double_12345678 *pd;
{
  union {
    double_12345678 d_12345678;
    double d;
  } bod;
  bod.d_12345678[0] = (*pd)[BO64_0];
  bod.d_12345678[1] = (*pd)[BO64_1];
  bod.d_12345678[2] = (*pd)[BO64_2];
  bod.d_12345678[3] = (*pd)[BO64_3];
  bod.d_12345678[4] = (*pd)[BO64_4];
  bod.d_12345678[5] = (*pd)[BO64_5];
  bod.d_12345678[6] = (*pd)[BO64_6];
  bod.d_12345678[7] = (*pd)[BO64_7];
  return(bod.d);
}

void sw_set_double_12345678(pd, d)
double_12345678 *pd;
double d;
{
  union {
    double_12345678 d_12345678;
    double d;
  } bod;
  bod.d = d;
  (*pd)[BO64_0] = bod.d_12345678[0];
  (*pd)[BO64_1] = bod.d_12345678[1];
  (*pd)[BO64_2] = bod.d_12345678[2];
  (*pd)[BO64_3] = bod.d_12345678[3];
  (*pd)[BO64_4] = bod.d_12345678[4];
  (*pd)[BO64_5] = bod.d_12345678[5];
  (*pd)[BO64_6] = bod.d_12345678[6];
  (*pd)[BO64_7] = bod.d_12345678[7];
}

Эти typedefs имеют то преимущество, что выдают ошибки компилятора, если они не используются с аксессорами, таким образом уменьшая забытые ошибки аксессора.

typedef char int8_1[1], uint8_1[1];

typedef char int16_12[2], uint16_12[2]; /* little endian */
typedef char int16_21[2], uint16_21[2]; /* big endian */

typedef char int24_321[3], uint24_321[3]; /* Alpha Micro, PDP-11 */

typedef char int32_1234[4], uint32_1234[4]; /* little endian */
typedef char int32_3412[4], uint32_3412[4]; /* Alpha Micro, PDP-11 */
typedef char int32_4321[4], uint32_4321[4]; /* big endian */

typedef char int64_12345678[8], uint64_12345678[8]; /* little endian */
typedef char int64_34128756[8], uint64_34128756[8]; /* Alpha Micro, PDP-11 */
typedef char int64_87654321[8], uint64_87654321[8]; /* big endian */

typedef char float_1234[4]; /* little endian */
typedef char float_3412[4]; /* Alpha Micro, PDP-11 */
typedef char float_4321[4]; /* big endian */

typedef char double_12345678[8]; /* little endian */
typedef char double_78563412[8]; /* Alpha Micro? */
typedef char double_87654321[8]; /* big endian */
0 голосов
/ 24 сентября 2014

Ух ты, я не мог поверить в некоторые ответы, которые я прочитал здесь. На самом деле есть инструкция по сборке, которая делает это быстрее, чем что-либо еще. BSWAP. Вы можете просто написать такую ​​функцию ...

__declspec(naked) uint32_t EndianSwap(uint32 value)
{
    __asm
    {
        mov eax, dword ptr[esp + 4]
        bswap eax
        ret
    }
}

Это НАМНОГО быстрее, чем предложенные свойства. Я их разобрал и посмотрел. Вышеуказанная функция не имеет пролога / эпилога, поэтому практически не имеет накладных расходов.

unsigned long _byteswap_ulong(unsigned long value);

Выполнение 16-битной системы так же просто, за исключением того, что вы используете xchg al, ах. bswap работает только на 32-битных регистрах.

64-бит немного сложнее, но не слишком. Гораздо лучше, чем все приведенные выше примеры с циклами, шаблонами и т. Д.

Здесь есть некоторые предостережения ... Во-первых, bswap доступен только для процессоров 80x486 и выше. Кто-нибудь планирует запустить его на 386?!? Если это так, вы все равно можете заменить bswap на ...

mov ebx, eax
shr ebx, 16
xchg bl, bh
xchg al, ah
shl eax, 16
or eax, ebx

Также встроенная сборка доступна только в коде x86 в Visual Studio. Голая функция не может быть выровнена и также не доступна в сборках x64. В этом случае вам придется использовать встроенные функции компилятора.

0 голосов
/ 20 сентября 2008

Посмотрите на сдвиг битов, так как это в основном все, что вам нужно сделать, чтобы поменять местами маленькие -> большие порядковые номера. Затем, в зависимости от размера бит, вы меняете способ сдвига битов.

...