私は現在、[〜#〜] 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
Cでのコンパイル時には、プリプロセッサを信頼する以上のことはできません#define
sであり、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
はマクロです#define
d以前はプリプロセッサのエンディアンチェックによる)
前述のように、ビッグエンディアンを検出する唯一の「実際の」方法は、ランタイムテストを使用することです。
ただし、マクロが優先される場合もあります。
残念ながら、私はこの状況を検出するための単一の「テスト」ではなく、それらのコレクションを見つけました。
たとえば、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を検出することを保証する「標準の事前定義マクロ」のようなものはありませんが、まとめて高い確率を与えるそのような事前定義マクロはたくさんあります。ほとんどの状況でビッグエンディアンを正しく検出します。
コンパイル時のチェックを探す代わりに、ビッグエンディアンの順序(多くの場合、 "network order" と見なされます)を使用して、 htons
/htonl
/ntohs
/ntohl
ほとんどのUNIXシステムおよびWindowsによって提供される関数。彼らはあなたがやろうとしている仕事をするようにすでに定義されています。なぜ車輪の再発明をするのですか?
次のようなものを試してください:
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;
}
他の人が指摘しているように、コンパイル時にエンディアンをチェックするポータブルな方法はありません。ただし、1つのオプションは、ビルドスクリプトの一部としてautoconf
ツールを使用して、システムがビッグエンディアンかリトルエンディアンかを検出し、これを保持するAC_C_BIGENDIAN
マクロを使用することです。情報。ある意味で、これは、システムがビッグエンディアンかリトルエンディアンかを実行時に検出するプログラムを構築し、そのプログラム出力情報を持って、メインソースコードで静的に使用できるようにします。
お役に立てれば!
コンパイル時にすべてのコンパイラ間で移植可能であることを検出することはできません。たぶん、実行時にそれを行うようにコードを変更することができます-これは達成可能です。
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;
}
プリプロセッサディレクティブを使用して、Cでエンディアンを移植可能に検出することはできません。
私はこのパーティーに遅れていることを知っていますが、ここに私の見解があります。
int is_big_endian() {
return 1 & *(uint16_t*)"01";
}
これは、'0'
は10進数で48、'1'
49、つまり'1'
にはLSBビットが設定されていますが、'0'
ではありません。私はそれらを作ることができました'\x00'
および'\x01'
しかし、私のバージョンはそれをより読みやすくすると思います。
私は引用されたテキストを再フォーマットする自由を取りました
2017年7月18日現在、私は_
union { unsigned u; unsigned char c[4]; }
_を使用しています
sizeof (unsigned) != 4
の場合、テストは失敗する可能性があります。
使用する方が良いかもしれません
_union { unsigned u; unsigned char c[sizeof (unsigned)]; }
_
ほとんどの人が述べているように、コンパイル時間は最善の策です。クロスコンパイルを行わず、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を使用している可能性があるため、クロスコンパイルです...しかし、少なくとも、残りのコードをコンパイルするときにエンディアンが得られ、ほとんどのプロジェクトで機能します。 。