web-dev-qa-db-ja.com

エンディアンの検出

私は現在、[〜#〜] c [〜#〜]ソースコードを作成しようとしています。このソースコードは、エンディアンが何であれ、I/Oを適切に処理します。ターゲットシステム。

I/O規則として「リトルエンディアン」を選択しました。つまり、ビッグエンディアンCPUの場合、書き込みまたは読み取り中にデータを変換する必要があります。

変換は問題ではありません。私が直面している問題は、できればコンパイル時にエンディアンを検出することです(CPUは実行中にエンディアンを変更しないため...)。

今まで、私はこれを使用してきました:

_#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
...
#else
...
#endif
_

これはGCCの事前定義されたマクロとして文書化されており、Visualもそれを理解しているようです。

ただし、一部のbig_endianシステム(PowerPC)でチェックが失敗するという報告を受けました。

そのため、コンパイラーやターゲットシステムに関係なく、エンディアンが正しく検出されることを保証する、絶対確実なソリューションを探しています。まあ、それらのほとんどは少なくとも...

[編集]:提案されたソリューションのほとんどは、「ランタイムテスト」に依存しています。これらのテストは、コンパイル中にコンパイラーによって適切に評価される場合があるため、実際の実行時のパフォーマンスは犠牲になりません。

ただし、ある種の<< if (0) { ... } else { ... } >>で分岐するだけでは不十分です。現在のコード実装では、変数と関数宣言はbig_endian検出に依存しています。これらはifステートメントで変更できません。

まあ、明らかに、コードを書き直すことであるフォールバック計画があります...

私はそれを避けたいのですが、まあ、それは減少する希望のように見えます...

[編集2]:コードを大幅に変更して「ランタイムテスト」をテストしました。それらは正しく機能しますが、これらのテストはパフォーマンスにも影響を与えます。

テストには予測可能な出力があるため、コンパイラーは不良ブランチを排除できると期待していました。しかし、残念ながら、それは常に機能するとは限りません。 MSVCは優れたコンパイラであり、不良ブランチの排除に成功していますが、GCCの結果はバージョンやテストの種類によって異なり、32ビットよりも64ビットに大きな影響を与えます。

それは変だ。また、ランタイムテストがコンパイラによって処理されることを保証できないことも意味します。

編集3:最近、私はコンパイル時定数ユニオンを使用しており、コンパイラーがそれを明確なyes/noシグナルに解決することを期待しています。そしてそれはかなりうまく機能します: https://godbolt.org/g/DAafKo

21
Cyan

Cでのコンパイル時には、プリプロセッサを信頼する以上のことはできません#definesであり、C標準はエンディアンに関係していないため、標準ソリューションはありません。

それでも、プログラムの開始時に実行時に実行されるアサーションを追加して、コンパイル時に実行された仮定が正しいことを確認できます。

inline int IsBigEndian()
{
    int i=1;
    return ! *((char *)&i);
}

/* ... */

#ifdef COMPILED_FOR_BIG_ENDIAN
assert(IsBigEndian());
#Elif COMPILED_FOR_LITTLE_ENDIAN
assert(!IsBigEndian());
#else
#error "No endianness macro defined"
#endif

(どこ COMPILED_FOR_BIG_ENDIANおよびCOMPILED_FOR_LITTLE_ENDIANはマクロです#defined以前はプリプロセッサのエンディアンチェックによる)

13
Matteo Italia

前述のように、ビッグエンディアンを検出する唯一の「実際の」方法は、ランタイムテストを使用することです。

ただし、マクロが優先される場合もあります。

残念ながら、私はこの状況を検出するための単一の「テスト」ではなく、それらのコレクションを見つけました。

たとえば、GCCは次のことを推奨しています:___BYTE_ORDER__ == __ORDER_BIG_ENDIAN___。ただし、これは最新バージョンでのみ機能し、NULL == NULLであるため、以前のバージョン(および他のコンパイラ)ではこのテストに偽の値「true」が与えられます。したがって、より完全なバージョンが必要です:defined(__BYTE_ORDER__)&&(__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)

OK、これは最新のGCCで機能しますが、他のコンパイラはどうですか?

ビッグエンディアンコンパイラで定義されることが多い___BIG_ENDIAN___または___BIG_ENDIAN_または__BIG_ENDIAN_を試すことができます。

これにより、検出が向上します。ただし、特にPowerPCプラットフォームをターゲットにしている場合は、さらにいくつかのテストを追加して、さらに多くの検出を改善できます。 __Arch_PPC_または___PPC___または___PPC_またはPPCまたは___powerpc___または___powerpc_またはpowerpcを試してください。これらすべての定義をバインドすると、コンパイラとそのバージョンに関係なく、ビッグエンディアンシステム、特にpowerpcを検出するかなりのチャンスがあります。

したがって、要約すると、すべてのプラットフォームとコンパイラでビッグエンディアンCPUを検出することを保証する「標準の事前定義マクロ」のようなものはありませんが、まとめて高い確率を与えるそのような事前定義マクロはたくさんあります。ほとんどの状況でビッグエンディアンを正しく検出します。

17
Cyan

コンパイル時のチェックを探す代わりに、ビッグエンディアンの順序(多くの場合、 "network order" と見なされます)を使用して、 htons/htonl/ntohs/ntohl ほとんどのUNIXシステムおよびWindowsによって提供される関数。彼らはあなたがやろうとしている仕事をするようにすでに定義されています。なぜ車輪の再発明をするのですか?

15
Chris Lutz

次のようなものを試してください:

if(*(char *)(int[]){1}) {
    /* little endian code */
} else {
    /* big endian code */
}

コンパイラがコンパイル時にそれを解決するかどうかを確認します。そうでない場合は、組合で同じことをするほうが幸運かもしれません。実際、私はbuf[HI]buf[LO]にアクセスするなどのことができるように、(それぞれ)0、1、または1,0と評価される共用体を使用してマクロを定義するのが好きです。

コンパイラ定義のマクロにもかかわらず、これを検出するコンパイル時の方法はないと思います。アーキテクチャのエンディアンを判断するには、データをメモリに格納する方法を分析する必要があるためです。

これがまさにそれを行う関数です:

bool IsLittleEndian () {

    int i=1;

    return (int)*((unsigned char *)&i)==1;

}

これはpから来ています。 45 of Cのポインタ

#include <stdio.h>
#define BIG_ENDIAN 0
#define LITTLE_ENDIAN 1

int endian()
{
   short int Word = 0x0001;
   char *byte = (char *) &Word;
   return (byte[0] ? LITTLE_ENDIAN : BIG_ENDIAN);
}

int main(int argc, char* argv[])
{
   int value;
   value = endian();
   if (value == 1)
      printf("The machine is Little Endian\n");
   else
      printf("The machine is Big Endian\n");
   return 0;
}
4
Geremia

他の人が指摘しているように、コンパイル時にエンディアンをチェックするポータブルな方法はありません。ただし、1つのオプションは、ビルドスクリプトの一部としてautoconfツールを使用して、システムがビッグエンディアンかリトルエンディアンかを検出し、これを保持するAC_C_BIGENDIANマクロを使用することです。情報。ある意味で、これは、システムがビッグエンディアンかリトルエンディアンかを実行時に検出するプログラムを構築し、そのプログラム出力情報を持って、メインソースコードで静的に使用できるようにします。

お役に立てれば!

4
templatetypedef

コンパイル時にすべてのコンパイラ間で移植可能であることを検出することはできません。たぶん、実行時にそれを行うようにコードを変更することができます-これは達成可能です。

2
pmod

Socketのntohl関数をこの目的に使用できます。 ソース

// Soner
#include <stdio.h>
#include <arpa/inet.h>


int main() {
    if (ntohl(0x12345678) == 0x12345678) {
        printf("big-endian\n");
    } else if (ntohl(0x12345678) == 0x78563412) {
        printf("little-endian\n");
    } else {
        printf("(stupid)-middle-endian\n");
    }
    return 0;
}
2
snr

プリプロセッサディレクティブを使用して、Cでエンディアンを移植可能に検出することはできません。

1
ouah

私はこのパーティーに遅れていることを知っていますが、ここに私の見解があります。

int is_big_endian() {
    return 1 & *(uint16_t*)"01";
}

これは、'0'は10進数で48、'1' 49、つまり'1'にはLSBビットが設定されていますが、'0'ではありません。私はそれらを作ることができました'\x00'および'\x01'しかし、私のバージョンはそれをより読みやすくすると思います。

0
Garmekain

私は引用されたテキストを再フォーマットする自由を取りました

2017年7月18日現在、私は_union { unsigned u; unsigned char c[4]; }_を使用しています

sizeof (unsigned) != 4の場合、テストは失敗する可能性があります。

使用する方が良いかもしれません

_union { unsigned u; unsigned char c[sizeof (unsigned)]; }
_
0
pmg

ほとんどの人が述べているように、コンパイル時間は最善の策です。クロスコンパイルを行わず、cmakeを使用すると仮定すると(もちろん、configureスクリプトなどの他のツールでも機能します)、コンパイルされた事前テストを使用できます。 .cまたは.cppファイル。これにより、実行しているプロセッサの実際に検証されたエンディアンが得られます。

cmakeでは、 TestBigEndian マクロを使用します。それはあなたがそれからあなたのソフトウェアに渡すことができる変数を設定します。このようなもの(未テスト):

TestBigEndian(IS_BIG_ENDIAN)
...
set(CFLAGS ${CFLAGS} -DIS_BIG_ENDIAN=${IS_BIG_ENDIAN}) // C
set(CXXFLAGS ${CXXFLAGS} -DIS_BIG_ENDIAN=${IS_BIG_ENDIAN}) // C++

次に、C/C++コードで次のことを確認できますIS_BIG_ENDIAN定義:

#if IS_BIG_ENDIAN
    ...do big endian stuff here...
#else
    ...do little endian stuff here...
#endif

したがって、このようなテストの主な問題は、エンディアンが異なる完全に異なるCPUを使用している可能性があるため、クロスコンパイルです...しかし、少なくとも、残りのコードをコンパイルするときにエンディアンが得られ、ほとんどのプロジェクトで機能します。 。

0
Alexis Wilke