64-битный ntohl() в C++?
Страницы руководства для htonl()
Похоже, вы можете использовать его только для 32-битных значений. (В действительности, ntohl()
определяется для unsigned long, который на моей платформе составляет 32 бита. Я полагаю, если бы unsigned long был 8 байтов, он работал бы для 64-битных целых).
Моя проблема заключается в том, что мне нужно преобразовать 64-битные целые числа (в моем случае это long без знака long) из big endian в little endian. Прямо сейчас мне нужно сделать это конкретное преобразование. Но было бы еще лучше, если бы функция (как ntohl()
) НЕ будет конвертировать мое 64-битное значение, если целевая платформа была с прямым порядком байтов. (Я бы предпочел не добавлять свою магию препроцессора для этого).
Что я могу использовать? Я хотел бы что-то стандартное, если оно существует, но я открыт для предложений по реализации. Я видел этот тип преобразования, сделанный в прошлом с помощью союзов. Я полагаю, у меня может быть союз с длинным без знака и символом [8]. Затем поменяйте местами байты соответственно. (Очевидно, что сломался бы на платформах, которые были с прямым порядком байтов).
16 ответов
Документация: man htobe64
в Linux (glibc >= 2.9) или FreeBSD.
К сожалению, во время попытки в 2009 году OpenBSD, FreeBSD и glibc (Linux) не совсем слаженно работали вместе, чтобы создать один (не-API-API) стандарт libc для этого.
В настоящее время этот короткий код препроцессора:
#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-библиотечный подход, доступный на данный момент.
Я рекомендовал бы прочитать это: 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-битный сейф тоже!
Быстрый ответ
#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 бит.
Но мы все еще можем сохранить общие функции.
(смотрите полный фрагмент на колиру)
В приведенном ниже фрагменте кода 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 Универсальный
Чтобы определить ваш порядковый номер, используйте следующий союз:
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
Некоторые системы BSD имеют betoh64
который делает то, что вам нужно.
Однострочный макрос для 64-битного свопа на машинах с прямым порядком байтов.
#define bswap64(y) (((uint64_t)ntohl(y)) << 32 | ntohl(y>>32))
Как насчет общей версии, которая не зависит от размера ввода (некоторые из реализаций выше предполагают, что 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;
}
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;
}
Как насчет:
#define ntohll(x) ( ( (uint64_t)(ntohl( (uint32_t)((x << 32) >> 32) )) << 32) |
ntohl( ((uint32_t)(x >> 32)) ) )
#define htonll(x) ntohll(x)
Мне нравится ответ профсоюза, довольно аккуратно. Обычно я просто сдвигаю бит для преобразования между прямым и младшим порядком байтов, хотя я думаю, что объединенное решение имеет меньше назначений и может быть быстрее:
//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 в системе с обратным порядком байтов.
Так:
unsigned long long numberToSystemEndian
(
unsigned long long In,
unsigned short SourceEndian
)
{
if (SourceEndian != 1)
{
//from an opposite endian system
endianFlip(In);
}
return In;
}
Поэтому, чтобы использовать это, вам нужно, чтобы SourceEndian был индикатором, указывающим порядковый номер входного числа. Это может быть сохранено в файле (если это проблема сериализации) или передано по сети (если это проблема сериализации сети).
Самый простой способ - использовать 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]);
}
htonl
может быть сделано по шагам ниже
- Если система с прямым порядком байтов возвращает значение напрямую. Нет необходимости делать какие-либо преобразования. Если это система с прямым порядком байтов, нужно выполнить приведенное ниже преобразование.
- Возьмите LSB 32 бита и примените "htonl" и сдвиньте 32 раза.
- Возьмите MSB 32 бита (сдвигая значение uint64_t 32 раза вправо) и примените 'htonl'
- Теперь примените побитовое ИЛИ для значения, полученного на 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 определения как функции также.
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-битное целое число). Должен быть независимым от ОС / платформы.
Универсальная функция для любого размера значения.
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;
}
union help64
{
unsigned char byte[8];
uint64_t quad;
};
uint64_t ntoh64(uint64_t src)
{
help64 tmp;
tmp.quad = src;
uint64_t dst = 0;
for(int i = 0; i < 8; ++i)
dst = (dst << 8) + tmp.byte[i];
return dst;
}
Это предполагает, что вы кодируете в Linux, используя 64-битную ОС; большинство систем имеют htole(x)
или же ntobe(x)
и т. д., как правило, это макросы для различных bswap
"s
#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);
}
Примечание; это просто функции, которые нужно вызвать для смены порядка байтов. Если вы используете метод с прямым порядком байтов, например, с сетью с прямым порядком байтов, но если вы используете кодирование с большим окончанием, то это излишне немного изменит порядок байтов "if __BYTE_ORDER == __LITTLE_ENDIAN
"проверка может потребоваться, чтобы сделать ваш код более переносимым, в зависимости от ваших потребностей.
Обновление: отредактировано, чтобы показать пример проверки порядка байтов
Как правило, нет необходимости знать порядок машин, чтобы преобразовать целое число хоста в сетевой порядок. К сожалению, это справедливо только в том случае, если вы записываете свое значение net-order в байтах, а не как другое целое число:
static inline void short_to_network_order(uchar *output, uint16_t in)
{
output[0] = in>>8&0xff;
output[1] = in&0xff;
}
(расширить, как требуется для больших чисел).
Это будет (а) работать на любой архитектуре, потому что ни в коем случае я не использую специальные знания о том, как целое число выкладывается в память, и (б) в основном следует оптимизировать в архитектурах с прямым порядком байтов, потому что современные компиляторы не глупы.
Недостаток, конечно, в том, что это не тот же стандартный интерфейс, что и у htonl() и друзей (что я не вижу в качестве недостатка, потому что дизайн htonl() был плохим выбором imo).