4


1

なぜこのEXC_BAD_ACCESSはintではなくlong longで発生するのですか?

データのシリアル化を処理するコードで「EXC_BAD_ACCESS」に遭遇しました。 コードはデバイス(iPhone)でのみ失敗し、シミュレーターでは失敗しません。 また、特定のデータ型でのみ失敗します。

問題を再現するテストコードは次のとおりです。

template
void test_alignment() {
    // allocate memory and record the original address
    unsigned char *origin;
    unsigned char *tmp = (unsigned char*)malloc(sizeof(unsigned short) + sizeof(T));
    origin = tmp;

    // push data with size of 2 bytes
    *((unsigned short*)tmp) = 1;
    tmp += sizeof(unsigned short);

    // attempt to push data of type T
    *((T*)tmp) = (T)1;

    // free the memory
    free(origin);
}

static void test_alignments() {
    test_alignment();
    test_alignment();
    test_alignment();
    test_alignment();
    test_alignment();
    test_alignment();   // fails on iPhone device
    test_alignment();
    test_alignment();      // fails on iPhone device
    test_alignment(); // fails on iPhone device
    test_alignment();
}

これはメモリ調整の問題であると推測し、問題を徹底的に理解したいと思いました。 私の(限られた)メモリアライメントの理解から、 `tmp`が2バイト進むと、アライメントが2バイトを超えるデータ型に対してアライメントがずれます:

    tmp += sizeof(unsigned short);

しかし、テストコードは「int」などのために大丈夫です! 「long long」、「double」、「long double」の場合にのみ失敗します。

各データ型のサイズと配置を調べると、失敗したデータ型は異なる「sizeof」値と「__alignof」値を持つものであることが明らかになりました。

iPhone 4:
bool           sizeof = 1 alignof = 1
wchar_t        sizeof = 4 alignof = 4
short int      sizeof = 2 alignof = 2
int            sizeof = 4 alignof = 4
long int       sizeof = 4 alignof = 4
long long int  sizeof = 8 alignof = 4 // 8 <> 4
float          sizeof = 4 alignof = 4
double         sizeof = 8 alignof = 4 // 8 <> 4
long double    sizeof = 8 alignof = 4 // 8 <> 4
void*          sizeof = 4 alignof = 4

iPhone Simulator on Mac OS X 10.6:
bool           sizeof = 1 alignof = 1
wchar_t        sizeof = 4 alignof = 4
short int      sizeof = 2 alignof = 2
int            sizeof = 4 alignof = 4
long int       sizeof = 4 alignof = 4
long long int  sizeof = 8 alignof = 8
float          sizeof = 4 alignof = 4
double         sizeof = 8 alignof = 8
long double    sizeof = 16 alignof = 16
void*          sizeof = 4 alignof = 4

(これらは、http://www.codesynthesis.com/~boris/blog/2009/04/06/cxx-data-alignment-portability/ ["C ++ data alignment and portability"]から印刷機能を実行した結果です)

誰かがエラーの原因を教えてくれますか? 違いが本当に「EXC_BAD_ACCESS」の原因ですか? もしそうなら、どのようなメカニズムで?

3 Answer


4


それは実際には非常に迷惑ですが、x86以前の世界で購入した私たちにとってはそれほど予想外ではありません:-)

頭に浮かぶ唯一の理由(そしてこれは_pure_推測です)は、コンパイラーがコードを「修正」してデータ型が正しく整列されるようにしますが、 `sizeof / alignof`の不一致が問題を引き起こしていることです。 ARM6アーキテクチャは一部のデータ型のルールの一部を緩和したが、別のCPUを使用するという決定が下されたため、よく見ていないことを思い出すようです。

_(更新:これは実際にはレジスタ設定によって制御されているため(おそらくソフトウェアです)、最新のCPUでさえ、ミスアライメントについて苦々しく文句を言う可能性があります)。

私が最初にすることは、生成されたアセンブリを見て、コンパイラがあなたのショートをパディングして次の(実際の)データ型を調整するか(印象的です)、または実際にプリパディングする可能性が高いことです書き込む前のデータ型。

第二に、IPhone4で使用されているコアと思われるCortex A8の実際のアライメント要件を確認します。

考えられる2つの解決策:

1 /各タイプを `char`配列にキャストし、一度に1つずつ文字を転送する必要がある場合があります。これにより、アライメントの問題を回避できるはずですが、パフォーマンスに影響する可能性があります。 `memcpy`の使用は、おそらく基礎となるCPUを既に利用するようにコーディングされているため(おそらく、開始および終了時に1バイトチャンクで4バイトチャンクを転送するなど)、最適です。

2 /「short」の直後に配置したくないデータ型については、「short」の後に十分なパディングを追加して、正しく整列するようにします。 たとえば、次のようになります。

tmp += sizeof(unsigned short);
tmp = (tmp + sizeof(T)) % alignof(T);

値を保存する前に、次の適切に位置合わせされた場所に「tmp」を進める必要があります。

後で同じ読み取りを行う必要があります(データの種類がわかるように、ショートは保存されているデータを示していると想定しています)。

'' '' '

完全性の答えにOPから最終的な解決策を入力する(したがって、人々はコメントを確認する必要がない):

最初に、アセンブリ(Xcodeで、[実行]メニュー> [デバッガーの表示]> [ソースと逆アセンブリー])は、8バイトのデータ(つまり、 'long long`)を処理するときに STR`の代わりに STMIA`命令が使用されることを示します命令。

次に、「ARMアーキテクチャリファレンスマニュアルARMv7-A」(Cortex A8に対応するアーキテクチャ)の「A3.2.1非境界整列データアクセス」セクションでは、「STM」は非境界整列データアクセスをサポートし、「STR」は(特定のレジストリ設定)。

そのため、問題は「ロングロング」のサイズとずれです。

解決策としては、一度に1文字ずつがスターターとして機能しています。


1


これは、ARMチップのメモリ調整の問題である可能性があります。 ARMチップはアライメントされていないデータを処理できず、特定の境界にアライメントされていないデータにアクセスすると予期しない動作をします。 iPhoneのARMチップのアライメントルールについては頭に浮かぶデータはありませんが、これを解決する最善の方法は、ポインタートリックを使用してデータを突かないことです。


0


すべてのARMプロセッサには、特定のアドレスに単一のワードをロードまたは格納する命令と、一度に複数のワードをロードまたは格納する命令が含まれています。 一部のプロセッサーは、単一の非整列ロード/ストアを一連の2つまたは3つの操作に自動的に変換できますが、そのような機能は、一度に複数のワードをロード/ストアする命令には拡張されません。 `int`のほとんどの操作は、シングルワードのロード/ストア命令のみを使用することを期待します[まれなケースでは、コンパイラが 連続して格納される2つの `int`変数は、単一の命令を使用してレジスタにロードできることを認識していますが、このような最適化は特に期待していません。 ただし、「long long」での操作では、連続したメモリ位置から1組のレジスタが定期的にロードされるため、単一の命令を使用することでメリットが得られます。 最新のARMチップのプロファイルは作成していませんが、ARM7-TDMIのようなものでは、2つの連続した「LDR」命令にそれぞれ3サイクルかかります。 2つのレジスタをロードする「LDM」には4サイクルかかります。 アドレスを計算するために「LDM」の前に「ADD」が必要な場合(「LDR」には「LDM」よりも多くのアドレス指定モードがある)、5サイクルかかる2つの命令は、6つかかる2つの命令よりも優れています。