はい、__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;
}
x86 Ubuntuでgcc 4.5.2を使用すると、次の出力が生成されます。
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
SPARC Solaris 9でgcc 4.5.1を使用すると、以下が生成されます。
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
以上のオブジェクトの配列では、少なくとも1つまたはもう1つx
メンバーの位置がずれます。)
(この場合、は、メンバーに続くp0
パックされたint
メンバーを指しているため、不揃いなアドレスを指しchar
ます。p1
配列の2番目の要素の同じメンバーを指しているため、そのchar
前に2つのオブジェクトがあるため、たまたま正しく整列されます-およびSPARC Solarisでは、アレイarr
は偶数のアドレスに割り当てられているようですが、4の倍数ではありません。)
コンパイラーx
はstruct foo
、名前でメンバーを参照する場合、x
それが正しく調整されていない可能性があることを認識し、正しくアクセスするための追加コードを生成します。
アドレスいったんarr[0].x
またはarr[1].x
ポインタオブジェクトに格納されている、コンパイラや実行中のプログラムでもないが、それがずれを指していることを知っているint
オブジェクト。正しく調整されていることを前提としているため、(一部のシステムでは)バスエラーなどの障害が発生します。
これをgccで修正することは、実用的ではないと思います。一般的な解決策では、(a)コンパイル時にポインタがパックされた構造体の誤って配置されたメンバーを指していないことをコンパイル時に証明するか、(b)いずれかの型へのポインタを逆参照しようとするたびに、整列されたオブジェクトまたは整列されていないオブジェクトを処理できる、より大きくて遅いコードを生成する。
gccバグレポートを送信しました。私が言ったように、私はそれを修正することが実用的であるとは思わないが、ドキュメントはそれを言及すべきである(現在はそうではない)。
更新:2018-12-20現在、このバグは修正済みとしてマークされています。パッチはgcc 9に表示され、新しい-Waddress-of-packed-member
オプションが追加され、デフォルトで有効になります。
structまたはunionのパックされたメンバーのアドレスが取得されると、アライメントされていないポインター値になる可能性があります。このパッチは、-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;
| ^~~~~~~~~