[解決済み] 関数から構造体を返す際のGCCバグの可能性
質問
GCCでO'NeillのPCG PRNGを実装しているときにバグを見つけたと思うのですが、これは何ですか?( Godbolt のコンパイラエクスプローラでの初期コード )
を掛けた後
oldstate
で
MULTIPLIER
で、(結果はrdiに格納されます)、GCCはその結果を
INCREMENT
に追加せず、movabs'ing
INCREMENT
をrdxに移動し、rand32_ret.stateの戻り値として使用されます。
再現性のある最小限の例 ( コンパイラエクスプローラ ):
#include <stdint.h>
struct retstruct {
uint32_t a;
uint64_t b;
};
struct retstruct fn(uint64_t input)
{
struct retstruct ret;
ret.a = 0;
ret.b = input * 11111111111 + 111111111111;
return ret;
}
生成されたアセンブリ (GCC 9.2, x86_64, -O3):
fn:
movabs rdx, 11111111111 # multiplier constant (doesn't fit in imm32)
xor eax, eax # ret.a = 0
imul rdi, rdx
movabs rdx, 111111111111 # add constant; one more 1 than multiplier
# missing add rdx, rdi # ret.b=... that we get with clang or older gcc
ret
# returns RDX:RAX = constant 111111111111 : 0
# independent of input RDI, and not using the imul result it just computed
興味深いことに、uint64_t を最初のメンバーとして持つように構造体を変更すると は正しいコードを生成します。 と同じように のように、両方のメンバを uint64_t に変更します。
x86-64 System V では、16 バイトより小さい構造体がコピー可能な場合、RDX:RAX で返されます。 この場合、RAX の上位半分がアライメントのためのパディングであるため、2 番目のメンバは RDX にあります。
.b
の場合
.a
は狭義のタイプです。 (
sizeof(retstruct)
はどちらにしても16です。
__attribute__((packed))
を使用していないので、alignof(uint64_t) = 8 を尊重しています)。
このコードはGCCが"incorrect"アセンブリを出すことを可能にするどんな未定義の行動も含んでいますか?
もしそうでなければ、これは https://gcc.gnu.org/bugzilla/
どのように解決するのですか?
あなたの型は符号なしなので、符号ありオーバーフローのUBは不可能であり、何も奇妙なことはありません。 (そして、たとえ符号付きであっても、以下のような入力に対して正しい出力を生成する必要があります。
でない入力に対して正しい出力を生成しなければなりません。
のようなオーバーフローUBを引き起こす入力に対して正しい出力を生成しなければなりません。
rdi=1
). これは、GCCのC++フロントエンドでも同様に壊れています。
また、GCC8.2はこれをコンパイルします。
は AArch64 と RISC-V に対して正しくコンパイルされます。
(そして
madd
を使用した後に
movk
を使って定数を構築した後、あるいは、RISC-Vのmulと定数をロードした後に追加してください)。 GCCが発見したのがUBであった場合、一般的には、他のISA、少なくとも同様の型幅とレジスタ幅を持つものについても、それを発見してコードを破壊することが予想されます。
Clang もまた、それを正しくコンパイルします。
これはGCC 5から6へのリグレッションのようです。GCC5.4は正しくコンパイルされますが、6.1以降ではされません。 ( ゴッドボルト ).
で報告することができます。 GCCのバグジラ で、あなたの質問のMCVEを使って報告してください。
それは本当に x86-64 System V の構造体の戻り値の処理のバグのように見えますが、おそらくパディングを含む構造体のものです。
それはなぜインライン化したときに動作し、ワイド化したときに動作するかを説明します。
a
を uint64_t (パディングを避ける) に広げるときに動作する理由を説明します。
関連
-
[解決済み] GCCで「文字列定数から'char*'`への非推奨の変換」という警告を消すにはどうしたらいいですか?
-
[解決済み] SQLiteのINSERT/per-secondのパフォーマンスを向上させる
-
[解決済み] プログラム終了前にmallocの後にfreeをしないと本当に何が起こるのか?
-
[解決済み] C言語のi++と++iに性能差はあるのでしょうか?
-
[解決済み] 「GCC使用時に「Xcode/iOSのライセンスに同意するには管理者権限が必要です。rootでsudoを使用して再実行してください。
-
[解決済み] gccでC/C++のソースからアセンブラ出力を得るにはどうしたらいいですか?
-
[解決済み] LD_PRELOADのトリックとは何ですか?
-
[解決済み】C言語で関数から `struct` を返します。
-
[解決済み] gccコマンドラインを使用して.cファイルから.soファイルをビルドする。
-
[解決済み] ある関数をインライン化しないように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 実装 サイバーパンク風ボタン
おすすめ
-
赤線の位置は必ずしも間違っていない:式は変更可能なlvalueでなければならないエラーは、この文とは別の場所に存在する可能性があります。
-
警告:代入がキャストなしで整数からポインタを作成する場合の修正方法に関する警告
-
error: 'for' loop initial declaration is only allowed in C99 mode 原因と解決方法
-
C/C++の再定義
-
[解決済み] Valgrind が初期化されていないバイトについて警告する
-
[解決済み] なぜmemsetではなくbzeroを使用するのですか?
-
[解決済み] 配列のすべてのメンバーを同じ値で初期化するには?
-
[解決済み] C言語で関数をパラメータとして渡すにはどうすればよいですか?
-
[解決済み] フリーは、どのように無料化を知っているのですか?
-
[解決済み] ストラクチャーとユニオンの違い