我相信我在执行O‘Neill的PCG PRNG时发现了GCC的错误.(Initial code on Godbolt's Compiler Explorer)

在将oldstate乘以MULTIPLIER(结果存储在rdi中)后,GCC不会将该结果添加到INCREMENT,而是将INCREMENT移动到rdx,然后将其用作rand32_ret.state的返回值

最小可复制示例(Compiler Explorer):

#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

有趣的是,将 struct 修改为将uint64_t作为第一个成员produces correct code,changing both members to be uint64_t也是如此

x86-64 system V确实会在rdx:rax中返回小于16字节的 struct ,如果它们很容易复制的话.在这种情况下,第二个构件在RDX中,因为Rax的上半部分是用于对齐的填充物,或者当.a是较窄的类型时是.b.(sizeof(retstruct)是16;我们不使用__attribute__((packed)),因此它遵循alignof(Uint64_T)=8.)

Does this code contain any undefined behaviour that would allow GCC to emit the "incorrect" assembly?

如果没有,则应在https://gcc.gnu.org/bugzilla/天内报告

推荐答案

我在这里没有看到任何UB;你的类型没有签名,所以签名溢出是不可能的,也没有什么奇怪的.(即使签名,它也必须为don't导致溢出UB的输入生成正确的输出,比如rdi=1).它也被GCC的C++前端打破了.

此外,GCC8.2将其编译为correctly for AArch64 and RISC-V(在使用movk构造常量后编译为madd指令,或者在加载常量后编译为RISC-V mul和add).如果GCC找到的是UB,我们通常认为它会找到它,并为其他ISA破解您的代码,至少是那些具有相似字体宽度和寄存器宽度的ISA.

Clang也能正确编译它.

这似乎是从GCC 5到6的回归;GCC5.4编译正确,6.1后就不行了.

你可以使用你的问题中的MCVE在GCC's bugzilla上报告这一点.

It really looks like it's a bug in x86-64 System V struct-return handling, perhaps of structs containing padding.这就解释了为什么内联和将a加宽到uint64_t(避免填充)时它会起作用.

C++相关问答推荐

如何在C中通过套接字自定义数据类型读取原始变量?

如何知道我是否从非阻塞套接字读取所有内容

为什么cudaFree不需要数据 struct 的地址?

在C语言中,是否可以使枚举数向后计数?

如何使用[BTStack]BLE发送大型(>;2kb)信息包

如何在STM8项目中导入STM8S/A标准外设库(ST VisualDeveloper)?

每次除以或乘以整数都会得到0.0000

无法在OpenGL上绘制三角形

在C++中父进程和子进程中的TAILQ队列同步问题

如何使用唯一数字对整型进行分区

指向不同类型的指针是否与公共初始序列规则匹配?

C堆栈(使用动态数组)realloc内存泄漏问题

将多项式从文件.txt加载到终端时出现问题

CS50判断灯泡运动的问题,判断时多出一个灯泡,但不在终端上

在运行时判断C/C++指针是否指向只读内存(在Linux操作系统中)

如何在C中定义指向函数的指针并将该指针赋给函数?

&stdbool.h&q;在嵌入式系统中的使用

Matlab/Octave对conv2函数使用哪种方法?

如何找出C中分配在堆上的数组的大小?

子进程不会修改父进程中的统计信息