为啥从 constexpr 引用生成的汇编代码与 constexpr 指针不同?

Posted

技术标签:

【中文标题】为啥从 constexpr 引用生成的汇编代码与 constexpr 指针不同?【英文标题】:Why is different assembly code generated from a constexpr reference than a constexpr pointer?为什么从 constexpr 引用生成的汇编代码与 constexpr 指针不同? 【发布时间】:2020-09-08 07:17:22 【问题描述】:

我用 MSVC /O2 和 clang 编译了以下程序:

int            i;
constexpr int& ir = i;
constexpr int* ip = &i;


int main()

    ir  = 1;
    *ip = 2;

MSVC /O2                           clang
===========================        =============================================
int i DD 01H DUP (?) ; i           i:
_DATA SEGMENT                              .long   0                       # 0x0
int & ir DQ FLAT:int i ; ir
_DATA ENDS                         ir:
                                           .quad   i
两个编译器都从ir 生成汇编代码,但不是从ip 生成的(上面省略了main 的代码)。请参阅godbolt。为什么irip 不同?我了解到,汇编代码中的引用和指针是相同的。

Gcc 做了一件更奇怪的事情。我用 gcc -O0-O2 编译了以下程序:

int            i;

constexpr int& ir1   = i;
constexpr int* ir1p1 = &i;

constexpr int& ir2   = i;
constexpr int* ir2p1 = &i;
constexpr int* ir2p2 = &i;

constexpr int& ir3   = i;
constexpr int* ir3p1 = &i;
constexpr int* ir3p2 = &i;
constexpr int* ir3p3 = &i;
gcc -O0            gcc -O2
=========          ==================
i:                 ir3:
  .zero 4                   .quad   i
ir1:               ir2:
  .quad i                   .quad   i
  .quad i          ir1:
ir2:                        .quad   i
  .quad i          i:
  .quad i                   .zero   4
  .quad i
ir3:
  .quad i
  .quad i
  .quad i
  .quad i
有了-O0,为什么引用变量下面会有几个.quad i-O2 代码是从引用生成的,而不是从指针生成的。为什么会有这种差异?请参阅godbolt。

【问题讨论】:

你为什么期望相同的代码?你认为指针和引用是一回事吗? @StephenM.Webb:我认为也有相似之处和不同之处,但在汇编代码中我从未看到它们之间有任何区别。因此,问题出现了:当前的差异是必要的还是只是在更复杂的实现中的疏忽? 不同的编译器生成不同的代码; 11 点的新闻。如果有多种方法可以生成导致相同结果的代码,C++ 标准将其留给实现来选择一种。对于将生成哪些实际指令,您没有保证,只有它们将实现原始源指定的行为(当然,除非存在未定义的行为,那么所有的赌注都没有了)。 在我看来,这确实像是编译器优化失败。可能是因为 constexpr 引用并没有真正意义,而且应该很少见。 "使用 -O0 为什么引用变量下有几个 .quad i ?" - 也许它使调试器更容易?毕竟,这是-O0 最关心的问题。 【参考方案1】:

除了指令问题,这只是链接constexpr 暗示const(在变量本身上,而不是在其所指对象上,如果有的话),从而将internal linkage 提供给您的指针。 (这是一个 hack,允许在 C++17 的内联变量之前的头文件中使用命名空间范围命名的常量;不幸的是,我们现在必须记住它。)由于没有其他翻译单元可以引用它们,因此无需发出符号;但是,这些引用可以在其他地方声明和使用:

extern int &ir;
void count() ++ir;

因此必须有一个符号来附加这些用途。

【讨论】:

extern int& ir 没有链接到constexpr int& ir = i;。我得到了未解决的外部符号。所以我认为,后者毕竟是有内在联系的。不能在其他翻译单元中使用。 @Dr.Gut:它works for me。 我很惊讶,它适用于 gcc。它不适用于 MSVC 和 clang。我不明白,为什么。 确实有效。当我切换编译器时,我忘记在编译中添加def.cc。如何创建指向魔杖盒程序的链接? @Dr.Gut:当你运行它时,一个共享链接会出现在输出窗口的正上方。

以上是关于为啥从 constexpr 引用生成的汇编代码与 constexpr 指针不同?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我不能使用 constexpr 全局变量来初始化 constexpr 引用类型?

为啥 Solaris 汇编器生成的机器代码与这里的 GNU 汇编器不同?

为啥包装在函数中的 GAS 内联汇编为调用者生成的指令与纯汇编函数不同

对静态 constexpr 数据成员的未定义引用错误

为啥 NVCC 对 constexpr 比非 constexpr 主机函数更严格?

为啥 MSVC 不在生成的汇编代码中分配 32 字节的影子空间?