64-битный ntohl () в C ++? - PullRequest
       45

64-битный ntohl () в C ++?

58 голосов
/ 01 мая 2009

Справочные страницы для htonl() предполагают, что вы можете использовать его только для 32-битных значений. (На самом деле, ntohl() определено для длинного без знака, который на моей платформе составляет 32 бита. Я полагаю, если бы длинный без знака был 8 байтов, он работал бы для 64-битных целых).

Моя проблема заключается в том, что мне нужно преобразовать 64-битные целые числа (в моем случае это long без знака long) из старшего байта в младший. Прямо сейчас мне нужно сделать это конкретное преобразование. Но было бы еще лучше, если бы функция (например, ntohl()) НЕ преобразовывала мое 64-битное значение, если целевая платформа была бы с прямым порядком байтов. (Я бы предпочел не добавлять свою магию препроцессора для этого).

Что я могу использовать? Я хотел бы что-то стандартное, если оно существует, но я открыт для предложений по реализации. Я видел этот тип преобразования, сделанный в прошлом с помощью союзов. Я полагаю, у меня может быть союз с длинным без знака и символом [8]. Затем поменяйте местами байты соответственно. (Очевидно, что будет работать на платформах с большим порядком байтов).

Ответы [ 16 ]

53 голосов
/ 10 декабря 2010

Документация: man htobe64 в Linux (glibc> = 2.9) или FreeBSD.

К сожалению, во время попытки в 2009 году OpenBSD, FreeBSD и glibc (Linux) не совсем слаженно работали вместе, чтобы создать для этого один стандарт libc (без ядра API),

В настоящее время этот короткий код препроцессора:

#if defined(__linux__)
#  include <endian.h>
#elif defined(__FreeBSD__) || defined(__NetBSD__)
#  include <sys/endian.h>
#elif defined(__OpenBSD__)
#  include <sys/types.h>
#  define be16toh(x) betoh16(x)
#  define be32toh(x) betoh32(x)
#  define be64toh(x) betoh64(x)
#endif

(протестировано на Linux и OpenBSD) должно скрывать различия. Он предоставляет вам макросы в стиле Linux / FreeBSD на этих 4 платформах.

Пример использования:

  #include <stdint.h>    // For 'uint64_t'

  uint64_t  host_int = 123;
  uint64_t  big_endian;

  big_endian = htobe64( host_int );
  host_int = be64toh( big_endian );

Это самый «стандартный» подход к библиотеке C, доступный на данный момент.

17 голосов
/ 13 ноября 2012

Я бы порекомендовал прочитать это: http://commandcenter.blogspot.com/2012/04/byte-order-fallacy.html

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

uint64_t
ntoh64(const uint64_t *input)
{
    uint64_t rval;
    uint8_t *data = (uint8_t *)&rval;

    data[0] = *input >> 56;
    data[1] = *input >> 48;
    data[2] = *input >> 40;
    data[3] = *input >> 32;
    data[4] = *input >> 24;
    data[5] = *input >> 16;
    data[6] = *input >> 8;
    data[7] = *input >> 0;

    return rval;
}

uint64_t
hton64(const uint64_t *input)
{
    return (ntoh64(input));
}

int
main(void)
{
    uint64_t ull;

    ull = 1;
    printf("%"PRIu64"\n", ull);

    ull = ntoh64(&ull);
    printf("%"PRIu64"\n", ull);

    ull = hton64(&ull);
    printf("%"PRIu64"\n", ull);

    return 0;
}

Покажет следующий вывод:

1
72057594037927936
1

Вы можете проверить это с помощью ntohl (), если отбросите старшие 4 байта.

Также Вы можете превратить это в красивую шаблонную функцию в C ++, которая будет работать с целым числом любого размера:

template <typename T>
static inline T
hton_any(const T &input)
{
    T output(0);
    const std::size_t size = sizeof(input);
    uint8_t *data = reinterpret_cast<uint8_t *>(&output);

    for (std::size_t i = 0; i < size; i++) {
        data[i] = input >> ((size - i - 1) * 8);
    }

    return output;
}

