はい、__attribute__((packed))一部のシステムでは安全でない可能性があります。この症状はおそらく x86 では表示されないため、問題がさらに厄介になります。x86 システムでテストしても問題は明らかになりません。(x86 では、整列されていないアクセスはハードウェアで処理されます。int*奇数アドレスを指すポインターを逆参照すると、適切に整列されている場合よりも少し遅くなりますが、正しい結果が得られます。)
SPARC などの他の一部のシステムでは、整列されていないintオブジェクトにアクセスしようとすると、バス エラーが発生し、プログラムがクラッシュします。
また、アラインされていないアクセスがアドレスの下位ビットを静かに無視し、メモリの間違ったチャンクにアクセスするシステムもありました。
次のプログラムを検討してください。
#include <stdio.h>
#include <stddef.h>
int main(void)
{
struct foo {
char c;
int x;
} __attribute__((packed));
struct foo arr[2] = { { 'a', 10 }, {'b', 20 } };
int *p0 = &arr[0].x;
int *p1 = &arr[1].x;
printf("sizeof(struct foo) = %d\n", (int)sizeof(struct foo));
printf("offsetof(struct foo, c) = %d\n", (int)offsetof(struct foo, c));
printf("offsetof(struct foo, x) = %d\n", (int)offsetof(struct foo, x));
printf("arr[0].x = %d\n", arr[0].x);
printf("arr[1].x = %d\n", arr[1].x);
printf("p0 = %p\n", (void*)p0);
printf("p1 = %p\n", (void*)p1);
printf("*p0 = %d\n", *p0);
printf("*p1 = %d\n", *p1);
return 0;
}
gcc 4.5.2 を使用する x86 Ubuntu では、次の出力が生成されます。
sizeof(struct foo) = 5
offsetof(struct foo, c) = 0
offsetof(struct foo, x) = 1
arr[0].x = 10
arr[1].x = 20
p0 = 0xbffc104f
p1 = 0xbffc1054
*p0 = 10
*p1 = 20
gcc 4.5.1 を使用する SPARC Solaris 9 では、次のように生成されます。
sizeof(struct foo) = 5
offsetof(struct foo, c) = 0
offsetof(struct foo, x) = 1
arr[0].x = 10
arr[1].x = 20
p0 = ffbff317
p1 = ffbff31c
Bus error
どちらの場合も、プログラムは追加のオプションなしでコンパイルされますgcc packed.c -o packed。.
(配列ではなく単一の構造体を使用するプログラムでは、確実に問題が発生するわけではありません。これは、コンパイラーが構造体を奇数アドレスに割り当てることができるため、xメンバーが適切に整列されるためです。2 つのオブジェクトの配列ではstruct foo、少なくとも一方または他方がメンバーの位置がずれxます。)
(この場合、メンバーに続くp0パックされたメンバーを指しているため、整列されていないアドレスを指しています。配列の 2 番目の要素で同じメンバーを指しているため、たまたま正しく整列されているため、その前に 2 つのオブジェクトがあります。 -- また、SPARC Solaris では、配列は偶数のアドレスに割り当てられているように見えますが、4 の倍数ではありません。)intcharp1chararr
x名前で aのメンバーを参照する場合struct foo、コンパイラはそれxが位置合わせされていない可能性があることを認識し、正しくアクセスするための追加のコードを生成します。
arr[0].xまたはのアドレスがarr[1].xポインタ オブジェクトに格納されると、コンパイラも実行中のプログラムも、それが位置合わせされていないオブジェクトを指していることを認識しませんint。適切に配置されていると想定しているため、(一部のシステムでは) バス エラーまたは同様の障害が発生します。
これを gcc で修正するのは非現実的だと思います。一般的な解決策では、重要なアラインメント要件を持つ任意の型へのポインターを逆参照しようとするたびに、(a) ポインターがパックされた構造体の位置合わせされていないメンバーを指していないことをコンパイル時に証明するか、または (b) のいずれかが必要になります。アラインされたオブジェクトまたはアラインされていないオブジェクトを処理できる、よりかさばる低速のコードを生成します。
gcc バグ レポートを提出しました。私が言ったように、それを修正するのは現実的ではないと思いますが、ドキュメントにはそれについて言及する必要があります (現在はありません)。
更新: 2018 年 12 月 20 日現在、このバグは修正済みとしてマークされています。パッチは、-Waddress-of-packed-memberデフォルトで有効になっている新しいオプションが追加された gcc 9 に表示されます。
構造体または共用体のパックされたメンバーのアドレスが取得されると、アラインされていないポインター値になる場合があります。このパッチは -Waddress-of-packed-member を追加して、ポインターの割り当て時にアラインメントをチェックし、アラインされていないアドレスとアラインされていないポインターを警告します
そのバージョンの gcc をソースからビルドしました。上記のプログラムの場合、次の診断が生成されます。
c.c: In function ‘main’:
c.c:10:15: warning: taking address of packed member of ‘struct foo’ may result in an unaligned pointer value [-Waddress-of-packed-member]
10 | int *p0 = &arr[0].x;
| ^~~~~~~~~
c.c:11:15: warning: taking address of packed member of ‘struct foo’ may result in an unaligned pointer value [-Waddress-of-packed-member]
11 | int *p1 = &arr[1].x;
| ^~~~~~~~~