[解決済み] gccの__attribute__((packed))は大丈夫でしょうか?/ #pragma pack unsafe?
質問
C言語では、コンパイラは構造体のメンバーを宣言された順に並べ、各メンバーが正しく配置されるように、メンバー間または最後のメンバーの後にパディングバイトを挿入することがあります。
gccは言語拡張を提供しています。
__attribute__((packed))
これは、パディングを挿入しないようにコンパイラに指示し、構造体メンバのアラインメントがずれることを許容します。 例えば、システムが通常、すべての
int
オブジェクトは4バイトのアライメントを持つ必要があります。
__attribute__((packed))
を引き起こす可能性があります。
int
構造体のメンバが奇数オフセットで割り当てられることがあります。
gccのドキュメントを引用しています。
packed' 属性は、変数や構造体のフィールドを指定します。 は、可能な限り小さなアライメントを持つ必要があります--変数の場合は1バイト。 でより大きな値を指定しない限り、フィールドは1ビットです。 aligned' 属性を使用します。
この拡張機能を使用すると、必要なデータは小さくなりますが、コンパイラは(プラットフォームによっては)不整列なメンバに1バイトずつアクセスするコードを生成しなければならないため、コードの速度が遅くなることは明らかです。
しかし、これが安全でないケースはあるのでしょうか? コンパイラは、packed structs の不整列メンバにアクセスするために、(速度は遅いですが)常に正しいコードを生成するのでしょうか? また、すべてのケースでそうすることが可能なのでしょうか?
解決方法は?
はい。
__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
オブジェクトは、少なくともどちらか一方に、位置のずれた
x
のメンバーです)。
(この場合
p0
を指しているため、ミスアラインメントされたアドレスを指しています。
int
の後に続くメンバー
char
メンバになります。
p1
は、配列の 2 番目の要素で同じメンバを指しているため、たまたま正しく配置されています。
char
オブジェクトの前にある -- そして SPARC Solaris では、配列
arr
は、4の倍数ではない偶数のアドレスに割り当てられているようです)。
メンバーを参照する場合
x
の
struct foo
を名前で指定すると、コンパイラは
x
は不整列の可能性があるため、正しくアクセスするためのコードを追加で生成します。
のアドレスが
arr[0].x
または
arr[1].x
がポインタオブジェクトに格納されている場合、コンパイラも実行中のプログラムも、それが位置のずれた
int
オブジェクトを作成します。 その結果、(システムによっては)バスエラーやその他の不具合が発生することがあります。
gccでこれを修正するのは、現実的ではないと思います。 一般的な解決策としては、非自明なアライメントを要求する型へのポインタをデリファレンスしようとするたびに、(a)コンパイル時にそのポインタがパック構造体の不整列なメンバーを指していないことを証明するか、(b)アラインメントまたは不整列なオブジェクトを処理できるより大きくて遅いコードを生成するか、が必要になると思います。
を提出しました。 gccバグレポート . 私が言ったように、私はそれを修正することが実用的であるとは思わないが、ドキュメントはそれに言及すべきである(現在はそうでない)。
アップデイト
: 2018-12-20現在、このバグはFIXEDと表示されています。このパッチは、gcc 9 で、新しい
-Waddress-of-packed-member
オプションがデフォルトで有効になっています。
構造体やユニオンの packed メンバのアドレスを取得する場合、そのアドレスが使用されることがあります。 の結果、整列されていないポインタ値になります。 このパッチでは -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;
| ^~~~~~~~~
関連
-
Cエラー [エラー] 代入_Ashesの左オペランドにlvalueが必要です-プログラマーズ・シークレット
-
Solve Dev-c++ [エラー] 'for' ループの初期宣言は、C99 または C11 モードでのみ許可されます。
-
関数 'malloc' の暗黙の宣言に対する解決策
-
[解決済み] c または c++ 用のシンプルな 2 次元クロスプラットフォームグラフィックスライブラリ?[クローズド]
-
[解決済み] C関数から文字列を返す
-
[解決済み] C言語で配列のサイズを決定するにはどうすればよいですか?
-
[解決済み] C言語でファイルが存在するかどうかを確認する最も良い方法は何ですか?
-
[解決済み] C言語でファイルサイズを取得するには?[重複]する
-
[解決済み】#pragma pack の効果
-
[解決済み] 関数から構造体を返す際のGCCバグの可能性
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
C++の配列コピー
-
[解決済み] Valgrind が初期化されていないバイトについて警告する
-
[解決済み] Linux Socket write() によるBad File Descriptor C
-
[解決済み] char *とchar[]の違い [重複]
-
[解決済み] C関数から文字列を返す
-
[解決済み] 配列の場合、なぜ a[5] == 5[a] になるのでしょうか?
-
[解決済み] 構造体のsizeofは、なぜ各メンバーのsizeofの合計と等しくないのですか?
-
[解決済み] C言語で関数をパラメータとして渡すにはどうすればよいですか?
-
[解決済み】uint8_tとunsigned charの比較
-
[解決済み] 属性__((packed, aligned(4))) の意味を教えてください。"