64 бит ntohl() в С++?

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

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

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

Ответ 1

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

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

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

#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-библиотеки".

Ответ 2

Я бы рекомендовал прочитать это: 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 байта.

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

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

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

    return output;
}

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

Ответ 3

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

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

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

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

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

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

#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;
}

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

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

// 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

Ответ 4

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

Ответ 5

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

uint64_t value = 0x1122334455667788;
#if __BYTE_ORDER == __LITTLE_ENDIAN
value = __bswap_constant_64(value);  // Compiler builtin
#endif

Остальная часть этого ответа касается общего hton() для 16 бит, 32 бит, 64 бит... в современных C++.

C++ 14 constexpr шаблона constexpr

#include <endian.h>    // __BYTE_ORDER
#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 шаблона 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 template Функции 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_constant_16(value);
}

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

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

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

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 Universal

Ответ 6

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

#define bswap64(y) (((uint64_t)ntohl(y)) << 32 | ntohl(y>>32))

Ответ 7

Как насчет общей версии, которая не зависит от размера ввода (некоторые из вышеприведенных реализаций предполагают, что 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;
    }

Ответ 8

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;
}

Ответ 9

Мне нравится ответ профсоюза, довольно аккуратный. Обычно я просто перехожу к преобразованию между маленьким и большим endian, хотя я думаю, что решение union имеет меньше назначений и может быть быстрее:

//note UINT64_C_LITERAL is a macro that appends the correct prefix
//for the literal on that platform
inline void endianFlip(unsigned long long& Value)
{
   Value=
   ((Value &   UINT64_C_LITERAL(0x00000000000000FF)) << 56) |
   ((Value &   UINT64_C_LITERAL(0x000000000000FF00)) << 40) |
   ((Value &   UINT64_C_LITERAL(0x0000000000FF0000)) << 24) |
   ((Value &   UINT64_C_LITERAL(0x00000000FF000000)) << 8)  |
   ((Value &   UINT64_C_LITERAL(0x000000FF00000000)) >> 8)  | 
   ((Value &   UINT64_C_LITERAL(0x0000FF0000000000)) >> 24) |
   ((Value &   UINT64_C_LITERAL(0x00FF000000000000)) >> 40) |
   ((Value &   UINT64_C_LITERAL(0xFF00000000000000)) >> 56);
}

Затем, чтобы обнаружить, что вам даже нужно сделать флип без макромагии, вы можете сделать аналогичную вещь как Pax, где, когда короткий присваивается 0x0001, он будет 0x0100 в противоположной системе endian.

Итак:

unsigned long long numberToSystemEndian
(
    unsigned long long In, 
    unsigned short SourceEndian
)
{
   if (SourceEndian != 1)
   {
      //from an opposite endian system
      endianFlip(In);
   }
   return In;
}

Итак, чтобы использовать это, вам нужно, чтобы SourceEndian был индикатором для передачи информации о значении входного номера. Это может быть сохранено в файле (если это проблема сериализации) или передается по сети (если это проблема сериализации сети).

Ответ 10

Легким способом было бы использовать 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]);
}

Ответ 11

Как насчет:

#define ntohll(x) ( ( (uint64_t)(ntohl( (uint32_t)((x << 32) >> 32) )) << 32) | 
    ntohl( ((uint32_t)(x >> 32)) ) )                                        
#define htonll(x) ntohll(x)

Ответ 12

template <typename T>
static T ntoh_any(T t)
{
    static const unsigned char int_bytes[sizeof(int)] = {0xFF};
    static const int msb_0xFF = 0xFF << (sizeof(int) - 1) * CHAR_BIT;
    static bool host_is_big_endian = (*(reinterpret_cast<const int *>(int_bytes)) & msb_0xFF ) != 0;
    if (host_is_big_endian) { return t; }

    unsigned char * ptr = reinterpret_cast<unsigned char *>(&t);
    std::reverse(ptr, ptr + sizeof(t) );
    return t;
}

Работает в 2 байтах, 4-байтах, 8-байтах и ​​16-байтах (если у вас 128-битное целое число). Должна быть независимой от ОС/платформы.

Ответ 13

Предполагается, что вы кодируете Linux с использованием 64-битной ОС; большинство систем имеют htole(x) или ntobe(x) и т.д., они обычно являются макросами для различных bswap

#include <endian.h>
#include <byteswap.h>

unsigned long long htonll(unsigned long long val)
{
    if (__BYTE_ORDER == __BIG_ENDIAN) return (val);
    else return __bswap_64(val);
}

unsigned long long ntohll(unsigned long long val)
{
    if (__BYTE_ORDER == __BIG_ENDIAN) return (val);
    else return __bswap_64(val);
}

Боковое примечание; это просто функции вызова, чтобы поменять порядок байтов. Если вы используете небольшую endian, например, с большой конечной сетью, но если вы используете большую конечную кодировку, это будет излишне отменять порядок байтов, поэтому для проверки вашего кода может потребоваться небольшая проверка "if __BYTE_ORDER == __LITTLE_ENDIAN", по вашим потребностям.

Обновление: Отредактировано, чтобы показать пример проверки endian

Ответ 14

универсальная функция для любого значения.

template <typename T>
T swap_endian (T value)
{
    union {
        T src;
        unsigned char dst[sizeof(T)];
    } source, dest;

    source.src = value;
    for (size_t k = 0; k < sizeof(T); ++k)
        dest.dst[k] = source.dst[sizeof(T) - k - 1];

    return dest.src;
}

Ответ 15

htonl можно выполнить с помощью следующих шагов

  • Если его большая конечная система возвращает значение напрямую. Не нужно делать никаких преобразований. Если его конечная система должна сделать следующее преобразование.
  • Возьмите LSB 32 бит и примените "htonl" и сдвиньте 32 раза.
  • Возьмите 32-битный бит MSB (переместив значение uint64_t 32 раза справа) и примените 'htonl'
  • Теперь примените бит wise OR для значения, полученного на 2-м и 3-м шагах.

Аналогично для ntohll также

#define HTONLL(x) ((1==htonl(1)) ? (x) : (((uint64_t)htonl((x) & 0xFFFFFFFFUL)) << 32) | htonl((uint32_t)((x) >> 32)))
#define NTOHLL(x) ((1==ntohl(1)) ? (x) : (((uint64_t)ntohl((x) & 0xFFFFFFFFUL)) << 32) | ntohl((uint32_t)((x) >> 32)))

Вы также можете delcare выше 2 определения как функции.

Ответ 16

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

static inline void short_to_network_order(uchar *output, uint16_t in)
{
    output[0] = in>>8&0xff;
    output[1] = in&0xff;
}

(расширяется, как требуется для больших чисел).

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

Недостатком является, конечно, то, что это не тот же стандартный интерфейс, что и htonl() и друзья (который я не вижу в качестве недостатка, потому что дизайн htonl() был плохим выбором imo).