返回的本地地图数据'Lucky'有效的指针数据?

'Lucky' valid pointer data to returned local map data?

本文关键字:数据 有效 指针 Lucky 地图 返回      更新时间:2023-10-16

在我的c++程序中,我有一个函数,它返回一个包含元素的映射,每个元素都可以有指向映射中另一个元素的指针。我在函数结束时返回映射之前设置了这些指针。示例代码:

#include <iostream>
#include <map>
#include <string>
class TestObject{
public:
    TestObject(std::string message) : message(message), other(nullptr){}
    TestObject* other;
    std::string message;
};
std::map<std::string, TestObject> mapReturningFunction(){
    std::map<std::string, TestObject> returnMap;
    TestObject firstObject("I'm the first message!");
    returnMap.insert(std::make_pair("first", firstObject));
    TestObject secondObject("I'm the second message!");
    returnMap.insert(std::make_pair("second", secondObject));
    TestObject* secondObjectPointer = &(returnMap.at("second"));
    returnMap.at("first").other = secondObjectPointer;
    return returnMap;
}
int main(){
    std::map<std::string, TestObject> returnedMap = mapReturningFunction();
    std::cout << returnedMap.at("first").other->message << std::endl; // Gives a valid message every time
    std::cin.get();
    return 0;
}

在函数的调用点,指针other仍然有效,尽管我怀疑它会因为函数内部的映射("指向对象"是其元素)超出作用域而失效。

这与在局部变量的内存可以在其作用域外访问中提到的基本相同吗?? 我基本上是"幸运"的指针仍然指向有效的数据?或者发生了什么不同的事情?

我真的认为它每次都是"幸运的"命中,但一些确认将是非常好的。

是的,你很"幸运"(不是很幸运,你的程序迟早会崩溃或以某种方式做不好的事情)。


:

returnMap被分配在堆栈上:当mapReturningFunction返回时,它将被销毁。

在映射中获取对象的地址,并将其赋值为other指针。

当你的函数返回时,returnMap被复制到返回值,所以(复制的)指针现在真的指向垃圾。

优化器经常避免最后一次复制,它被称为"复制省略"(或"返回值优化"),可能是您的"正常"行为的原因。但是没关系,你的程序有未定义的行为

这不是运气。你正在返回一个指向堆栈上某个位置的指针。该位置是有效的,并且它恰好具有最后放置在那里的值,直到其他内容更改它。

这里有一个例子,我借用了你链接的另一个问题的想法,这是完全相同的:

#include <stdio.h>
int* foo()
{
    int a = 5;
    return &a;
}
void nukestack()
{
    int a = 7;
    printf("putting 7 on the stackn");
}
void main()
{
    int* p = foo();
    printf("%dn", *p);
    nukestack();
    printf("%dn", *p);
}

程序将打印如下:

5
putting 7 on the stack
7

原因如下。我们首先调用foo(),它在堆栈上为变量a分配空间。我们将5写入这个位置,然后从函数返回,释放堆栈空间,但内存保持不变。然后调用nukestack(),它在堆栈上为自己的变量a分配空间。由于两个函数非常相似,并且两个函数中的变量大小相同,因此它们的内存位置恰好重叠。

此时,新的a变量仍然具有旧的值。但是我们现在用7覆盖了5。当从函数返回时,原来的指针p仍然指向同一个位置,这里现在有一个7。

这是未定义的行为,如果你依赖它,从技术上讲你违反了规则。对于大多数编译器,当您返回指向局部变量的指针时,您还会得到一个警告,并且警告确实不应该被忽略。

是的,只有"幸运"。通过保留指向已被销毁的映射元素的指针,您将获得未定义的行为。这与保持一个指向局部映射本身的指针并不完全相同,因为映射的元素是动态分配的,但在这里得到的结果是相同的。

在这里强制执行崩溃或其他"错误"行为并不那么容易。这可能是由于返回值优化,即映射的副本被省略,因此源不会被覆盖。

但以下工作对我在vc++ 2013:

mapReturnFunction中,将返回语句更改为

return true ? returnMap : returnMap;

虽然这可能很奇怪,但这个技巧将禁用返回值优化,导致实际复制。用/EHsc /Za编译(尽管这两个标志在这里没有什么区别),在我的机器上的结果是没有打印。

事实上,可观察到的行为通过一个应该没有什么区别的语句发生了如此剧烈的变化,这给了你一个强烈的暗示,表明某些事情是非常错误的。

你运气不好。复制省略或移动构造函数(c++ 11)将防止原始数据的破坏。虽然第一个是可选的编译器优化,但第二个将(如果第一个不适用)创建原始版本的移动版本,而不会使引用无效。

请注意,一旦你创建了一个副本(没有移动),并且原始元素被销毁,指向其他元素的指针就无效了!

Pre c++ 11 ,你是"幸运"。代码可能会工作,因为复制省略,特别是NRVO(命名返回值优化)意味着returnMapreturnedMap实际上是同一个对象。但是,您不允许信任它,因此代码调用未定义行为

Post c++ 11,你是"幸运的",但不那么幸运。映射有一个移动构造函数,return returnMap;隐式地将本地returnMap移动为prvalue,然后可用于returnedMap的移动构造函数。编译器仍然可以忽略这一点,但您可以依赖于移动的局部值。然而,该标准并没有保证容器移动时究竟发生了什么,因此您仍然调用未定义行为,但是一旦LWG开放问题2321得到解决,情况可能会发生变化。