【问题标题】:Can a unique_ptr be used with a negative index without leaking memory?可以将 unique_ptr 与负索引一起使用而不会泄漏内存吗?
【发布时间】:2012-10-18 11:12:11
【问题描述】:

我阅读了Are negative array indexes allowed in C?,发现可以将负值用于数组的索引很有趣。我用 c++11 unique_ptr 再次尝试了它,它也可以在那里工作!当然删除器必须替换为可以删除原始数组的东西。这是它的样子:

#include <iostream>
#include <memory>

int main()
{
    const int min = -23; // the smaller valid index
    const int max = -21; // the highest valid index
    const auto deleter = [min](char* p)
    {
        delete [](p+min);
    };
    std::unique_ptr<char[],decltype(deleter)> up(new char[max-min+1] - min, deleter);

    // this works as expected
    up[-23] = 'h'; up[-22] = 'i'; up[-21] = 0;
    std::cout << (up.get()-23) << '\n'; // outputs:hi
}

我想知道内存泄漏的可能性是否非常非常小。在堆上创建的内存地址 (new char[max-min+1]) 在向其添加 23 时可能会溢出并成为空指针。减去 23 仍会产生数组的原始地址,但 unique_ptr 可能会将其识别为空指针。 unique_ptr 可能不会删除它,因为它为空。

那么,之前的代码是否有可能会泄漏内存,或者智能指针的行为方式是否安全?

注意:我实际上不会在实际代码中使用它;我只是对它的行为方式感兴趣。

【问题讨论】:

    标签: c++ memory-leaks c++11 smart-pointers


    【解决方案1】:

    编辑:icepack 提出了一个有趣的观点,即在指针算术中只允许两个有效的指针值:

    §5.7 [expr.add] p5

    如果指针操作数和结果都指向同一个数组对象的元素,或者超过数组对象的最后一个元素,则求值不会产生溢出; 否则,行为未定义

    因此,您的代码的 new char[N] - min 已经调用了 UB。


    现在,在大多数实现中,这不会导致问题。但是,std::unique_ptr 的析构函数将(从这里开始预编辑答案):

    §20.7.1.2.2 [unique.ptr.single.dtor] p2

    效果:如果get() == nullptr 没有效果。否则get_deleter()(get())

    所以是的,如果内存确实映射到表示空指针值的任何值(很可能是0,但不一定),那么您可能会在这里泄漏内存。是的,我知道这是针对单个对象的,但数组的行为完全相同:

    §20.7.1.3 [unique.ptr.runtime] p2

    下面仅针对行为不同于主模板的成员函数提供描述。

    并且没有关于析构函数的描述。

    【讨论】:

    • sn-p 中的删除器实际上尝试访问指向已分配数组开头的正确指针,因此应该没有问题。但是,还有另一个问题 - 请参阅我的回答。
    • @icepack:如果get() == nullptr,甚至不会调用删除器。
    • 为了使其为空,unique_ptr 必须没有托管对象的所有权,而这里不是这种情况。
    • @icepack:如果new char[N] - min 产生0,那就是。
    • 不确定。这实际上取决于unique_ptr 的实现——就unique_ptr 而言,所有权很可能仍然有效。例如,这可能意味着拥有一个从地址 0 开始的对象(此外,实际上我认为没有现代计算机架构允许将地址 23 作为指向已分配内存的有效指针返回)
    【解决方案2】:

    new char[max-min+1] 不在堆栈上分配内存,而是在堆上 - 这就是标准 operator new 的行为方式。表达式max-min+1 由编译器求值并产生3,所以最终这个表达式等于在堆上分配3 个字节。这里没问题。

    但是,减去min 会导致指针超出new 返回的分配内存的开头 23 个字节,并且由于在 new 中您只分配了 3 个字节,这肯定会指向一个不属于您的位置 - -> 以下任何内容都会导致未定义的行为。

    【讨论】:

    • 谢谢,我实际上想说“堆”而不是“堆栈”。不错的收获!此外,即使您不取消引用它们,指向不拥有的东西的指针是否也被视为未定义的行为?例如,vector.end() 指向向量的末尾,只要不取消引用就可以。
    • 只要不取消引用指针就可以了:指针只是一个数字。在您的代码中,您将此指针用作unique_ptr 的输入参数,而那里真正发生的情况取决于unique_ptr 类的实现。
    • @Ryan 取消引用甚至不是一个考虑因素,问题在于计算指针。在进行指针运算时,您必须保持在数组的范围内或指向过去的末尾。并且没有必要将迭代器带入其中。
    • 不,即使不取消引用指针也不行。创建该指针的唯一行为会产生问题,请参阅我现在编辑的答案(感谢您提出这个问题,+1)。
    • 我认为我们在这里有点困惑。我指的是标准指针(C 类型),因此设置为任何值都没有问题。当然,智能指针并非如此。
    猜你喜欢
    • 1970-01-01
    • 2018-08-13
    • 2016-08-01
    • 1970-01-01
    • 2011-01-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多