【问题标题】:'Lucky' valid pointer data to returned local map data?'幸运' 指向返回的本地地图数据的有效指针数据?
【发布时间】:2014-08-06 17:47:41
【问题描述】:

在我的 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 仍然有效,尽管我怀疑它会变得无效,因为函数内部的“指向对象”是其元素的映射消失了范围。

这与Can a local variable's memory be accessed outside its scope?中提到的基本相同吗?我基本上'幸运'指针仍然指向有效数据?还是发生了一些不同的事情?

我确实认为每次都是“幸运”命中,但如果有一些确认会非常好。

【问题讨论】:

标签: c++


【解决方案1】:

是的,你很“幸运”(没那么多,你的程序迟早会崩溃或以某种方式做坏事)。


解释

returnMap在栈上分配:mapReturningFunction返回时销毁。

您获取地图内对象的地址并将其分配为other 指针。

当您的函数返回时,returnMap 被复制到返回值,因此(复制的)指针现在真正指向垃圾。

优化器通常会避免最后一次复制,它被称为“复制省略”(或"Return Value Optimization"),可能是您“正常”行为的原因。但没关系,您的程序有未定义的行为

【讨论】:

    【解决方案2】:

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

    这是一个例子,我也从你链接的另一个问题中借用了这个想法,这完全一样:

    #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 分配空间。因为函数非常相似,并且两个函数中的变量大小相同,所以它们的内存位置恰好重叠。

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

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

    【讨论】:

    • "您正在返回一个指向堆栈上某个位置的指针。"不,他从堆栈中返回一个映射,而不是一个指针。源映射包含指向堆上元素的指针。当源映射被销毁时,这些元素也会被销毁,从而导致对它们占用的内存的访问是非法的。因此,复制的映射包含指向非法内存的指针。 void main 不是 C++,我认识的每一个现代编译器都应该拒绝它。
    • 是的,我措辞错误。在我的示例中,指针被返回。在原始问题中,指向映射的指针被存储,它仍然指向堆栈上的相同内存位置。即使映射被破坏,内存通常不会在析构函数中被覆盖,并且在被覆盖之前包含相同的值。
    【解决方案3】:

    是的,这只是“幸运”。通过保留指向已被破坏的地图元素的指针,您将获得未定义的行为。这与保留指向本地地图本身的指针并不完全相同,因为地图的元素是动态分配的,但在这里归结为相同的结果。

    在这里实际执行崩溃或其他“错误”行为并不容易。这大概是由于返回值优化,即地图的副本被省略,所以源没有被覆盖。

    但以下内容在 VC++ 2013 中对我有用:

    在您的mapReturnFunction 中,将return 语句更改为

    return true ? returnMap : returnMap;
    

    尽管这可能很奇怪,但此技巧将禁用返回值优化,从而生成实际副本。使用/EHsc /Za 编译(尽管这两个标志在这里都不应该真正产生影响),我的机器上的结果是 nothing 被打印。

    通过一个不应该产生影响的陈述,可观察到的行为发生了如此巨大的变化,这一事实给你一个强烈的暗示,表明某些事情是非常错误的。

    【讨论】:

      【解决方案4】:

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

      请注意,一旦您复制(不移动)并且原件被破坏,指向其他元素的指针无效!

      【讨论】:

        【解决方案5】:

        Pre C++11,你很“幸运”。由于复制省略,代码可能会起作用,特别是NRVO(命名返回值优化)意味着returnMapreturnedMap 实际上是同一个对象。但是,您不能相信这一点,因此代码会调用未定义的行为

        发布 C++11,你很“幸运”,但没那么幸运。该地图有一个移动构造函数,return returnMap; 隐式移动本地 returnMap 作为 prvalue,然后可用于returnedMap 的移动构造函数。编译器仍然可以忽略这一点,但您可以依赖被移动的本地值。但是,该标准并未保证容器移动时究竟会发生什么,因此您仍会调用未定义的行为,但一旦解决了LWG open issue 2321,这种情况可能会发生变化。

        【讨论】:

          猜你喜欢
          • 2010-12-01
          • 2012-04-12
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-04-03
          • 2014-08-02
          • 2012-10-02
          相关资源
          最近更新 更多