临时函数参数的生命周期

时间:2014-11-20 23:00:04

标签: c++ undefined-behavior temporary

创建临时字符缓冲区作为默认函数参数并将 r值引用绑定到它允许我们在单行,同时防止需要在堆上创建存储。

const char* foo(int id, tmp_buf&& buf = tmp_buf()) // buf exists at call-site

将引用/指针绑定到临时缓冲区并稍后访问它会产生未定义的行为,因为临时缓冲区不再存在。

从下面的示例应用程序可以看出,tmp_buf的析构函数在第一个输出之后和第二个输出之前被调用。

我的编译器(gcc-4.8.2)没有警告我将变量绑定到临时变量。这意味着使用这种微优化来使用自动字符缓冲区而不是std::string与相关的堆分配是非常危险的。

其他人进入并捕获返回的const char*可能会无意中引入错误。

1。有没有办法让编译器警告下面的第二种情况(捕获临时情况)?

有趣的是,你可以看到我试图使缓冲区无效 - 我没有做到,所以它可能表明我不完全理解堆栈tmp_buf的创建位置。

2。当我拨打tmp_buf时,为什么我不会在try_stomp()中删除内存?如何废弃tmp_buf

第3。或者 - 以我展示的方式使用是否安全? (我不希望这是真的!)

#include <iostream>

struct tmp_buf
{
    char arr[24];
    ~tmp_buf() { std::cout << " [~] "; }
};

const char* foo(int id, tmp_buf&& buf = tmp_buf())
{
    sprintf(buf.arr, "foo(%X)", id);
    return buf.arr;
}

void try_stomp()
{
    double d = 22./7.;
    char buf[32];
    snprintf(buf, sizeof(buf), "pi=%lf", d);
    std::cout << "\n" << buf << "\n";
}

int main()
{
    std::cout << "at call site: " << foo(123456789);
    std::cout << "\n";

    std::cout << "after call site: ";
    const char* p = foo(123456789);

    try_stomp();

    std::cout << p << "\n";
    return 0;
}

输出:

at call site: foo(75BCD15) [~] 
after call site:  [~] 
pi=3.142857
foo(75BCD15)

1 个答案:

答案 0 :(得分:1)

问题2。

你没有删除变量的原因是编译可能在函数调用开始时分配了它所需的所有堆栈空间。这包括临时对象的所有堆栈空间,以及在嵌套范围内声明的对象。您不能保证编译器会这样做(我认为),而不是根据需要在堆栈上推送对象,但是通过这种方式跟踪堆栈变量的位置会更有效,更容易。

当您调用try_stomp函数时,该函数会在main函数的堆栈之后(或之前,取决于您的系统)分配其堆栈。

请注意,函数调用的默认变量实际上是通过编译到调用代码,而不是被调用函数的一部分(这就是为什么需要成为函数声明的一部分,而不是定义,如果它被单独宣布)。

所以你在try_stomp中的堆栈看起来像这样(堆栈中还有很多东西,但这些是相关的部分):

main       - p
main       - temp1
main       - temp2
try_stomp  - d
try_stomp  - buf

因此,您无法将try_stomp的临时文件丢弃,至少在没有真正离谱的情况下也是如此。

同样,你不能依赖这种布局,因为它依赖于编译,而且只是编译器如何做到的例子。

删除临时缓冲区的方法是在tmp_buf的析构函数中执行此操作。

同样有趣的是,MSVC似乎为所有临时对象分别分配堆栈空间,而不是为两个对象重用堆栈空间。这意味着即使重复拨打foo也不会互相捣乱。同样,你不能依赖这种行为(我想 - 我无法找到它的参考)。

问题3。 不,不要这样做!