在我的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
仍然有效,即使我怀疑它会变得无效,因为函数中的映射是指向对象&#39 ;是一个超出范围的元素。
这与Can a local variable's memory be accessed outside its scope?中提到的基本相同吗?我基本上是幸运的&#39;指针仍然指向有效数据?或者是不同的事情?
我确实认为这是一个幸运的&#39;每次都打,但有些确认会非常好。
答案 0 :(得分:3)
是的,你是“幸运的”(不是那么多,你的程序迟早会以某种方式崩溃或做坏事)。
<强>解释强>:
returnMap
在堆栈上分配:当mapReturningFunction
返回时,它将被销毁。
您获取地图内对象的地址,并将其指定为other
指针。
当你的函数返回时,returnMap
被复制到返回的值,所以(复制的)指针现在真的指向垃圾。
优化工具通常会避免使用此最后一个副本,其名为“ copy elision ”(或"Return Value Optimization")和可能是您“正常”行为的原因。但是没关系,你的程序有不明确的行为。
答案 1 :(得分:1)
这不是运气。您正在返回指向堆栈上某个位置的指针。该位置是有效的,它恰好具有放置在那里的最后一个值,直到其他东西改变它为止。
这是一个例子,我从你链接的其他问题中借用了这个想法,这是完全相同的:
#include <stdio.h>
int* foo()
{
int a = 5;
return &a;
}
void nukestack()
{
int a = 7;
printf("putting 7 on the stack\n");
}
void main()
{
int* p = foo();
printf("%d\n", *p);
nukestack();
printf("%d\n", *p);
}
程序的打印将是:
5
putting 7 on the stack
7
原因是这个。我们首先调用foo(),它在堆栈上为变量a分配空间。我们将5写入此位置,然后从函数返回,释放该堆栈空间,但保持内存不变。然后我们调用nukestack(),它在堆栈上为其自己的变量a分配空间。因为函数非常相似,并且两个函数中的变量大小相同,所以它们的内存位置恰好重叠。
此时新变量仍将具有旧值。但是我们现在用7覆盖5。我们从函数返回,我们的旧指针p仍然指向同一个位置,现在它有7个。
这是未定义的行为,如果你依赖它,你在技术上违反规则。对于大多数编译器,当您返回指向局部变量的指针时,您也会收到警告,并且警告应该永远不会被忽略。
答案 2 :(得分:1)
是的,它只是&#34;幸运&#34;。通过保持指向已销毁的地图元素的指针,您将获得未定义的行为。这与保持指向本地地图本身的指针并不完全相同,因为地图的元素是动态分配的,但在此处归结为相同的结果。
实际执行崩溃或其他错误&#34;并不容易。这里的行为。这可能是由于返回值优化,即地图的副本被省略,因此源不会被覆盖。
但以下内容适用于VC ++ 2013:
在mapReturnFunction
中,将return语句更改为
return true ? returnMap : returnMap;
尽管这可能很奇怪,但这个技巧会禁用返回值优化,从而导致实际复制。用/EHsc /Za
编译(虽然这两个标志都不会在这里产生影响),但我机器上的结果是打印 nothing 。
可观察行为通过一个应该没有区别的陈述如此剧烈地改变的事实让你强烈暗示某些事情是非常错误的。
答案 3 :(得分:1)
你不幸运。复制elision或移动构造函数(C ++ 11)将防止破坏原始数据。虽然第一个是可选的编译器优化,但第二个是(如果第一个不适用)创建原始的移动版本而不会使引用无效。
请注意,一旦你复制(没有移动)并且原件被销毁,指向其他元素的指针就无效了!
答案 4 :(得分:1)
Pre C ++ 11 ,你是“幸运的”。代码可能会因复制省略而生效,特别是 NRVO (命名返回值优化)意味着returnMap
和returnedMap
实际上是同一个对象。但是,您不能相信这一点,因此代码会调用未定义的行为。
发布C ++ 11 ,你是“幸运的”,但不那么幸运。地图有一个移动构造函数,return returnMap;
隐式将局部returnMap
移动为 prvalue ,然后可用于returnedMap
的移动构造函数。编译器仍然可以忽略这一点,但允许您依赖于移动的本地值。但是,该标准没有给出移动容器时确切发生的事情的保证,因此您仍然会调用未定义的行为,但这可能会在LWG open issue 2321解决后发生更改。