Обнаружение порядка байтов программно в программе на C++
Есть ли программный способ определить, используете ли вы архитектуру с прямым или обратным порядком байтов? Мне нужно иметь возможность писать код, который будет выполняться в системе I ntel или PPC и использовать точно такой же код (т.е. без условной компиляции).
30 ответов
Мне не нравится метод, основанный на типе punning - его часто предупреждает компилятор. Именно для этого нужны профсоюзы!
bool is_big_endian(void)
{
union {
uint32_t i;
char c[4];
} bint = {0x01020304};
return bint.c[0] == 1;
}
Этот принцип эквивалентен регистру типов, как это было предложено другими, но это более понятно - и в соответствии с C99 гарантированно будет правильным. GCC предпочитает это по сравнению с прямым указателем.
Это также намного лучше, чем исправление порядка байтов во время компиляции - для ОС, которые поддерживают мульти-архитектуру (например, двоичный файл на Mac OS X), это будет работать как для ppc/i386, так как в противном случае очень легко все испортить,
Ты можешь использовать std::endian
если у вас есть доступ к компилятору C++20, например, GCC 8+ или Clang 7+:
#include <type_traits>
if constexpr (std::endian::native == std::endian::big)
{
// Big endian system
}
else if constexpr (std::endian::native == std::endian::little)
{
// Little endian system
}
else
{
// Something else
}
Вы можете сделать это, установив int и замаскировав биты, но, вероятно, самый простой способ - это просто использовать встроенные операции преобразования сетевых байтов (так как порядок сетевых байтов всегда с прямым порядком байтов).
if ( htonl(47) == 47 ) {
// Big endian
} else {
// Little endian.
}
Немного возиться можно было бы быстрее, но этот способ прост, понятен и его практически невозможно испортить.
Пожалуйста, смотрите эту статью:
Вот некоторый код, чтобы определить, какой тип вашей машины
int num = 1; if(*(char *)&num == 1) { printf("\nLittle-Endian\n"); } else { printf("Big-Endian\n"); }
Обычно это делается во время компиляции (особенно по соображениям производительности), используя файлы заголовков, доступные из компилятора, или создавайте свои собственные. В Linux у вас есть заголовочный файл "/usr/include/endian.h"
Не используйте union
!
C++ не разрешает ввод типов через union
s!
Чтение из поля объединения, которое не было последним записанным полем, является неопределенным поведением!
Многие компиляторы поддерживают это как расширения, но язык не дает никаких гарантий.
Смотрите этот ответ для более подробной информации:
Есть только два правильных ответа, которые гарантированно будут переносимыми.
Первый ответ, если у вас есть доступ к системе, которая поддерживает C++20,
это использовать std::endian
от <type_traits>
заголовок.
(На момент написания C++ 20 еще не был выпущен, но если что-то не влияет на std::endian
Включение, это должно быть предпочтительным способом проверки порядка байтов во время компиляции начиная с C++ 20 и далее.)
C++ 20 г.в.
constexpr bool is_little_endian = (std::endian::native == std::endian::little);
До C++ 20 единственный верный ответ - сохранить целое число, а затем проверить его первый байт с помощью типа punning.
В отличие от использования union
s, это явно разрешено системой типов C++.
Также важно помнить, что для оптимальной мобильности static_cast
должен быть использован,
так как reinterpret_cast
определяется реализация.
Если программа пытается получить доступ к сохраненному значению объекта через glvalue, отличный от одного из следующих типов, поведение не определено: ... a
char
или жеunsigned char
тип.
C++11 и далее
enum class endianness
{
little = 0,
big = 1,
};
inline endianness get_system_endianness()
{
const int value { 0x01 };
const void * address = static_cast<const void *>(&value);
const unsigned char * least_significant_address = static_cast<const unsigned char *>(address);
return (*least_significant_address == 0x01) ? endianness::little : endianness::big;
}
C++11 и далее (без перечисления)
inline bool is_system_little_endian()
{
const int value { 0x01 };
const void * address = static_cast<const void *>(&value);
const unsigned char * least_significant_address = static_cast<const unsigned char *>(address);
return (*least_significant_address == 0x01);
}
C++ 98 / C++ 03
inline bool is_system_little_endian()
{
const int value = 0x01;
const void * address = static_cast<const void *>(&value);
const unsigned char * least_significant_address = static_cast<const unsigned char *>(address);
return (*least_significant_address == 0x01);
}
Я удивился, что никто не упомянул макросы, которые препроцессор определяет по умолчанию. Хотя они будут варьироваться в зависимости от вашей платформы; они намного чище, чем необходимость написания собственного чека с порядком байтов.
Например; если мы посмотрим на встроенные макросы, которые определяет GCC (на машине X86-64):
:| gcc -dM -E -x c - |grep -i endian
#define __LITTLE_ENDIAN__ 1
На машине КПП я получаю:
:| gcc -dM -E -x c - |grep -i endian
#define __BIG_ENDIAN__ 1
#define _BIG_ENDIAN 1
(The :| gcc -dM -E -x c -
магия распечатывает все встроенные макросы).
Эмм... Меня удивляет, что никто не понял, что компилятор просто оптимизирует тест и поместит фиксированный результат в качестве возвращаемого значения. Это делает все приведенные выше примеры кода практически бесполезными. Единственное, что будет возвращено - это порядок байтов во время компиляции! И да, я проверил все приведенные выше примеры. Вот пример с MSVC 9.0 (Visual Studio 2008).
Чистый код C
int32 DNA_GetEndianness(void)
{
union
{
uint8 c[4];
uint32 i;
} u;
u.i = 0x01020304;
if (0x04 == u.c[0])
return DNA_ENDIAN_LITTLE;
else if (0x01 == u.c[0])
return DNA_ENDIAN_BIG;
else
return DNA_ENDIAN_UNKNOWN;
}
разборка
PUBLIC _DNA_GetEndianness
; Function compile flags: /Ogtpy
; File c:\development\dna\source\libraries\dna\endian.c
; COMDAT _DNA_GetEndianness
_TEXT SEGMENT
_DNA_GetEndianness PROC ; COMDAT
; 11 : union
; 12 : {
; 13 : uint8 c[4];
; 14 : uint32 i;
; 15 : } u;
; 16 :
; 17 : u.i = 1;
; 18 :
; 19 : if (1 == u.c[0])
; 20 : return DNA_ENDIAN_LITTLE;
mov eax, 1
; 21 : else if (1 == u.c[3])
; 22 : return DNA_ENDIAN_BIG;
; 23 : else
; 24 : return DNA_ENDIAN_UNKNOWN;
; 25 : }
ret
_DNA_GetEndianness ENDP
END
Возможно, возможно отключить ЛЮБУЮ оптимизацию во время компиляции только для этой функции, но я не знаю. В противном случае это может быть возможно жестко закодировать в сборке, хотя это не переносимо. И даже тогда даже это может быть оптимизировано. Это заставляет меня думать, что мне нужен какой-то действительно дерьмовый ассемблер, реализовать один и тот же код для всех существующих процессоров / наборов команд, и, ну... неважно
Кроме того, кто-то здесь сказал, что порядок байтов не меняется во время выполнения. НЕПРАВИЛЬНО. Там есть би-байтовые машины. Их порядок может меняться в процессе исполнения. ТАКЖЕ, есть не только Little Endian и Big Endian, но и другие порядки байтов (что за слово).
Я ненавижу и люблю кодировать одновременно...
Объявите переменную int:
int variable = 0xFF;
Теперь используйте char* указатели на различные его части и проверяйте, что находится в этих частях.
char* startPart = reinterpret_cast<char*>( &variable );
char* endPart = reinterpret_cast<char*>( &variable ) + sizeof( int ) - 1;
В зависимости от того, какой из них указывает на байт 0xFF, теперь вы можете определить порядок байтов. Это требует sizeof( int) > sizeof( char), но это определенно верно для обсуждаемых платформ.
Для получения более подробной информации, вы можете проверить эту статью codeproject Основные понятия о порядке байтов:
Как динамически проверить тип Endian во время выполнения?
Как объясняется в FAQ по компьютерной анимации, вы можете использовать следующую функцию, чтобы увидеть, работает ли ваш код в системе Little- или Big-Endian: Свернуть
#define BIG_ENDIAN 0 #define LITTLE_ENDIAN 1
int TestByteOrder()
{
short int word = 0x0001;
char *byte = (char *) &word;
return(byte[0] ? LITTLE_ENDIAN : BIG_ENDIAN);
}
Этот код присваивает значение 0001h 16-разрядному целому числу. Затем назначается указатель на символ, указывающий на первый (наименее значимый) байт целочисленного значения. Если первый байт целого числа равен 0x01h, то система имеет формат Little-Endian (адрес 0x01h находится в самом младшем или наименее значимом адресе). Если это 0x00h, то система является Big-Endian.
Если вы не используете платформу, которая была портирована на процессоры PPC и Intel, вам придется выполнять условные компиляции, поскольку платформы PPC и Intel имеют совершенно разные аппаратные архитектуры, конвейеры, шины и т. Д. Это делает код сборки совершенно разным между два.
Что касается нахождения порядка байтов, сделайте следующее:
short temp = 0x1234;
char* tempChar = (char*)&temp;
Вы также получите tempChar равным 0x12 или 0x34, из которого вы будете знать порядок байтов.
Путь C++ заключался в использовании boost, где проверки и приведения препроцессора разделены на части внутри очень тщательно протестированных библиотек.
Библиотека Predef (boost/prefn.h) распознает четыре различных типа байтов.
Библиотеку Endian планировалось представить в соответствии со стандартом C++, и она поддерживает широкий спектр операций с данными, чувствительными к порядку байтов.
Как указано в ответах выше, Endianness будет частью C++20.
bool isBigEndian()
{
static const uint16_t m_endianCheck(0x00ff);
return ( *((uint8_t*)&m_endianCheck) == 0x0);
}
Как указано выше, используйте трюки союза.
Тем не менее, есть несколько проблем с теми, о которых говорилось выше, в особенности то, что доступ к невыровненной памяти общеизвестно медленен для большинства архитектур, и некоторые компиляторы даже не распознают такие постоянные предикаты, если только не выровнено слово.
Так как простой тест с порядком байтов скучен, здесь идет функция (шаблон), которая перевернет ввод / вывод произвольного целого числа в соответствии с вашей спецификацией, независимо от архитектуры хоста.
#include <stdint.h>
#define BIG_ENDIAN 1
#define LITTLE_ENDIAN 0
template <typename T>
T endian(T w, uint32_t endian)
{
// this gets optimized out into if (endian == host_endian) return w;
union { uint64_t quad; uint32_t islittle; } t;
t.quad = 1;
if (t.islittle ^ endian) return w;
T r = 0;
// decent compilers will unroll this (gcc)
// or even convert straight into single bswap (clang)
for (int i = 0; i < sizeof(r); i++) {
r <<= 8;
r |= w & 0xff;
w >>= 8;
}
return r;
};
Использование:
Для преобразования из данного порядкового номера в хост используйте:
host = endian(source, endian_of_source)
Чтобы преобразовать порядковый номер узла в указанный, используйте:
output = endian(hostsource, endian_you_want_to_output)
Результирующий код работает так же быстро, как и сборка рук на clang, на gcc он немного медленнее (развернутый &,<<, >>, | для каждого байта), но все еще приличный.
Я бы сделал что-то вроде этого:
bool isBigEndian() {
static unsigned long x(1);
static bool result(reinterpret_cast<unsigned char*>(&x)[0] == 0);
return result;
}
Вдоль этих строк вы получите эффективную по времени функцию, которая выполняет вычисления только один раз.
Время компиляции, не-макрос, C++11 решение constexpr:
union {
uint16_t s;
unsigned char c[2];
} constexpr static d {1};
constexpr bool is_little_endian() {
return d.c[0] == 1;
}
Не проверено, но на мой взгляд, это должно работать? потому что это будет 0x01 на младшем порядке, и 0x00 на старшем порядке?
bool runtimeIsLittleEndian(void)
{
volatile uint16_t i=1;
return ((uint8_t*)&i)[0]==0x01;//0x01=little, 0x00=big
}
union {
int i;
char c[sizeof(int)];
} x;
x.i = 1;
if(x.c[0] == 1)
printf("little-endian\n");
else printf("big-endian\n");
Это еще одно решение. Аналогично решению Эндрю Хэра.
Если вам не нужна условная компиляция, вы можете просто написать независимый код с порядком байтов. Вот пример (взят от Роба Пайка):
Чтение целого числа, хранящегося в порядке с прямым порядком байтов на диске, с прямым порядком байтов:
i = (data[0]<<0) | (data[1]<<8) | (data[2]<<16) | (data[3]<<24);
Тот же код, пытающийся учесть машинный порядок байтов:
i = *((int*)data);
#ifdef BIG_ENDIAN
/* swap the bytes */
i = ((i&0xFF)<<24) | (((i>>8)&0xFF)<<16) | (((i>>16)&0xFF)<<8) | (((i>>24)&0xFF)<<0);
#endif
Если заголовок endian не предназначен только для GCC, он предоставляет макросы, которые вы можете использовать.
#include "endian.h"
...
if (__BYTE_ORDER == __LITTLE_ENDIAN) { ... }
else if (__BYTE_ORDER == __BIG_ENDIAN) { ... }
else { throw std::runtime_error("Sorry, this version does not support PDP Endian!");
...
Вы также можете сделать это через препроцессор, используя что-то вроде файла заголовка Boost, который можно найти в Boost Endian.
Как компиляторы C (по крайней мере, все, кого я знаю) работают с порядком байтов , должны быть определены во время компиляции. Даже для процессоров Biendian (например, ARM и MIPS) вы должны выбирать порядковый номер во время компиляции. Более того, порядок байтов определяется во всех распространенных форматах файлов для исполняемых файлов (таких как ELF). Несмотря на то, что можно создать двоичный двоичный код (возможно, для эксплойта ARM-сервера?), Это, вероятно, должно быть сделано в сборке.
См. Endianness - Иллюстрация кода уровня C.
// assuming target architecture is 32-bit = 4-Bytes
enum ENDIANESS{ LITTLEENDIAN , BIGENDIAN , UNHANDLE };
ENDIANESS CheckArchEndianalityV1( void )
{
int Endian = 0x00000001; // assuming target architecture is 32-bit
// as Endian = 0x00000001 so MSB (Most Significant Byte) = 0x00 and LSB (Least Significant Byte) = 0x01
// casting down to a single byte value LSB discarding higher bytes
return (*(char *) &Endian == 0x01) ? LITTLEENDIAN : BIGENDIAN;
}
Как насчет этого?
#include <cstdio>
int main()
{
unsigned int n = 1;
char *p = 0;
p = (char*)&n;
if (*p == 1)
std::printf("Little Endian\n");
else
if (*(p + sizeof(int) - 1) == 1)
std::printf("Big Endian\n");
else
std::printf("What the crap?\n");
return 0;
}
решение С++20:
constexpr bool compare(auto const c, auto const ...a) noexcept
{
return [&]<auto ...I>(std::index_sequence<I...>) noexcept
{
return ((std::uint8_t(c >> 8 * I) == a) && ...);
}(std::make_index_sequence<sizeof...(a)>());
}
static constexpr auto is_big_endian_v{
compare(std::uint32_t(0x01234567), 0x01, 0x23, 0x45, 0x67)
};
static constexpr auto is_little_endian_v{
compare(std::uint32_t(0x01234567), 0x67, 0x45, 0x23, 0x01)
};
static constexpr auto is_pdp_endian_v{
compare(std::uint32_t(0x01234567), 0x23, 0x01, 0x67, 0x45)
};
Задача может быть выполнена более легко , но почему-то<bit>
заголовочный файл не всегда присутствует. Вот демо .
Вот еще одна версия C. Он определяет макрос с именем wicked_cast()
для строкового типа с помощью литералов C99 и нестандартных __typeof__
оператор.
#include <limits.h>
#if UCHAR_MAX == UINT_MAX
#error endianness irrelevant as sizeof(int) == 1
#endif
#define wicked_cast(TYPE, VALUE) \
(((union { __typeof__(VALUE) src; TYPE dest; }){ .src = VALUE }).dest)
_Bool is_little_endian(void)
{
return wicked_cast(unsigned char, 1u);
}
Если целые числа являются однобайтовыми значениями, порядок байтов не имеет смысла и будет сгенерирована ошибка времени компиляции.
Хотя не существует быстрого и стандартного способа определить его, он выведет его:
#include <stdio.h>
int main()
{
unsigned int i = 1;
char *c = (char*)&i;
if (*c)
printf("Little endian");
else
printf("Big endian");
getchar();
return 0;
}
Я просматривал учебник: Компьютерная система: взгляд программиста, и есть проблема, чтобы определить, какой это порядковый номер в C-программе.
Я использовал функцию указателя, чтобы сделать это следующим образом:
#include <stdio.h>
int main(void){
int i=1;
unsigned char* ii = &i;
printf("This computer is %s endian.\n", ((ii[0]==1) ? "little" : "big"));
return 0;
}
Поскольку int занимает 4 байта, а char занимает только 1 байт. Мы могли бы использовать указатель на символ, чтобы указывать на int со значением 1. Таким образом, если компьютер имеет младший порядковый номер, символ, на который указывает указатель на символ, имеет значение 1, в противном случае его значение должно быть 0.
Как указывает Coriiander, большинство (если не все) этих кодов здесь будут оптимизированы во время компиляции, поэтому сгенерированные двоичные файлы не будут проверять "порядковый номер" во время выполнения.
Было замечено, что данный исполняемый файл не должен запускаться в двух разных порядках байтов, но я понятия не имею, так ли это всегда, и мне кажется, что это хак для проверки во время компиляции. Итак, я закодировал эту функцию:
#include <stdint.h>
int* _BE = 0;
int is_big_endian() {
if (_BE == 0) {
uint16_t* teste = (uint16_t*)malloc(4);
*teste = (*teste & 0x01FE) | 0x0100;
uint8_t teste2 = ((uint8_t*) teste)[0];
free(teste);
_BE = (int*)malloc(sizeof(int));
*_BE = (0x01 == teste2);
}
return *_BE;
}
MinGW не смог оптимизировать этот код, хотя он и здесь оптимизирует другие коды. Я полагаю, что это потому, что я оставляю "случайное" значение, которое было выделено в меньшей байтовой памяти, как было (по крайней мере, 7 его битов), поэтому компилятор не может знать, что это случайное значение, и не оптимизирует функция прочь
Я также закодировал функцию так, чтобы проверка выполнялась только один раз, а возвращаемое значение сохранялось для следующих тестов.