2

重複の可能性:
エンディアンが要因になるのはいつですか?

エンディアンに関するこのツトを読んで、私はエンディアンが重要であるこの例に当てはまります。これは、1と0で満たされたchar *を書くことです。その後、shortに変換でき、結果は、エンディアンに応じて、少しまたは大きくなります。これが引用された例です。

unsigned char endian [2] = {1、0}; 短いx;

x = *(short *) endian;

xの値は何でしょうか?このコードが何をしているのか見てみましょう。2バイトの配列を作成してから、その2バイトの配列を1つのshortにキャストします。配列を使用することにより、基本的に特定のバイト順序を強制し、システムがそれらの2バイトをどのように処理するかを確認します。これがリトルエンディアンシステムの場合、0と1は逆方向に解釈され、0,1であるかのように見えます。上位バイトは0であり、下位バイトは1であるため、xは1に等しくなります。一方、ビッグエンディアンシステムの場合、上位バイトは1であり、xの値は256。

私は疑問に思います:与えられた数のメモリバイト割り当て(ここでは2バイト)で配列をインスタンス化するとき、配列にその数が割り当てられている限り、どのように任意のタイプ(short、int ...)に変換できますか?このバイトに対応するバイトの?'このタイプを含む'に十分なメモリが割り当てられていない場合でも、次のメモリアドレスが読み取られますか?たとえば、エンディアンをロングにキャストしたい場合、これはエンディアンの先頭から4バイトを読み取って実行されますか、それとも失敗しますか?

次に、エンディアンに関する質問:これは、メモリ内にバイトを書き込む習慣に関するプロセッサの特性であり、最も重要なバイトが最小のメモリ位置(ビッグエンディアン)または最大のメモリ位置(リトルエンディアン)にあります。この場合、2つの1バイト要素を持つ配列が割り当てられています。1が最も重要なバイトと言われるのはなぜですか?

4

4 に答える 4

2

コンパイラはアセンブリコードのみを書き込むことを忘れないでください。コンパイラーからの警告をすべて無視すると、コンパイラーによって生成されたアセンブリコードを調べて、実際に何が起こるかを理解できます。

私はこの簡単なプログラムを取りました:

#include <iostream>

int main()
{
    unsigned endian[2] = { 0, 0 } ;
    long * casted_endian = reinterpret_cast<long*>( endian );
    std::cout << *casted_endian << std::endl;
}

を使用してこのコードを抽出しobjdumpました。解読しましょう。

 804879c:   55                      push   %ebp
 804879d:   89 e5                   mov    %esp,%ebp
 804879f:   83 e4 f0                and    $0xfffffff0,%esp
 80487a2:   83 ec 20                sub    $0x20,%esp

これらの行は関数のプロローグにすぎません。無視してください。

    unsigned endian[2] = { 0, 0 } ;
 80487a5:   c7 44 24 14 00 00 00    movl   $0x0,0x14(%esp)
 80487ac:   00 
 80487ad:   c7 44 24 18 00 00 00    movl   $0x0,0x18(%esp)
 80487b4:   00 

これらの2行から、(0x14)%espが0で初期化されていることがわかります。したがって、配列endianがスタック上の%ESP(スタックポインタ)+0x14のアドレスにあることがわかります。

    long * casted_endian = reinterpret_cast<long*>( endian );
 80487b5:   8d 44 24 14             lea    0x14(%esp),%eax

LEAは単なる算術演算です。EAXには、スタック上のアレイのアドレスである%ESP+0x14が含まれるようになりました。

 80487b9:   89 44 24 1c             mov    %eax,0x1c(%esp)

また、アドレスESP + 0x1c(変数の場所casted_endian)にEAXを配置するため、エンディアンの最初のバイトのアドレスになります。

    std::cout << *casted_endian << std::endl;
 80487bd:   8b 44 24 1c             mov    0x1c(%esp),%eax
 80487c1:   8b 00                   mov    (%eax),%eax
 80487c3:   89 44 24 04             mov    %eax,0x4(%esp)
 80487c7:   c7 04 24 40 a0 04 08    movl   $0x804a040,(%esp)
 80487ce:   e8 1d fe ff ff          call   80485f0 <std::ostream::operator<<(long)@plt>

次に、これ以上チェックせずに、関連する引数を使用して演算子<<の呼び出しを準備します。これで、プログラムはこれ以上チェックを行いません。変数のタイプは、マシンとはまったく関係ありません。

これで、配列にないoperator<<部分を読み取るときに2つのことが発生する可能性があります。*casted_endian

