3
#include <stdio.h>
void main(void)
{
    char array[4] = {0, 1, 2, 3};
    float *fpek;
    int i;
    for(i=0;i<4;i++)
    {
        fprintf(stderr,"i = %d ", i);
        fpek = (float *)(&array[i]);
        fprintf(stderr, "fpek = %lx ", (unsigned long) fpek);
        *fpek = (float) i + 10;
        fprintf(stderr, "*fpek = %.2f\n", *fpek);
    }
}

$ cc alignment.c
$ a.out
i = 0 fpek = effff0fc *fpek = 10.00
i = 1 fpek = effff0fd Bus error

上記のコードは、C プログラミングの演習資料で見つかりました。文章自体は理解できますが、著者が何を説明しようとしているのかはよくわかりません。なぜバスエラーが発生するのですか?

4

3 に答える 3

4

整列しているかどうかにかかわらず、これは単純に間違っています:

    fpek = (float *)(&array[i]); // invalid for i>0, questionable for i==0
    fprintf(stderr, "fpek = %lx ", (unsigned long) fpek);
    *fpek = (float) i + 10;

arrayはせいぜい 4 文字幅であるため、system- が 4 バイトであっても、 で代入すると、配列の範囲外のfloat(おそらくアラインされていない) データ バイトをすぐにアドレス指定します。そのポインターを逆参照するとすぐに、ミスアライメントが原因でバスエラーが発生するか、偶然にも、アーキテクチャにアライメントの制限がない(そしてほとんどの場合) 割り当てを使用してスタック変数を踏みつけ始めます。続きます。ii>0float

それ以外に未定義の動作を導入することなく、作成者がおそらく理解しようとしているエラー (アライメントが重要である可能性がある) を強調するには、代わりに次のようなものを検討してください。

#include <stdlib.h>
#include <stdio.h>

int main()
{
    /* note: defined to hold enough bytes for *two* floats. */
    float *fpek = NULL;
    char array[2*sizeof(*fpek)];
    int i;

    /* fill with incrementing values */
    for (i=0; i<sizeof(array)/sizeof(array[0]);++i)
        array[i] = (i+1);

    // now walk the array, one char at a time,
    //  casting the address of the current element
    //  to a float pointer and try to read/write it.
    fprintf(stderr, "array = %p, size=%lu\n", array, sizeof(array));
    for(i=0;i<sizeof(*fpek);++i)
    {
        fprintf(stderr,"i = %d, ", i);
        fpek = (void*)(array+i);
        fprintf(stderr, "fpek = %p, ", fpek);
        *fpek = i+10;
        fprintf(stderr, "*fpek = %.2f\n", *fpek);
    }

    return 0;
}

システムでa がどれほど広い/狭いかに関係なく、これはchar 配列の終わりを通り過ぎることに固有のfloat未定義の動作を導入することなく機能します。それでもバスエラーになる可能性は十分にありますが、少なくとも配列添字を超える UB は解決されます。

これを Mac Air (Intel 64 ビット CPU) で実行しても、バス エラーは発生しません。

array = 0x7fff5fbff868, size=8
i = 0, fpek = 0x7fff5fbff868, *fpek = 10.00
i = 1, fpek = 0x7fff5fbff869, *fpek = 11.00
i = 2, fpek = 0x7fff5fbff86a, *fpek = 12.00
i = 3, fpek = 0x7fff5fbff86b, *fpek = 13.00

ご覧のとおり、私のプラットフォームは、floatアライメントに関して特に細かいことはしていません。結果は変わる可能性があります(以前の出力から判断すると、おそらくそうなるでしょう)。

注: の(void*)キャストはfpek = (void*)(array+i);奇妙に見えるかもしれませんが、これは C であるため、問題なく使用できます。これを行った理由は、他の浮動小数点型のデモを行い、それらにアライメント制限があるかどうかを確認できるようにするためです。fpek書かれているように、関数の先頭にあるの宣言だけを次のように変更できますdouble

double *fpek = NULL;

その後、プログラムを再実行します。私のシステムでは、これにより次が生成されます。

array = 0x7fff5fbff860, size=16
i = 0, fpek = 0x7fff5fbff860, *fpek = 10.00
i = 1, fpek = 0x7fff5fbff861, *fpek = 11.00
i = 2, fpek = 0x7fff5fbff862, *fpek = 12.00
i = 3, fpek = 0x7fff5fbff863, *fpek = 13.00
i = 4, fpek = 0x7fff5fbff864, *fpek = 14.00
i = 5, fpek = 0x7fff5fbff865, *fpek = 15.00
i = 6, fpek = 0x7fff5fbff866, *fpek = 16.00
i = 7, fpek = 0x7fff5fbff867, *fpek = 17.00
于 2013-01-12T20:14:17.193 に答える
3

すべてのシステムがメモリへの非整列アクセスを実行できるわけではありません。たとえば、古い Motorola M68000 では実行できませんでした。これが発生したときの典型的なエラーは、バス エラーです。ただし、これらのシステムは今日では珍しくなりつつあり、最新の (そしてそれほど最新ではない) コンパイラーはそれを適切に処理できるはずです。

于 2013-01-12T19:36:50.507 に答える
0

問題は、char配列(割り当てられたメモリは4バイト)を作成し、floatポインタを配列の先頭にポイントすることです。ポインターに10を追加した後に読み取ろうとしています(ポインターはfloat型からのものであり、sizeof(float)* 10で増分し、これはすでに配列の外にあるため、エラーが発生することが予想されます。

この行を削除してみてください*fpek = (float) i + 10;。そしてそれを試してみてください。

変更するもう1つのことfprintf(stderr, "*fpek = %.2f\n", *fpek);fprintf(stderr, "*fpek = %c\n", *fpek);

于 2013-01-12T20:00:43.760 に答える