問題タブ [strict-aliasing]
For questions regarding programming in ECMAScript (JavaScript/JS) and its various dialects/implementations (excluding ActionScript). Note JavaScript is NOT the same as Java! Please include all relevant tags on your question; e.g., [node.js], [jquery], [json], [reactjs], [angular], [ember.js], [vue.js], [typescript], [svelte], etc.
c++ - char* と std::uint8_t* の間の reinterpret_cast - 安全ですか?
現在、バイナリ データを扱う必要がある場合があります。C++ では、一連のバイトを扱います。最初から、char
ビルディング ブロックでした。1を持つように定義されたsizeof
、それはバイトです。また、すべてのライブラリ I/O 関数はchar
デフォルトで使用します。すべては問題ありませんが、常に少しの懸念がありました。一部の人々を悩ませた少し奇妙な点です。1 バイトのビット数は処理系で定義されています。
そのため、C99 では、開発者が固定幅整数型を簡単に表現できるように、いくつかの typedef を導入することが決定されました。もちろん、可搬性を損なうことは絶対にしたくないので、オプションです。その中で、固定幅の 8 ビット符号なし整数型uint8_t
として C++11 に移行されstd::uint8_t
た は、8 ビット バイトを本当に処理したい人にとって最適な選択でした。
std::uint8_t*
そのため、開発者は新しいツールを採用し、8 ビットのバイト シーケンスをstd::vector<std::uint8_t>
やその他の形式で受け入れることを明示的に示すライブラリの構築を開始しました。
しかし、おそらく非常に深く考えて、標準化委員会は の実装を要求しないことを決定したため、開発者がs をバイナリ データとしてstd::char_traits<std::uint8_t>
簡単かつ移植可能にインスタンス化したりstd::basic_fstream<std::uint8_t>
、簡単に読み取ったりすることを禁止しました。std::uint8_t
あるいは、1 バイトのビット数を気にせず、満足している人もいます。
char*
しかし、残念なことに、2 つの世界が衝突し、データを as として取得し、それを期待するライブラリに渡さなければならない場合がありますstd::uint8_t*
。でもちょっと待って、char
可変ビットじゃなくstd::uint8_t
て8固定じゃないの?データが失われますか?
さて、これについて興味深い標準があります。はchar
正確に 1 バイトを保持するように定義されており、バイトはメモリのアドレス指定可能な最小のチャンクであるため、ビット幅が のビット幅よりも小さい型は存在できませんchar
。次に、UTF-8 コード単位を保持できるように定義されています。これにより、最小値である 8 ビットが得られます。これで、8 ビット幅である必要がある typedef と、少なくとも 8 ビット幅の型ができました。しかし、代替手段はありますか?はいunsigned char
。char
の署名は実装定義であることを思い出してください。他のタイプは?ありがたいことに、いいえ。他のすべての整数型には、8 ビット外の範囲が必要です。
最後に、std::uint8_t
オプションです。つまり、この型を使用するライブラリは、定義されていないとコンパイルされません。しかし、それがコンパイルされるとどうなるでしょうか? これは、8 ビット バイトとCHAR_BIT == 8
.
8 ビットのバイトがあり、 または のいずれかとして実装されているというこの知識をstd::uint8_t
得たら、 からへ、またはその逆を行うことができると仮定できますか? ポータブルですか?char
unsigned char
reinterpret_cast
char*
std::uint8_t*
これは、私の標準語の読解力が私を失敗させるところです。私は安全に派生したポインター ( [basic.stc.dynamic.safety]
) について読み、私が理解している限り、次のことを読みました。
触れなければ安全ですbuffer2
。私が間違っている場合は修正してください。
したがって、次の前提条件が与えられます。
CHAR_BIT == 8
std::uint8_t
が定義されています。
バイナリデータを扱っていて、潜在的な符号の欠如が問題にならないと仮定すると、移植性があり、前後にキャストchar*
しても安全ですか?std::uint8_t*
char
説明付きの標準への参照をいただければ幸いです。
編集: ありがとう、ジェリー コフィン。標準 ([basic.lval]、§3.10/10) からの引用を追加します。
プログラムが、次の型以外の glvalue を介してオブジェクトの格納された値にアクセスしようとした場合、動作は未定義です。
...
— char または unsigned char 型。
EDIT2:わかりました、さらに深くなります。std::uint8_t
の typedef であることは保証されませんunsigned char
。拡張符号なし整数型として実装でき、拡張符号なし整数型は §3.10/10 に含まれていません。今何?
c++ - G++: __attribute__((__may_alias__)) をクラス定義自体ではなく、クラス インスタンスへのポインタとして使用できますか?
私は次の質問への答えを探しています:may_alias
あるクラスのオブジェクトへのポインタの属性として適していますFoo
か? それとも、クラス レベルでのみ使用する必要がありますか?
次のコードを検討してください (より複雑な実際の例に基づいています)。
その単一のポインターをmay_alias
別のポインターにgccに伝える方法はありますか?
ところで、このような問題の gcc 検出メカニズムは不完全であるため、実際に問題を解決せずにこの警告を簡単に消すことができます。
次のコード スニペットを検討してください。
行の 1 つをコメント解除して、コンパイラ出力の違いを確認します。
c - パイプへのポインターを書き込みます。厳密なエイリアシングまたは駄洒落タイプの問題はありますか?
同じプロセス内のスレッド間の通信に使用される、プログラム内に多数の FIFO キューを作成する必要があります。
この目的のために pipe() を使用できると思います。このようにして、キューからノードをフェッチするスレッドで select または poll を使用できるからです。
問題は、各ノードが構造体であるため、ポインターをキューに入れる方法です。ポインターをキューに入れたいのですが、次のようなものです。
typedef{ struct パケット *pkt; 構造体情報 *info; int シーケンス; }ノード;
次に、mynode が必要です。
私のプログラムに何か問題がありますか?特に厳密なエイリアシングまたは駄洒落タイプの問題? ありがとう!
c - 標準に違反することなく、構造体へのポインタを使用して配列をエイリアシングする
これを読んで、構造体に互換性のあるメンバーがある場合、つまり次の構造体が与えられている場合、(標準に違反することなく) 構造体にエイリアスを設定できることがわかりました。
以下はエイリアシングの規則に違反します:
ただし、次の場合はそうではありません。
問題の「集約型」には、キャストするポインターと互換性のある型が含まれているためです。つまり、型へのポインターは、エイリアス規則を破ることなくuint32_t
、型のメンバー (またはメンバー) を含む構造体にキャストできます。uint32_t
まず、これを正しく理解できましたか?
次に、構造体内の (他の) 変数の順序と型は重要ですか? が次のようFrizzly
に定義されているとします。
2 番目の例のキャストの後、互換性のない ( ) 型b
のメモリによってサポートされるようになりました。uint32_t
キャストはまだ有効ですか (または、キャストされたポインターを介して値にアクセスします)? のいずれかの要素を変更すると、厳密なエイリアシングが無効になっているかのように、a
の最初の要素の値が変更されますか?i
また、上記が有効な場合、次のような構造体がある場合はどうなりますか:
次のキャストは、私が正しければ、エイリアシング ルールに違反します。
単一のunsigned char
メンバーを構造体に追加するだけで、キャストを有効にすることはできますか? 言い換えると、互換性のない型のポインターをキャストすると、通常、エイリアシング規則が破られますが、型のメンバーを含む構造体X
へのポインターからポインターへX
のキャストは例外であるため、X へのポインターから集計 Y へのキャストはすべて可能です。タイプXの(おそらくダミーの)メンバーをYに追加するだけで有効になりますか?
(上記のコード スニペットをコンパイラで実際にテストしたわけではありません。)
編集:
私の言い回しと例はかなり貧弱かもしれないことを知っているので、質問を言い換えてみます: 私が正しく理解していれば、構造体へのポインターが型 'X' の要素の配列をエイリアスすることは合法です。構造体には「X」型のメンバーが含まれています。現在、構造体のメンバーを逆参照する場合、メンバーは型 'X' である必要がありますか。適切なタイプ?
c++ - GCC と Clang がこのエイリアシング最適化を行わないのはなぜですか?
友人が「ベース」型の非基本クラス オブジェクトをクラス型オブジェクト「派生」にキャストする場合があります。「派生」は「ベース」の派生クラスであり、関数のみを追加しますが、データは追加しません。以下のコードでは、x
派生クラスにデータ メンバーを追加しました。
厳密なエイリアス分析をオンにすると、GCC (Clang も同様) は常に を返します。明確に定義されたコードでは を指すことができない10
ため11
です。ただし、削除すると(友人のコードで実際にそうであるように)、GCC の出力アセンブラー コードはのリターン アクセスを最適化せず、メモリから値をリロードします。したがって、GCCで「動作」を呼び出す友人のコードは(彼が意図したように)、まだ未定義の動作をしていると思いますがb
a
B::x
a.a
g
したがって、本質的に同じ 2 つのケースで、GCC は一方のケースを最適化し、もう一方のケースを最適化しません。合法的に指すことb
ができるからですか?それとも、GCC が実際のコードを壊したくないだけなのでしょうか?a
私は述べている答えをテストしました
B::x を削除すると、B は標準レイアウト クラスの 9p7 の要件を満たし、アクセスは完全に明確になります。これは、2 つの型がレイアウト互換である 9.2p17 であるためです。
2 つのレイアウト互換列挙型を使用
とがレイアウト互換 (7.2p8)であるにもかかわらず、のアセンブラ出力はではなくをg
返します。1
0
A
B
したがって、私のさらなる質問は(回答を引用して)次のとおりです。「まったく同じレイアウトを持つ2つのクラスは「ほぼ同じ」と見なされ、最適化から除外されます。」. 誰かがGCCまたはClangについてこれを証明できますか?
c - 構造体は、それ自身の最初で唯一のメンバーに別名を付けることができますか?
たとえば、このコードは有効ですか、それともエイリアシング ルールに違反して未定義の動作を引き起こしますか?
私の関心は、これに基づく手法を使用して、エイリアス読み取りを実行するための移植可能な方法を開発することです。
更新: これは意図した使用例です。少し異なりますが、上記が有効な場合にのみ有効です。
必要に応じて、GCC はこれを単一の 32 ビット ロードにコンパイルし、p
実際に 以外の型を指している場合に発生する可能性のあるエイリアシングの問題を回避しているようですchar
。つまり、GNU C__attribute__((__may_alias__))
属性のポータブルな代替品として機能しているようです。しかし、それが本当に明確に定義されているかどうかはわかりません...
c - void* 型キャストは厳密なエイリアシングを破りますか?
次のような動的配列を作成しました。
動的配列は操作でアクセスできます[]
。サイズを変更するときは、__dynarray_header*)array - 1
容量と長さの情報を取得するために使用できます。
このアイデアは小さなテストで機能します。ただし、GCC は、strict-aliasing を壊すことについて警告します。
-fno-strict-aliasing
また、コンパイラ オプションなしで (-O3
最適化ありで)いくつかの大規模なプロジェクトの segfault も見つかりました。
strict-aliasing とは何か、私のコードが strict-aliasing を破る理由を知っています。
[]
私の質問は次のとおりです。操作と動的サイズ変更の両方をサポートする動的配列を実装するより良い方法はありますか?
追加:
この動的配列を使用したデモ プログラム:
c++ - 厳密なエイリアシングは矛盾しているようです
厳密なエイリアシングによるバグがいくつかあったので、それらすべてを修正しようと思いました。それが何であるかを詳細に調べたところ、GCCが警告を発行しない場合があり、実装できないものもあるようです。少なくとも私の理解では、以下のすべてが壊れています。私の理解は間違っていますか、これらすべてのことを行う正しい方法はありますか、それとも技術的にルールを破ってシステムテストで十分にカバーする必要があるコードがありますか?
バグは、以下のように、char バッファーと unsigned char バッファーが混在しているコードに起因していました。
これを以下に変更すると問題が解決したようですが、まだキャストが含まれているため、なぜこれが機能し、警告が表示されないのかわかりません。
また、警告なしで動作するように見える他の場所がたくさんあります
そして、いくつかの...
非チャーケース。これには警告がありません。たとえそれが悪いとしても、どうすれば回避できますか (どちらの方法でもうまくいくようです)。
他の API を見ると、私の理解では、ルールに違反しているさまざまなケースがあるようです (Linux/GCC 固有のものに遭遇したことはありませんが、どこかに必ずあるはずです)。
CoCreateInstance 明示的なポインター キャストを必要とする void** 出力パラメーターがあります。Direct3D にもこのようなものがあります。
LARGE_INTEGER は、さまざまなメンバーに対して読み取り/書き込みを行う可能性が高い共用体です (たとえば、一部のコードは高/低を使用し、他のコードは int64 を読み取る可能性があります)。
CPython の実装は、PyObject* を、たまたま最初に同じメモリ レイアウトを持つ他の多くのものに非常に喜んでキャストしたことを思い出します。
私が見た多くのハッシュ実装は、入力バッファーを uint32_t* にキャストし、おそらく uint8_t を使用して最後に 1 ~ 3 バイトを処理します。
私が見たほとんどすべてのメモリ アロケータの実装では、char* または unsigned char* を使用しています。これらは、目的の型にキャストする必要があります (おそらく、返された void* を介してですが、内部的には少なくとも char でした)。