Теперь ваш 128-битный сейф тоже!

14 голосов
/ 01 мая 2009

Чтобы определить ваш порядковый номер, используйте следующий союз:

union {
    unsigned long long ull;
    char c[8];
} x;
x.ull = 0x0123456789abcdef; // may need special suffix for ULL.

Затем вы можете проверить содержимое x.c[], чтобы определить, куда ушел каждый байт.

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

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

Вот пример кода, который я написал для иллюстрации. Он был проверен, хотя и не полностью, но этого должно быть достаточно, чтобы начать работу.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define TYP_INIT 0
#define TYP_SMLE 1
#define TYP_BIGE 2

static unsigned long long cvt(unsigned long long src) {
    static int typ = TYP_INIT;
    unsigned char c;
    union {
        unsigned long long ull;
        unsigned char c[8];
    } x;

    if (typ == TYP_INIT) {
        x.ull = 0x01;
        typ = (x.c[7] == 0x01) ? TYP_BIGE : TYP_SMLE;
    }

    if (typ == TYP_SMLE)
        return src;

    x.ull = src;
    c = x.c[0]; x.c[0] = x.c[7]; x.c[7] = c;
    c = x.c[1]; x.c[1] = x.c[6]; x.c[6] = c;
    c = x.c[2]; x.c[2] = x.c[5]; x.c[5] = c;
    c = x.c[3]; x.c[3] = x.c[4]; x.c[4] = c;
    return x.ull;
}

int main (void) {
    unsigned long long ull = 1;
    ull = cvt (ull);
    printf ("%llu\n",ull);
    return 0;
}

Имейте в виду, что это просто проверка на чистый большой / маленький порядок байтов. Если у вас есть какой-то странный вариант, в котором байты хранятся, например, в порядке {5,2,3,1,0,7,6,4}, cvt() будет немного сложнее. Такая архитектура не заслуживает существования, но я не исключаю безумия наших друзей в индустрии микропроцессоров: -)

Также имейте в виду, что это технически неопределенное поведение, поскольку вы не должны получать доступ к члену объединения с помощью любого поля, кроме последнего написанного. Вероятно, он будет работать с большинством реализаций, но, с точки зрения пуристов, вам, вероятно, следует просто откусить пулю и использовать макросы для определения своих собственных подпрограмм, например:

// Assumes 64-bit unsigned long long.
unsigned long long switchOrderFn (unsigned long long in) {
    in  = (in && 0xff00000000000000ULL) >> 56
        | (in && 0x00ff000000000000ULL) >> 40
        | (in && 0x0000ff0000000000ULL) >> 24
        | (in && 0x000000ff00000000ULL) >> 8
        | (in && 0x00000000ff000000ULL) << 8
        | (in && 0x0000000000ff0000ULL) << 24
        | (in && 0x000000000000ff00ULL) << 40
        | (in && 0x00000000000000ffULL) << 56;
    return in;
}
#ifdef ULONG_IS_NET_ORDER
    #define switchOrder(n) (n)
#else
    #define switchOrder(n) switchOrderFn(n)
#endif
12 голосов
/ 01 мая 2009

в некоторых системах BSD betoh64 делает то, что вам нужно.

11 голосов
/ 06 февраля 2015

Быстрый ответ

#include <endian.h>    // __BYTE_ORDER __LITTLE_ENDIAN
#include <byteswap.h>  // bswap_64()

uint64_t value = 0x1122334455667788;

#if __BYTE_ORDER == __LITTLE_ENDIAN
value = bswap_64(value);  // Compiler builtin GCC/Clang
#endif

Заголовочный файл

Как сообщает zhaorufei (см. Ее / его комментарий) endian.h не является стандартным заголовком C ++, и макросы __BYTE_ORDER и __LITTLE_ENDIAN могут быть неопределенными. Поэтому оператор #if не является предсказуемым, поскольку неопределенный макрос рассматривается как 0.

