代码之家  ›  专栏  ›  技术社区  ›  Thomas Weller

我在这里有什么样的UB,删除运算符new[]的数组?

  •  0
  • Thomas Weller  · 技术社区  · 6 月前

    给定以下代码 operator new[] 以及各种错误的版本 delete :

    #include <iostream>
    
    class Test {
    public:
        Test()  { std::cout << "Test()\n";  }
        ~Test() { std::cout << "~Test()\n"; }
    };
    
    int main()
    {
        void* p = operator new[](10 * sizeof(Test));
        Test* test = new (p) Test();
        test->~Test();
        // delete p; // UB1
        // delete[] p; // UB2
        // delete[] test; // UB3
        // operator delete[](test); // UB4?
        operator delete[](p);
        std::cout << "End\n";
    }
    
    

    继续活着 https://godbolt.org/z/zG3nfzsv6

    你能解释一下为什么UB2到UB4是未定义的行为吗?用一些外行术语解释一下C++20草案N4861的标准,第7.6.2.8节[删除],也许还有第7.6.2.7节[新增]?

    这是我的尝试。我对UB1很有信心,但对UB2和UB3不太有信心,更不用说对UB4了。

    UB1

    (强调我)

    § 7.6.2.8 (1)

    删除表达式:
    ::选择删除强制转换表达式
    ::opt-delete[]强制转换表达式

    第一种选择是使用单个对象删除表达式 ,第二个是数组删除表达式。

    § 7.6.2.8 (2)

    在一个 单对象删除表达式 ,delete操作数的值可以是空指针值 指向非数组对象的指针 例如由先前的新表达式创建的对象(11.7)或指向表示此类对象的基类的子对象(6.7.2)的指针。 如果没有,则行为未定义。

    所以,为了 删除 没有 [] 我不能使用数组指针。但我做到了,所以我有UB。好的。

    UB2至UB4

    (强调我)

    在数组中删除 表达 ,delete操作数的值可能是空指针值或导致的指针值 从以前的数组中生成新表达式 . 74 如果没有,则行为未定义。

    好吧,我有一个来自以前新表达式的数组。

    [注意:这意味着delete表达式的语法必须与new分配的对象类型匹配, 不是新表达式的语法 .end注释]

    附带问题:为什么这意味着什么?

    好吧,我有 操作员新[] 这个注释告诉我,删除的语法可能不同。我不一定需要 operator delete[] 而是与对象类型相匹配的东西。

    附带问题:什么 我的对象的类型?我的指针p是 void* 但是 void 不是对象类型。p是否指向a void[] 无效[] 是对象类型吗?

    § 7.6.2.8 (11)

    对于数组删除表达式 delete对象是数组对象。当执行删除表达式时,所选的释放函数 应在单个对象删除表达式中调用已删除对象的地址,或 在数组删除表达式中针对数组分配开销(7.6.2.7)适当调整已删除对象, 作为其第一个论点。

    对不起,我对此一无所知。我只是有一种直觉,这可能是相关的。

    1 回复  |  直到 6 月前
        1
  •  1
  •   Remy Lebeau    6 月前

    你需要区分 new / delete 表达 以及 / 删除 操作员 它们是两件不同的事情。这个 操作员 只需分配和释放内存。这个 表达 调用运算符,以及对正在创建/销毁的对象调用构造函数/析构函数。

    在您的示例中,您正在使用 new[] 操作人员 直接,但随后UB1-UB3正试图使用 删除 表达 delete[] 表达 ,这是错误的。

    你需要把事情正确地匹配起来。使用 删除 / 删除[] 表达 只有与 / 表达 1. .使用 删除 / 删除[] 操作员 只有与 / 操作员 .

    1. 除了你的情况 test 指针,因为您不能调用任何形式的 删除 表达 操作人员 在由返回的指针上 placement-new ,因为 新位置 不分配新内存,只是在现有内存中创建一个对象。这就是为什么在这种情况下必须直接调用对象的析构函数。