指向已分配内存的指针是否属于“无效指针”或“对象指针”?

5
C++17(草案N4659)[basic.compound]/3中写道:

指针类型的每个值都是以下类型之一:

  • 指向对象或函数的指针(指针被称为指向该对象或函数),或

  • 指向对象末尾之后的指针([expr.add]),或

  • 该类型的空指针值([conv.ptr]),或

  • 无效指针值。

在下面的程序中,分配给对象寿命以外的内存的指针属于这些类别中的哪一个,特别是在// (1)// (3)处的a// (4)处的b的值?

#include<new>
#include<algorithm>

struct S {
    ~S() { /* Non-trivial destructor */ }
};

struct T {
    ~T() { /* Non-trivial destructor */ }
};

int main() {
    void* a = operator new(std::max(sizeof(S), sizeof(T)));
    // (1)
    a = new(a) S;
    static_cast<S*>(a)->~S();
    // (2)
    a = new(a) T;
    static_cast<T*>(a)->~T();
    // (3)
    operator delete(a);   

    void* b = operator new(42);
    // (4)
    operator delete(b);
}

在我看来,指针变量的值在被释放时会变得无效,而不是当对象的生命周期结束时,但如果指针变量是指向一个对象的话,那它指向的是哪个对象呢?


当内存被释放时,指向该内存的指针将变为无效。当对象不再存在时,指向该对象的指针将变为无效。对于具有析构函数的类型,当其析构函数完成时,该类型的实例(也称为对象)将不再存在。在析构一个对象并释放内存的删除表达式中,当析构函数完成时,任何指向该对象的指针都将变为无效,并且当内存被释放时(例如,operator delete()函数返回时),任何指向其底层内存的指针也将变为无效。 - Peter
据我所知,operator new返回一个指向已分配内存块的第一个字节的指针。您可以考虑在链接中的注释“注意:指向void的指针没有对象类型的指针,因为void不是对象类型。-结束注释”。即使在对象被销毁后,ab仍然指向内存。析构函数不会为已分配的内存调用delete。也许在所有情况下它都是“对象类型的指针”,但是您在使用<new>时结合了概念,这重新引入了类似于C风格的mallocfree的分配方式。 - JHBonarius
@JHBonarius 在此之前的句子中提到 void* 是一个“对象指针类型”,而引用的段落谈论的是“指针类型”的每个值,我认为这是“对象指针类型”的超集。我完全知道 operator new 返回分配内存的指针,并且析构函数调用不会释放它。 <new> 包含了非分配放置 new 的实现。std::freestd::malloc 声明在 <cstdlib> 中,而不是 <new> - walnut
一个指针无效,而另一个指针有效并没有任何问题;或者从有效指针获取无效指针(反之亦然)也是可能的。在删除表达式中,“T *”从析构函数完成后变为无效(例如,使用该指针调用对象成员函数会导致未定义行为),但“void *”仍然有效,直到内存被释放(即“operator delete()”返回)。 - Peter
@Peter "指向对象的指针在对象不存在时变得无效" 对象的存在是什么?它是指对象的生命周期吗? - curiousguy
显示剩余10条评论
2个回答

2
以下程序中,指向对象生命周期外分配内存的指针属于哪个类别,具体而言,在//(1)//(3)处的a值以及//(4)处的b值属于哪个类别?
从分配函数返回的指针值(//(1)处的a//(4)处的b)目前没有明确规定,几乎无法按照[basic.compound]/3中的分类法进行分类,请参见https://groups.google.com/a/isocpp.org/d/msg/std-discussion/4NQawIytVzM/eMKo2AJ9BwAJ
以下是我的理解:当释放时,指针值变为无效,而不是在对象的生命周期结束时变为无效。但是,如果指针值是“指向对象的指针”,那么它们指向哪个对象?
它们指向对象在其存在期间所指向的对象。

啊,好的,那很有道理。我猜这也有点让我不确定我可以用从operator new返回的指针做什么,例如是否总是允许我复制/传递它(对于无效指针是实现定义),或将其转换为其他对象指针类型(允许在特定目标类型的对象生命周期之外进行)。 - walnut
它们指向的是对象在存活时分配的内存空间,而不是对象本身。这些对象是在该内存空间中分配的,即通过new(a)进行分配,因此它们是在同一空间中创建的。正如刚才所解释的,从分配函数返回的指针是未指定的,因此为什么要这样说呢? - JHBonarius
这个脚注意味着指针可以指向其生命周期之外的对象,但不能访问已分配的内存空间。 - Language Lawyer
是的,好的,没错...但是operator new()是个例外,因为它返回的指针指向的是一个从未被任何对象占用过的已分配内存块的第一个字节...所以这有点像先有鸡还是先有蛋的故事...看起来我们必须推理直到现实符合标准...这是反过来的...我认为标准只是不完整:太严格了。 - JHBonarius

-4

所有C++标准在基本运行时概念方面都是一团糟。

什么是左值?它需要能够引用尚未创建的对象。(指针也是如此。)

未创建的对象何时存在?它们是在需要它们之前出现吗?

对我来说,最好的方法是假设所有对象类型存在于它们适合的内存中的任何地方。这被很多人认为是疯狂和矛盾的,但没有人指出矛盾之处。


“它们是否在需要之前出现?”:我认为这基本上是P0593r5提案背后的想法,这可能仍然成为C++20的一部分。 - walnut
@uneven_mark 我还无法理解现有的标准文本关于对象生命周期和对象所占用存储的含义。自从什么时候可以使用未来?在我看来,对象存在,并且在该存在期间定义了其生命周期 - curiousguy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接