4

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

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

template <typename T>
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<bool>();
    test_alignment<wchar_t>();
    test_alignment<short>();
    test_alignment<int>();
    test_alignment<long>();
    test_alignment<long long>();   // fails on iPhone device
    test_alignment<float>();
    test_alignment<double>();      // fails on iPhone device
    test_alignment<long double>(); // fails on iPhone device
    test_alignment<void*>();
}

メモリーアライメントの問題だと思い、徹底的に理解したいと思いました。私の(限定された)メモリアライメントの理解から、tmp2バイト進むと、アライメントが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

(これらは、「C ++データアライメントと移植性」から印刷機能を実行した結果です)

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

4

3 に答える 3

4

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

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

(更新:これは実際にはレジスタ設定(したがっておそらくソフトウェア)によって制御されるので、最近のCPUでさえミスアライメントについてひどく不平を言うことができると思います)。

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

次に、iPhone4で使用されているコアであると私が思うCortexA8の実際の位置合わせ要件を調べます。

2つの可能な解決策:

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

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

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

tmp値を保存する前に、次に適切に配置された場所に進む必要があります。

後で同じように読み戻す必要があります(shortはデータが格納されていることを示していると思いますので、データ型がわかります)。


OPからの最終的な解決策を完全性のための答えに入れます(人々がコメントをチェックする必要がないように):

まず、アセンブリ(Xcode上で)は、命令の代わりに8バイトのデータ(つまり、)を処理するときに命令が使用されるRun menu > Debugger Display > Source and Disassemblyことを示しています。STMIAlong longSTR

次に、「ARMアーキテクチャリファレンスマニュアルARMv7-A」(Cortex A8に対応するアーキテクチャ)の「A3.2.1非アラインデータアクセス」のセクションでは、STMIA非アラインデータアクセスをサポートしている間はサポートしないと述べていSTRます(特定のレジストリ設定によって異なります)。

ですから、問題はサイズlong longとミスアライメントでした。

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

于 2010-07-14T03:51:15.513 に答える
1

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

于 2010-07-14T03:58:29.710 に答える
0

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

于 2014-05-29T18:59:40.370 に答える