在旧的 C++ 编译器的 `* const&` 中引入临时的令人费解的介绍

Puzzling introduction of temporary in `* const&` on older C++ compilers

在使用较旧的 C++ 编译器的平台上构建时,我注意到在其他地方运行良好的代码出现意外行为。我不确定这是否表示旧编译器中的错误,或者标准中的某些更改,但我一直在明确地使用 C++11 进行构建。

特别是,较旧的编译器似乎在 * const& 中使用临时文件,而我不希望它这样做,并且当其堆栈帧被清理和覆盖时,该临时文件会导致 SIGSEGV。

这是我从原始代码中提取 MWE 的最大努力。我的问题围绕 class C:

的构造函数
#include <stdio.h>

struct A {
    int* i;
};

class B {
public:
    int* const& i_ptr_const_ref; // normally not public
    B(int* const& i_ptr) : i_ptr_const_ref(i_ptr) {}
    operator int* const& (void) const { return i_ptr_const_ref; }
};

int null_b = { 0 };
int* null_b_ptr = &null_b;
int* const& null_b_ptr_const_ref = null_b_ptr;

class C {
public:
    B b;
//  C(const A* a_ptr) : b(a_ptr ? a_ptr->i : null_b_ptr_const_ref) {} // this works
    C(A* a_ptr) : b(a_ptr ? a_ptr->i : null_b_ptr_const_ref) {} // this fails
//  C(A* a_ptr) : b(a_ptr ? (int* const&) a_ptr->i : null_b_ptr_const_ref) {} // this works
//  C(A* a_ptr) : b(a_ptr->i) {} // this works
};

int main(void) {
    A a;
    A* a_ptr = &a;
    a_ptr->i = (int*) 42;
    C c(a_ptr);
    printf("c.b.i_ptr_const_ref = %p\n", (void*) c.b.i_ptr_const_ref);
    printf("c.b=                  %p\n", (void*) c.b);
    printf("a_ptr->i=             %p\n", (void*) a_ptr->i);
    return 0;
}

Try it on Compiler Explorer

我打印出来的值应该都匹配,但是在 5.1 之前的 GCC 编译器和 18 之前的 ICC 编译器上(我知道英特尔以 "bug-for-bug" 与其他编译器的兼容性而自豪),中间那个显示堆栈地址而不是预期值。我能够尝试正确运行的所有版本的 Clang 和 ELLCC 编译器。

未注释的 C 构造函数是我想使用的构造函数,但它不能正常工作。如果我将 A* a_ptr 参数设置为 const,我会在 MWE 中得到预期的结果,但在更大的代码库中我不能这样做。如果我在初始化程序中不使用 ?:,或者如果我在初始化程序中明确地将 a_ptr->i 转换为 int* const&,我也会得到预期的结果,但我不明白为什么我应该有。

我原以为用 int* 初始化 int* const& 会很好,但我最好的猜测是 ?: 以某种方式混淆了编译器。任何人都可以帮助我了解旧的 GCC 和 ICC 编译器在这里是否不正确,或者我是否误解了语言?

这很明显是一个编译器错误:int* 类型的左值当然可以隐式转换为 int* const&,因此条件运算符的结果应该是一个左值。

printf 输出看起来很神秘,但实际上是错误的直接结果:即使 first 读取 c.b.i_ptr_const_ref(这是当然是透明遵循的参考)正在从死的临时读取,但它尚未被覆盖并且仍然包含 copy of a.i。在第一个 printf 之后,该内存已被破坏并恰好保存了一个堆栈地址。