为什么临时取rvalue的地址是违法的?

Lig*_*ica 24 c++

根据" 如何绕过警告"rvalue用作左值"? ",Visual Studio只会警告代码如下:

int bar() {
   return 3;
}

void foo(int* ptr) {

}

int main() {
   foo(&bar());
}
Run Code Online (Sandbox Code Playgroud)

在C++中,不允许获取临时(或者至少是由rvalue表达式引用的对象?)的地址,我认为这是因为临时保证甚至不能保存.

但是,虽然诊断程序可能以编译器选择的任何形式呈现,但我仍然期望MSVS 出错而不是在这种情况下发出警告.

那么,临时保证有储存吗?如果是这样,为什么上面的代码首先被禁止?

Com*_*sMS 46

实际上,在原始语言设计中,它允许采用临时的地址.正如您已经注意到的那样,没有技术原因不允许这样做,MSVC今天仍然允许通过非标准语言扩展.

C++使其成为非法的原因是对temporaries的绑定引用与从C:Implicit类型转换继承的另一个C++语言特性冲突.考虑:

void CalculateStuff(long& out_param) {
    long result;
    // [...] complicated calculations
    out_param = result;
}

int stuff;
CalculateStuff(stuff);  //< this won't compile in ISO C++
Run Code Online (Sandbox Code Playgroud)

CalculateStuff()应该通过输出参数返回其结果.但真正发生的是:函数接受一个long&但是给出了一个类型的参数int.到C的隐式类型转换,即int现在隐式转换为类型的变量long,创建一个无名临时在过程中.因此stuff,该函数实际上是在一个未命名的临时函数上运行而不是变量,并且一旦临时销毁,该函数应用的所有副作用都将丢失.变量的值stuff永远不会改变.

向C++引入了引用以允许运算符重载,因为从调用者的角度来看,它们在语法上与按值调用相同(而不是指针调用,它需要&在调用者方面显式).不幸的是,当与C的隐式类型转换结合时,语法等价会导致麻烦.

由于Stroustrup想要保留这两个特性(引用和C兼容性),他引入了我们今天都知道的规则:未命名的临时代码只绑定到const引用.使用该附加规则,上述示例不再编译.由于问题仅在函数将副作用应用于引用参数时才会发生,因此将未命名的临时值绑定到const引用仍然是安全的,因此仍然允许这样做.

整个故事也在C++的设计和演变的第3.7章中描述:

允许通过非左值初始化引用的原因是允许按值调用和按引用调用之间的区别是被调用函数指定的细节,并且对调用者不感兴趣.作为const参考,这是可能的; 对于non-const参考,它不是.对于版本2.0,更改了C++的定义以反映这一点.

我还隐约记得在一篇首次发现这种行为的论文中读书,但我现在不记得了.也许有人可以帮助我?


bdo*_*lan 10

当然临时拥有存储空间.你可以这样做:

template<typename T>
const T *get_temporary_address(const T &x) {
    return &x;
}

int bar() { return 42; }

int main() {
    std::cout << (const void *)get_temporary_address(bar()) << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

在C++ 11中,您也可以使用非const rvalue引用执行此操作:

template<typename T>
T *get_temporary_address(T &&x) {
    return &x;
}

int bar() { return 42; }

int main() {
    std::cout << (const void *)get_temporary_address(bar()) << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

当然,请注意,取消引用有问题的指针(在get_temporary_address其自身之外)是一个非常糟糕的主意; 临时只存在于完整表达式的末尾,所以有一个指向它的指针逃脱表达式几乎总是一个灾难的秘诀.

此外,请注意,不需要编译器来拒绝无效程序.C和C++标准仅仅要求诊断(即错误警告),编译器可以拒绝该程序,或者可以在运行时使用未定义的行为编译程序.如果您希望编译器严格拒绝产生诊断的程序,请将其配置为将警告转换为错误.

  • 请注意,您非常接近未定义的行为.`bar()`引入的临时只会持续到完整表达式结束,在这种情况下可能不完全清楚:`std :: cout.operator <<((const void*)get_temporary_address(bar() ))`. (2认同)

Geo*_*sov 8

你说"暂时不能保证存储空间"是正确的,因为临时存储器可能没有存储在可寻址存储器中.实际上,通常为RISC体系结构(例如ARM)编译的函数将返回通用寄存器中的值,并且还期望这些寄存器中的输入.

为x86体系结构生成代码的MSVS可能总是生成在堆栈上返回其值的函数.因此,它们存储在可寻址存储器中并具有有效地址.