ビッグ エンディアンまたはリトルエンディアン アーキテクチャを使用しているかどうかをプログラムで検出する方法はありますか? Intel または PPC システムで実行され、まったく同じコードを使用する (つまり、条件付きコンパイルがない) コードを記述できる必要があります。
29 に答える
型のパンニングに基づく方法は好きではありません。コンパイラから警告を受けることがよくあります。それこそが組合の目的です!
bool is_big_endian(void)
{
union {
uint32_t i;
char c[4];
} bint = {0x01020304};
return bint.c[0] == 1;
}
原則は他の人が提案している活字ケースと同等ですが、これはより明確です-そしてC99によれば、正しいことが保証されています。gccは、直接ポインターキャストと比較してこれを優先します。
これは、コンパイル時にエンディアンを修正するよりもはるかに優れています-マルチアーキテクチャをサポートするOS(たとえば、Mac os xのファットバイナリ)の場合、これはppc / i386の両方で機能しますが、それ以外の場合は非常に簡単に混乱します。
std::endian
GCC 8+ や Clang 7+ などの C++20 コンパイラにアクセスできる場合に使用できます。
注:std::endian
で開始されましたが<type_traits>
、2019 年のケルン会議で変更されました。GCC 8、Clang 7、8、および 9 には含まれていますが、GCC 9+ および Clang 10+ には含まれています。<bit>
<type_traits>
<bit>
#include <bit>
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 を設定してビットをマスクすることでそれを行うことができますが、おそらく最も簡単な方法は、組み込みのネットワーク バイト変換 ops を使用することです (ネットワーク バイト オーダーは常にビッグ エンディアンであるため)。
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」があります。
プリプロセッサがデフォルトで定義するマクロについて誰も言及していないことに驚きました。これらはプラットフォームによって異なりますが、独自のエンディアン チェックを作成するよりもはるかにクリーンです。
例えば; GCC が (X86-64 マシン上で) 定義する組み込みマクロを見ると:
:| gcc -dM -E -x c - |grep -i endian
#define __LITTLE_ENDIAN__ 1
PPC マシンでは次のようになります。
:| gcc -dM -E -x c - |grep -i endian
#define __BIG_ENDIAN__ 1
#define _BIG_ENDIAN 1
(:| gcc -dM -E -x c -
マジックはすべての組み込みマクロを出力します)。
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 ) が必要ですが、議論されているプラットフォームには間違いなく当てはまります。
うーん... コンパイラが単純にテストを最適化し、固定結果を戻り値として出力することに誰も気付いていないことに驚きました。これにより、上記のすべてのコード例が事実上役に立たなくなります。返される唯一のものは、コンパイル時のエンディアンです! はい、上記の例をすべてテストしました。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
おそらく、この関数だけのコンパイル時の最適化をオフにすることは可能ですが、私にはわかりません。それ以外の場合は、移植性はありませんが、アセンブリでハードコードすることは可能かもしれません。それでも、それが最適化される可能性があります。本当にくだらないアセンブラが必要で、既存のすべてのCPU/命令セットに同じコードを実装する必要があると思います....気にしないでください。
また、エンディアンは実行時に変化しないと誰かが言いました。違う。そこにはバイエンディアンのマシンがあります。それらのエンディアンは、実行中に変化する可能性があります。また、リトル エンディアンやビッグ エンディアンだけでなく、他のエンディアンも存在します (なんていうか)。
私はコーディングが嫌いであり、同時に好きです...
C++ の方法はboostを使用してきました。この場合、プリプロセッサのチェックとキャストは、非常に徹底的にテストされたライブラリ内で区画化されています。
Predef ライブラリ (boost/predef.h) は、4 種類のエンディアンを認識します。
エンディアン ライブラリは、C++ 標準に提出される予定であり、エンディアンに依存するデータに対するさまざまな操作をサポートしています。
上記の回答で述べたように、エンディアンは c++20 の一部になります。
詳細については、この codeproject の記事「エンディアンに関する基本概念」を参照してください。
実行時にエンディアン タイプを動的にテストする方法は?
Computer Animation FAQ で説明されているように、次の関数を使用して、コードがリトルエンディアン システムまたはビッグ エンディアン システムで実行されているかどうかを確認できます。
#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 ビット整数に割り当てます。次に、整数値の最初の (最下位) バイトを指すように char ポインターが割り当てられます。整数の最初のバイトが 0x01h の場合、システムはリトル エンディアンです (0x01h は最下位または最下位アドレスにあります)。0x00h の場合、システムはビッグ エンディアンです。
bool isBigEndian()
{
static const uint16_t m_endianCheck(0x00ff);
return ( *((const 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 では少し遅くなります (すべてのバイトで &,<<,>>,| がアンロールされます) が、それでもまともです。
PPC と Intel のプロセッサに移植されたフレームワークを使用していない限り、PPC と Intel のプラットフォームではハードウェア アーキテクチャ、パイプライン、バスなどがまったく異なるため、条件付きコンパイルを行う必要があります。二つ。
エンディアンを見つけるには、次のようにします。
short temp = 0x1234;
char* tempChar = (char*)&temp;
tempChar を 0x12 または 0x34 にすることで、エンディアンを知ることができます。
テストされていませんが、私の考えでは、これはうまくいくはずですか?リトル エンディアンでは 0x01、ビッグ エンディアンでは 0x00 になるのでしょうか。
bool runtimeIsLittleEndian(void)
{
volatile uint16_t i=1;
return ((uint8_t*)&i)[0]==0x01;//0x01=little, 0x00=big
}
私はこのようなことをします:
bool isBigEndian() {
static unsigned long x(1);
static bool result(reinterpret_cast<unsigned char*>(&x)[0] == 0);
return result;
}
これらの線に沿って、計算を 1 回だけ行う時間効率の良い関数が得られます。
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");
これは別の解決策です。Andrew Hareのソリューションに似ています。
宣言する:
私の最初の投稿は、「コンパイル時」と誤って宣言されています。そうではなく、現在の C++ 標準では不可能ですらあります。constexpr は、関数が常にコンパイル時の計算を行うという意味ではありません。修正してくれた Richard Hodges に感謝します。
コンパイル時、非マクロ、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;
}
ブースト エンディアンにあるブースト ヘッダー ファイルのようなものを使用して、プリプロセッサ経由でこれを行うこともできます。
エンディアン- C レベル コードの図を参照してください。
// assuming target architecture is 32-bit = 4-Bytes
enum ENDIANNESS{ LITTLEENDIAN , BIGENDIAN , UNHANDLE };
ENDIANNESS 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;
}
int i=1;
char *c=(char*)&i;
bool littleendian=c;
Cコンパイラ(少なくとも私が知っているすべての人)がエンディアンを動作させる方法は、コンパイル時に決定する必要があります。ビエンディアン プロセッサ (ARM や MIPS など) の場合でも、コンパイル時にエンディアンを選択する必要があります。さらに、エンディアンは、実行可能ファイル (ELF など) のすべての一般的なファイル形式で定義されています。ビアンディアン コードのバイナリ blob を作成することは可能ですが (おそらく ARM サーバーの悪用のため?)、おそらくアセンブリで行う必要があります。
これが別の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 <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;
}
それを決定するための迅速かつ標準的な方法はありませんが、これはそれを出力します:
#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;
}
Coriiander が指摘したように、これらのコードのほとんど (すべてではないにしても) はコンパイル時に最適化されるため、生成されたバイナリは実行時に「エンディアン」をチェックしません。
特定の実行可能ファイルが 2 つの異なるバイト順で実行されるべきではないことが観察されていますが、常にそうであるかどうかはわかりません。また、コンパイル時にチェックするのはハックのように思えます。だから私はこの関数をコーディングしました:
#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 ビット) に割り当てられた "ランダム" 値をそのままにしておくためだと思います。そのため、コンパイラはそのランダム値が何であるかを知ることができず、最適化されません。離れた機能。
また、チェックが 1 回だけ実行され、戻り値が次のテストのために保存されるように関数をコーディングしました。
私は教科書を読んでいました:コンピュータシステム: プログラマーの視点, そして、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 バイトしか使用しないためです。char ポインターを使用して、値 1 のintを指すことができます。したがって、コンピューターがリトル エンディアンの場合、charポインターが指す char の値は 1 であり、そうでない場合、その値は 0 になります。