[解決済み】ストリクト・エイリアシング・ルールとは何ですか?
質問
について質問する場合
C言語における一般的な未定義の動作
という、厳密なエイリアシングルールについて言及されることがあります。
何を言っているのだろう?
解決方法は?
厳密にエイリアシングの問題に遭遇する典型的な状況は、構造体(デバイス/ネットワークのメッセージなど)をシステムのワードサイズのバッファにオーバーレイするときです(例えば、ポインタの
uint32_t
または
uint16_t
s). このようなバッファに構造体を重ねたり、ポインタキャストによって構造体にバッファを重ねたりすると、厳格なエイリアシングルールに簡単に違反することができます。
このような場合、何かにメッセージを送ろうとすると、同じメモリの塊を指す2つの互換性のないポインタが必要になるわけです。そうすると、素朴に次のようなコードを書くかもしれません。
typedef struct Msg
{
unsigned int a;
unsigned int b;
} Msg;
void SendWord(uint32_t);
int main(void)
{
// Get a 32-bit buffer from the system
uint32_t* buff = malloc(sizeof(Msg));
// Alias that buffer through message
Msg* msg = (Msg*)(buff);
// Send a bunch of messages
for (int i = 0; i < 10; ++i)
{
msg->a = i;
msg->b = i+1;
SendWord(buff[0]);
SendWord(buff[1]);
}
}
厳密なエイリアシングルールでは、この設定は違法となります。 互換型 または C 2011 6.5 パラグラフ 7 で許可されているその他の型のうちの 1 つになります。 1 は未定義の動作です。残念ながら、まだこのようなコーディングが可能です。 もしかしたら 警告が出て、コンパイルはうまくいったのに、コードを実行すると予期せぬおかしな挙動をする。
(GCCはエイリアシング警告を出す能力にやや一貫性がないようで、親切な警告を出すときと出さないときがあります)。
なぜこの動作が未定義なのかを知るには、厳密なエイリアシングルールがコンパイラに何を買っているのかを考える必要があります。基本的に、このルールがあれば、コンパイラは
buff
ループの実行のたびに その代わり、最適化の際に、エイリアシングに関するいくつかの厄介な前提条件を用いて、これらの命令を省くことができ、ロードされた
buff[0]
と
buff[1]
をループ実行前に一度だけCPUのレジスタに登録し、ループ本体を高速化します。厳密なエイリアシングが導入される以前は、コンパイラは以下のような内容をパラノイア状態で生活しなければなりませんでした。
buff
が、先行するメモリストアによって変更される可能性があります。そこで、より高いパフォーマンスを得るために、また、ほとんどの人がポインターをタイプパンしないと仮定して、ストリクト・エイリアス・ルールが導入されたのです。
もし、この例が作為的だと思うのであれば、送信を行う他の関数にバッファを渡す場合にも、このようなことが起こるかもしれないことを覚えておいてください。
void SendMessage(uint32_t* buff, size_t size32)
{
for (int i = 0; i < size32; ++i)
{
SendWord(buff[i]);
}
}
そして、この便利な関数を利用するために、先ほどのループを書き直しました。
for (int i = 0; i < 10; ++i)
{
msg->a = i;
msg->b = i+1;
SendMessage(buff, 2);
}
コンパイラは SendMessage をインライン化することができるかもしれないし、できないかもしれない、そして buff を再度ロードするかしないかを決定するかもしれません。もし
SendMessage
が別個にコンパイルされた別の API の一部である場合、おそらく buff の内容をロードする命令があるはずです。また、C++で、コンパイラがインライン化できると考えた、テンプレート化されたヘッダのみの実装である可能性もあります。あるいは、あなたが自分の便宜のために.cファイルに書いただけかもしれません。いずれにせよ、未定義の動作が発生する可能性はある。ボンネットの中で何が起きているのかがある程度わかっていても、ルール違反であることに変わりはないので、うまく定義された動作は保証されない。だから、単語区切りのバッファを受け取る関数をラップしても、必ずしも解決にはならない。
では、これを回避するにはどうすればよいのでしょうか?
-
ユニオンを使用する。ほとんどのコンパイラは、厳密なエイリアスについて文句を言うことなく、これをサポートしています。これはC99で許可されており、C11では明示的に許可されています。
union { Msg msg; unsigned int asBuffer[sizeof(Msg)/sizeof(unsigned int)]; };
-
コンパイラでストリクト・エイリアスを無効にすることができます ( f[no-]strict-aliasing gccの場合))
-
を使用することができます。
char*
を使用すると、システムの単語の代わりにエイリアシングを行うことができます。ルールでは、例外としてchar*
(を含む)。signed char
とunsigned char
). 常に想定されているのはchar*
は他の型をエイリアスします。しかし、これは逆には働きません。構造体が文字列のバッファをエイリアスするという前提はないのです。
初心者の方はご注意ください
このように、2つのタイプを重ね合わせることは、地雷の可能性があります。また エンディアン , ワードアライメント によるアライメントの問題への対処方法、および パッキング構造 を正しく表示します。
脚注
1 C 2011 6.5 7でlvalueがアクセスできる型は次のとおりです。
- オブジェクトの実効型と互換性のある型。
- オブジェクトの有効な型と互換性のある型の修飾バージョン。
- オブジェクトの有効な型に対応する符号付きまたは符号なし型である。
- オブジェクトの有効型の修飾バージョンに対応する符号付きまたは符号なしの型。
- そのメンバに前述の型のいずれかを含む集約型または和集合型(再帰的に、下位集約型または含まれる和集合のメンバを含む)、または
- 文字型。
関連
-
[解決済み】C++コンパイルタイムエラー:数値定数の前に期待される識別子
-
[解決済み】Cygwin Make bash コマンドが見つかりません。
-
[解決済み】Enterキーを押して続行する
-
[解決済み] explicit キーワードの意味は?
-
[解決済み] ルール・オブ・スリーとは?
-
[解決済み] コピーアンドスワップ慣用句とは?
-
[解決済み] C++11では、標準化されたメモリモデルが導入されました。その意味するところは?そして、C++プログラミングにどのような影響を与えるのでしょうか?
-
[解決済み] 未定義の動作とシーケンスポイント
-
[解決済み] 未定義、未指定、および実装で定義された動作
-
[解決済み】C/C++の"-->"演算子とは何ですか?
最新
-
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++エラー。アーキテクチャ x86_64 に対して未定義のシンボル
-
[解決済み】構造体のベクター初期化について
-
[解決済み】致命的なエラー LNK1169: ゲームプログラミングで1つ以上の多重定義されたシンボルが発見された
-
[解決済み] error: 'if' の前に unqualified-id を期待した。
-
[解決済み】文字列関数で'char const*'のインスタンスを投げた後に呼び出されるterminate [閉店].
-
[解決済み】C++エラー:の初期化に一致するコンストラクタがありません。
-
[解決済み】クラスのコンストラクタへの未定義参照、.cppファイルの修正も含む
-
[解決済み] to_string は std のメンバーではない、と g++ が言っている (mingw)
-
[解決済み】VC++の致命的なエラーLNK1168:書き込みのためにfilename.exeを開くことができません。
-
[解決済み】'std::cout'への未定義の参照