Пожалуйста, отредактируйте этот ответ, если вы хотите поделиться своим элегантным трюком в C ++ для обнаружения порядка байтов.

Портативность

Более того, макрос bswap_64() доступен для компиляторов GCC и Clang, но не для компилятора Visual C ++. Чтобы предоставить переносимый исходный код, вас может вдохновить следующий фрагмент:

#ifdef _MSC_VER
  #include <stdlib.h>
  #define bswap_16(x) _byteswap_ushort(x)
  #define bswap_32(x) _byteswap_ulong(x)
  #define bswap_64(x) _byteswap_uint64(x)
#else
  #include <byteswap.h>  // bswap_16 bswap_32 bswap_64
#endif

См. Также более переносимый исходный код: Кроссплатформенный _byteswap_uint64

C ++ 14 constexpr функция шаблона

Универсальный hton() для 16 бит, 32 бит, 64 бит и более ...

#include <endian.h>   // __BYTE_ORDER __LITTLE_ENDIAN
#include <algorithm>  // std::reverse()

template <typename T>
constexpr T htonT (T value) noexcept
{
#if __BYTE_ORDER == __LITTLE_ENDIAN
  char* ptr = reinterpret_cast<char*>(&value);
  std::reverse(ptr, ptr + sizeof(T));
#endif
  return value;
}

C ++ 11 constexpr функция шаблона

  • C ++ 11 не разрешает использование локальной переменной в функции constexpr.
    Поэтому хитрость заключается в использовании аргумента со значением по умолчанию.
  • Кроме того, функция C ++ 11 constexpr должна содержать одно-единственное выражение.
    Поэтому тело состоит из одного возврата с несколькими разделенными запятыми утверждениями .
template <typename T>
constexpr T htonT (T value, char* ptr=0) noexcept
{
  return 
#if __BYTE_ORDER == __LITTLE_ENDIAN
    ptr = reinterpret_cast<char*>(&value), 
    std::reverse(ptr, ptr + sizeof(T)),
#endif
    value;
}

Нет предупреждений о компиляции на clang-3.5 и GCC-4.9 с использованием -Wall -Wextra -pedantic
(см. компиляцию и запуск вывода на coliru ).

C ++ 11 constexpr шаблон SFINAE функций

Однако вышеприведенная версия не позволяет создавать переменную constexpr как:

constexpr int32_t hton_six = htonT( int32_t(6) );

Наконец, нам нужно разделить (специализировать) функции в зависимости от 16/32/64 бит.
Но мы все еще можем сохранять общие функции.
(полный фрагмент см. на coliru )

В приведенном ниже фрагменте кода C ++ 11 используются черты std::enable_if для использования Ошибка замены не является ошибкой (SFINAE).

template <typename T>
constexpr typename std::enable_if<sizeof(T) == 2, T>::type
htonT (T value) noexcept
{
   return  ((value & 0x00FF) << 8)
         | ((value & 0xFF00) >> 8);
}

template <typename T>
constexpr typename std::enable_if<sizeof(T) == 4, T>::type
htonT (T value) noexcept
{
   return  ((value & 0x000000FF) << 24)
         | ((value & 0x0000FF00) <<  8)
         | ((value & 0x00FF0000) >>  8)
         | ((value & 0xFF000000) >> 24);
}

template <typename T>
constexpr typename std::enable_if<sizeof(T) == 8, T>::type
htonT (T value) noexcept
{
   return  ((value & 0xFF00000000000000ull) >> 56)
         | ((value & 0x00FF000000000000ull) >> 40)
         | ((value & 0x0000FF0000000000ull) >> 24)
         | ((value & 0x000000FF00000000ull) >>  8)
         | ((value & 0x00000000FF000000ull) <<  8)
         | ((value & 0x0000000000FF0000ull) << 24)
         | ((value & 0x000000000000FF00ull) << 40)
         | ((value & 0x00000000000000FFull) << 56);
}