そのアドレスは、現在マップされているメモリページにあるか、そうでないかのどちらかです。最初のケースでoperator<<は、文句を言わずにそのアドレスにあるものをすべて読み取ります。これはおそらく画面に何か奇妙なことを書くでしょう。2番目のケースでは、OSは、プログラムが読み取れないものを読み取ろうとしていることについて文句を言い、中断を引き起こします。これは有名なセグメンテーション違反です。

于 2012-10-10T18:28:36.820 に答える
0

ああ、主よ。ここで言うのは、これがほとんどのアーキテクチャで機能する理由ですが、これが実際にどれだけ標準であるかはわかりません。

そこで行っているのは、配列endianをshortにキャストすることです。現在、配列は基本的にポインタであり、配列の名前は実際には最初の要素のアドレスを保持しています。唯一の本当の違いは、配列にはより有用なメタデータが含まれており、一部の操作は配列で異なることです(sizeofたとえば)。次に、そのアドレス()を使用して、そこからポインターendianを作成します。shortメモリアドレスは同じままです。それは、ポイントされたデータを別の方法で解釈しているだけです。次に、このポインタを逆参照して値を元に戻し、に割り当てxます。

簡単なサイドノート。これは、すべてのシステムで機能するとは限りません。Cではint、アーキテクチャのネイティブワードサイズ(x86では4バイト、x86_64では8バイト)と同じ幅にのみ定義されます。shortその場合、intよりも短い(またはメモリが正しく機能する場合は等しい)ようにのみ定義されます。このため、そのコードは8ビットアーキテクチャでは失敗します。これが機能するためには、バイト単位のターゲットデータ型のサイズが配列のサイズ以下である必要があります。

同様に、は、x86およびx86_64でそれぞれ通常8バイトまたは16バイトlongより長くなるように定義されています。intその場合、このコードはx86で機能します。

unsigned char endian[8] = {1,2,3,4,5,6,7,8};
long x = *(long*)endian;

とにかく、プロセッサのエンディアンは完全にプロセッサに依存します。x86はリトルエンディアンです(そして基本的にLEデバイスの慣習であるIIRCを開始しました)。SPARCはビッグエンディアンです(9まで、どちらでもかまいません)。ARMとMIPSも構成可能であり、Microblazeは使用するバス(AXIまたはPLB)によって異なります。いずれにせよ、エンディアンはプロセッサだけに限定されるものではなく、ハードウェアや他のコンピュータと通信する際の問題でもあります。

最後の質問では、最上位バイトは、値が表すため、小さいバイトが表すことができる最大値よりも大きいと呼ばれます。16ビットワードの場合、最下位バイトは0〜255を表し、最上位バイトは256〜65535を表すことができます。

いずれにせよ、低レベルのシステムプログラミングを行っている(つまり、メモリを直接変更している)場合や通信プロトコルを記述している場合を除いて、エンディアンについて心配する必要はありません

于 2012-10-10T18:24:42.090 に答える
0

配列よりも大きいサイズにキャストしようとすると、未定義の動作が発生します。おそらく、配列の直後にあるメモリの内容を読み取ろうとしますが、その結果は保証されておらず、一貫している必要もありません。

于 2012-10-10T18:17:02.650 に答える
0
unsigned char endian[2] = {1, 0};
short x;

x = *(short *) endian;

このコードの動作は未定義です。結果を1、256、4000にx設定するか、プログラムがクラッシュするか、その他の問題が合法的に発生する可能性があります。これは、配列がキャスト先の型に対して十分に大きいかどうかを考慮しなくても当てはまります。

これは、コードを合法にし、作成者が意図したとおりに実行するように書き直したものです。

unsigned char endian[sizeof(short)] = {1};
short x;
std::memcpy(&x, endian, sizeof(short));

その配列から抜け出そうとするコードを書くとしたら、intそれは正当な配列の範囲外にアクセスし、再び未定義の振る舞いにぶつかるでしょう。なんでも起こる可能性がある。

この場合、2つの1バイト要素を持つ配列が割り当てられています。1が最も重要なバイトと言われるのはなぜですか?

endian[1](なぜ最上位バイトを保持すると言われているのかを尋ねるつもりだと思います。)

その例では、システムはリトルエンディアンであり、あなたが言うように、リトルエンディアンの定義は、メモリ位置の最上位バイトが最上位アドレスであるということです。最上位バイトを保持するendian[1]よりも高いアドレスを持っていendian[0]ます。endian[1]

于 2012-10-10T18:33:11.823 に答える