临时函数参数的生存期

lifetime of a temporary function parameter

本文关键字:生存期 参数 函数      更新时间:2023-10-16

创建一个临时的char缓冲区作为默认函数参数并将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.为什么我打电话给try_stomp()时没有把tmp_buf的记忆扔掉?如何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)

对于问题 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。不,不要这样做!