Или еще более короткая версия на основе встроенных макросов компилятора и синтаксиса C ++ 14 std::enable_if_t<xxx> в качестве ярлыка для std::enable_if<xxx>::type:

template <typename T>
constexpr typename std::enable_if_t<sizeof(T) == 2, T>
htonT (T value) noexcept
{
    return bswap_16(value);  // __bswap_constant_16
}

template <typename T>
constexpr typename std::enable_if_t<sizeof(T) == 4, T>
htonT (T value) noexcept
{
    return bswap_32(value);  // __bswap_constant_32
}

template <typename T>
constexpr typename std::enable_if_t<sizeof(T) == 8, T>
htonT (T value) noexcept
{
    return bswap_64(value);  // __bswap_constant_64
}

Тестовый код первой версии

std::uint8_t uc = 'B';                  std::cout <<std::setw(16)<< uc <<'\n';
uc = htonT( uc );                       std::cout <<std::setw(16)<< uc <<'\n';

std::uint16_t us = 0x1122;              std::cout <<std::setw(16)<< us <<'\n';
us = htonT( us );                       std::cout <<std::setw(16)<< us <<'\n';

std::uint32_t ul = 0x11223344;          std::cout <<std::setw(16)<< ul <<'\n';
ul = htonT( ul );                       std::cout <<std::setw(16)<< ul <<'\n';

std::uint64_t uL = 0x1122334455667788; std::cout <<std::setw(16)<< uL <<'\n';
uL = htonT( uL );                      std::cout <<std::setw(16)<< uL <<'\n';

Тестовый код второй версии

constexpr uint8_t  a1 = 'B';               std::cout<<std::setw(16)<<a1<<'\n';
constexpr auto     b1 = htonT(a1);         std::cout<<std::setw(16)<<b1<<'\n';

constexpr uint16_t a2 = 0x1122;            std::cout<<std::setw(16)<<a2<<'\n';
constexpr auto     b2 = htonT(a2);         std::cout<<std::setw(16)<<b2<<'\n';

constexpr uint32_t a4 = 0x11223344;        std::cout<<std::setw(16)<<a4<<'\n';
constexpr auto     b4 = htonT(a4);         std::cout<<std::setw(16)<<b4<<'\n';

constexpr uint64_t a8 = 0x1122334455667788;std::cout<<std::setw(16)<<a8<<'\n';
constexpr auto     b8 = htonT(a8);         std::cout<<std::setw(16)<<b8<<'\n';

выход

               B
               B
            1122
            2211
        11223344
        44332211
1122334455667788
8877665544332211

Генерация кода

Онлайн-компилятор C ++ gcc.godbolt.org указывает на сгенерированный код.

g++-4.9.2 -std=c++14 -O3

std::enable_if<(sizeof (unsigned char))==(1), unsigned char>::type htonT<unsigned char>(unsigned char):
    movl    %edi, %eax
    ret
std::enable_if<(sizeof (unsigned short))==(2), unsigned short>::type htonT<unsigned short>(unsigned short):
    movl    %edi, %eax
    rolw    $8, %ax
    ret
std::enable_if<(sizeof (unsigned int))==(4), unsigned int>::type htonT<unsigned int>(unsigned int):
    movl    %edi, %eax
    bswap   %eax
    ret
std::enable_if<(sizeof (unsigned long))==(8), unsigned long>::type htonT<unsigned long>(unsigned long):
    movq    %rdi, %rax
    bswap   %rax
    ret

clang++-3.5.1 -std=c++14 -O3

std::enable_if<(sizeof (unsigned char))==(1), unsigned char>::type htonT<unsigned char>(unsigned char): # @std::enable_if<(sizeof (unsigned char))==(1), unsigned char>::type htonT<unsigned char>(unsigned char)
    movl    %edi, %eax
    retq

