ビッグエンディアンまたはリトルエンディアンのアーキテクチャを使用しているかどうかをプログラムで検出する方法はありますか?IntelまたはPPCシステムで実行され、まったく同じコードを使用できる(つまり、条件付きコンパイルがない)コードを記述できる必要があります。
ビッグエンディアンまたはリトルエンディアンのアーキテクチャを使用しているかどうかをプログラムで検出する方法はありますか?IntelまたはPPCシステムで実行され、まったく同じコードを使用できる(つまり、条件付きコンパイルがない)コードを記述できる必要があります。
回答:
型のパンニングに基づく方法は好きではありません-多くの場合、コンパイラによって警告されます。それがまさに組合の目的です!
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の両方で機能しますが、それ以外の場合は非常に簡単です。
CHAR_BIT != 8
?
これは、intを設定してビットをマスクすることで実行できますが、おそらく最も簡単な方法は、組み込みのネットワークバイト変換opsを使用することです(ネットワークバイトオーダーは常にビッグエンディアンであるため)。
if ( htonl(47) == 47 ) {
// Big endian
} else {
// Little endian.
}
ビットをいじるのは速いかもしれませんが、この方法は単純で簡単で、失敗することはほとんど不可能です。
BSWAP
操作をサポートするマイクロアーキテクチャーを対象とする場合、インラインアセンブラを使用してhtonlを非常に効率的に実装できます(Linux / gccでも実装できます)。
この記事をご覧ください:
ここにあなたのマシンのタイプを決定するためのコードがあります
int num = 1; if(*(char *)&num == 1) { printf("\nLittle-Endian\n"); } else { printf("Big-Endian\n"); }
std::endian
GCC 8+やClang 7+などのC ++ 20コンパイラにアクセスできる場合に使用できます。
注:2019年にstd::endian
始まりまし<type_traits>
たが<bit>
、2019年のケルン会議に移されました。GCC 8、Clang <type_traits>
7、8、9はGCC 9+とClang 10+に搭載されてい<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
}
これは通常、コンパイル時に(特にパフォーマンス上の理由により)コンパイラーから使用可能なヘッダーファイルを使用するか、独自のヘッダーファイルを作成することによって行われます。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 -
魔法はすべての組み込みマクロを出力します)。
echo "\n" | gcc -x c -E -dM - |& grep -i 'endian'
ても何も返されませんが、/usr/sfw/bin
Solarisのgcc 3.4.3(とにかく)はこれらの行に沿った定義を持っています。VxWorks Tornado(gcc 2.95)-vs- VxWorks Workbench(gcc 3.4.4)でも同様の問題が発生しました。
Ehm ...コンパイラがテストを最適化し、固定の結果を戻り値として出力することに誰も気付いていないことに驚いています。これにより、上記のすべてのコード例が事実上役に立たなくなります。返されるのは、コンパイル時のエンディアンだけです。そして、はい、上記の例をすべてテストしました。以下は、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 /命令セットに同じコードを実装します。
また、ここの誰かは、実行時にエンディアンは変化しないと述べました。違う。そこにはバイエンディアンのマシンがあります。それらのエンディアンは、実行中に変化する可能性があります。また、リトルエンディアンとビッグエンディアンだけでなく、他のエンディアン(単語)もあります。
私は嫌いで、同時にコーディングが大好きです...
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の記事「エンディアンネスの基本概念」をご覧ください。
実行時にエンディアン型を動的にテストする方法は?
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の場合、システムはビッグエンディアンです。
C ++の方法はboostを使用することでした。プリプロセッサのチェックとキャストは、非常に徹底的にテストされたライブラリ内で区分化されています。
Predef Library(boost / predef.h)は4種類のエンディアンを認識します。
エンディアンライブラリは、 C ++標準に提出される予定、とエンディアン機密データに対する操作のさまざまなサポートしていました。
上記の回答で述べたように、エンディアンネスはc ++ 20の一部になります。
PPCとIntelのプラットフォームに移植されたフレームワークを使用している場合を除き、PPCとIntelのプラットフォームはハードウェアアーキテクチャ、パイプライン、バスなどがまったく異なるため、条件付きコンパイルを行う必要があります。これにより、アセンブリコードが完全に異なります二つ。
エンディアンを見つけるには、次のようにします。
short temp = 0x1234;
char* tempChar = (char*)&temp;
tempCharが0x12または0x34のいずれかになると、エンディアンがわかります。
stdint.h
、int16_t
ショートが別のプラットフォームで異なることに対する将来の証明に使用します。
私はこのようなことをします:
bool isBigEndian() {
static unsigned long x(1);
static bool result(reinterpret_cast<unsigned char*>(&x)[0] == 0);
return result;
}
これらの線に沿って、一度だけ計算を行う時間効率の良い関数を取得します。
上記のように、結合トリックを使用します。
ただし、上記のアドバイスにはほとんど問題がありません。最も顕著なのは、ほとんどのアーキテクチャで非境界整列メモリアクセスが遅いことで有名です。一部のコンパイラは、単語境界でない限り、このような定数述語をまったく認識しません。
単なるエンディアンテストは退屈なので、ホストアーキテクチャに関係なく、仕様に従って任意の整数の入力/出力を反転する(テンプレート)関数を次に示します。
#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では少し遅くなりますが(すべてのバイトで&、<<、>>、|展開される)、それでもまともです。
使用しないでくださいunion
!
C ++では、union
sによる型パンニングは許可されていません。
最後に書き込まれたフィールドではない共用体フィールドからの読み取りは、未定義の動作です。
多くのコンパイラはこれを拡張機能としてサポートしていますが、言語はこれを保証しません。
詳細については、この回答を参照してください:
https://stackoverflow.com/a/11996970
移植可能であることが保証されている有効な回答は2つだけです。
C ++ 20をサポートするシステムにアクセスできる場合、最初の答えはヘッダー
から使用することです。 std::endian
<type_traits>
(執筆時点では、C ++ 20はまだリリースされていませんが、何かがstd::endian
のインクルードに影響を与えない限り、これはC ++ 20以降のコンパイル時にエンディアンをテストするための推奨される方法です。)
constexpr bool is_little_endian = (std::endian::native == std::endian::little);
C ++ 20以前は、唯一の有効な答えは、整数を格納してから、型パンニングによって最初のバイトを検査することです。s
の使用とは異なりunion
、これはC ++の型システムで明示的に許可されています。
これは、最適な移植性のためにことを覚えておくことも重要ですstatic_cast
、使用しなければならない
のでreinterpret_cast
、実装が定義されています。
プログラムが次のタイプの1つ以外のglvalueを介してオブジェクトの格納された値にアクセスしようとした場合の動作は定義されていません:... a
char
またはunsigned char
type。
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;
}
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);
}
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);
}
未テストですが、私の心の中で、これはうまくいくでしょうか?リトルエンディアンの場合は0x01、ビッグエンディアンの場合は0x00になるためです。
bool runtimeIsLittleEndian(void)
{
volatile uint16_t i=1;
return ((uint8_t*)&i)[0]==0x01;//0x01=little, 0x00=big
}
宣言:
コンパイル時、非マクロ、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;
}
また、ブーストエンディアンにあるブーストヘッダーファイルのようなものを使用して、プリプロセッサ経由でこれを行うこともできます。
エンディアンヘッダーが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!");
...
__BYTE_ORDER__
、__ORDER_LITTLE_ENDIAN__
と__ORDER_BIG_ENDIAN__
?
条件付きコンパイルが必要ない場合は、エンディアンに依存しないコードを記述するだけです。次に例を示します(Rob Pikeから取得):
ディスク上のリトルエンディアンに格納されている整数を、エンディアンに依存しない方法で読み取ります。
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
これは別の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);
}
整数が1バイト値の場合、エンディアンは意味がなく、コンパイル時エラーが生成されます。
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回だけ実行され、戻り値が次のテストのために保存されるように、関数をコーディングしました。
0x7FE
ですか?なぜ使用malloc()
するのですか?それは無駄です。そして_BE
、(わずかではありますが)メモリリークと発生するのを待っている競合状態です。結果を動的にキャッシュすることの利点は、問題に値しません。代わりに私はこのようなことをします:static const uint16_t teste = 1; int is_little_endian() { return (0x01 == ((uint8_t*)&teste)[0]); } int is_big_endian() { return (0x01 == ((uint8_t*)&teste)[1]); }
シンプルで効果的で、実行時に実行する作業がはるかに少なくなります。
volatile
、または#pragma
、など
エンディアンネス -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;
}
私は教科書を読んでいました:コンピューターシステム:プログラマーの視点」を読んでいましたが、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つのバイトを占め、そしてチャーはわずか1バイトを占めます。我々は、使用できる文字ポインタをポイントにINTコンピュータがリトルエンディアンである場合、このように値1とチャーことチャーポインタそうでなければ、その値は0でなければならず、値1とされる点。