std::enable_if<(sizeof (unsigned short))==(2), unsigned short>::type htonT<unsigned short>(unsigned short): # @std::enable_if<(sizeof (unsigned short))==(2), unsigned short>::type htonT<unsigned short>(unsigned short)
    rolw    $8, %di
    movzwl  %di, %eax
    retq

std::enable_if<(sizeof (unsigned int))==(4), unsigned int>::type htonT<unsigned int>(unsigned int): # @std::enable_if<(sizeof (unsigned int))==(4), unsigned int>::type htonT<unsigned int>(unsigned int)
    bswapl  %edi
    movl    %edi, %eax
    retq

std::enable_if<(sizeof (unsigned long))==(8), unsigned long>::type htonT<unsigned long>(unsigned long): # @std::enable_if<(sizeof (unsigned long))==(8), unsigned long>::type htonT<unsigned long>(unsigned long)
    bswapq  %rdi
    movq    %rdi, %rax
    retq

Примечание: мой оригинальный ответ не был совместим с C ++ 11- constexpr.

Этот ответ находится в Public Domain CC0 1.0 Универсальный

6 голосов
/ 25 апреля 2012

однострочный макрос для 64-битного свопа на машинах с прямым порядком байтов.

#define bswap64(y) (((uint64_t)ntohl(y)) << 32 | ntohl(y>>32))
5 голосов
/ 17 января 2012
uint32_t SwapShort(uint16_t a)
{
  a = ((a & 0x00FF) << 8) | ((a & 0xFF00) >> 8);
  return a;
}

uint32_t SwapWord(uint32_t a)
{
  a = ((a & 0x000000FF) << 24) |
      ((a & 0x0000FF00) <<  8) |
      ((a & 0x00FF0000) >>  8) |
      ((a & 0xFF000000) >> 24);
  return a;
}

uint64_t SwapDWord(uint64_t a)
{
  a = ((a & 0x00000000000000FFULL) << 56) | 
      ((a & 0x000000000000FF00ULL) << 40) | 
      ((a & 0x0000000000FF0000ULL) << 24) | 
      ((a & 0x00000000FF000000ULL) <<  8) | 
      ((a & 0x000000FF00000000ULL) >>  8) | 
      ((a & 0x0000FF0000000000ULL) >> 24) | 
      ((a & 0x00FF000000000000ULL) >> 40) | 
      ((a & 0xFF00000000000000ULL) >> 56);
  return a;
}
5 голосов
/ 10 января 2012

Как насчет универсальной версии, которая не зависит от размера ввода (некоторые из приведенных выше реализаций предполагают, что unsigned long long равен 64 битам, что не всегда верно):

    // converts an arbitrary large integer (preferrably >=64 bits) from big endian to host machine endian
    template<typename T> static inline T bigen2host(const T& x)
    {
        static const int one = 1;
        static const char sig = *(char*)&one; 

        if (sig == 0) return x; // for big endian machine just return the input

        T ret;
        int size = sizeof(T);
        char* src = (char*)&x + sizeof(T) - 1;
        char* dst = (char*)&ret;

        while (size-- > 0) *dst++ = *src--;

        return ret;
    }
3 голосов
/ 05 июня 2009

Как насчет:

#define ntohll(x) ( ( (uint64_t)(ntohl( (uint32_t)((x << 32) >> 32) )) << 32) | 
    ntohl( ((uint32_t)(x >> 32)) ) )                                        
#define htonll(x) ntohll(x)
2 голосов
/ 18 мая 2009

Самый простой способ - использовать ntohl для двух частей по отдельности:

unsigned long long htonll(unsigned long long v) {
    union { unsigned long lv[2]; unsigned long long llv; } u;
    u.lv[0] = htonl(v >> 32);
    u.lv[1] = htonl(v & 0xFFFFFFFFULL);
    return u.llv;
}

unsigned long long ntohll(unsigned long long v) {
    union { unsigned long lv[2]; unsigned long long llv; } u;
    u.llv = v;
    return ((unsigned long long)ntohl(u.lv[0]) << 32) | (unsigned long long)ntohl(u.lv[1]);
}
...