【问题标题】:delete [] after casting unsigned char* to const unsigned char*将 unsigned char* 转换为 const unsigned char* 后删除 []
【发布时间】:2013-04-19 08:05:31
【问题描述】:

粗略地说,我有一个包含 const unsigned char 数组的类。 此类的对象由一个特殊的工厂函数创建,该函数还负责构造数组(在堆上)。当在工厂函数中创建一个对象时,它将被赋予指向数组的指针。数组不会被复制,对象只会使用给定的指针。销毁时,它将释放数组占用的内存块。

class Foo
{
private:
    const unsigned char* array;
    size_t size;

public:
    Foo(const unsigned char* array, size_t size) : array(array), size(size) {}
    ~Foo() {delete [] array;}
};

Foo* factoryFunction(const void* data, size_t size)
{
    unsigned char* array = new unsigned char[size];
    memcpy(array, data, size);
    return new Foo(array, size);
}

现在想知道有没有副作用,因为new []返回unsigned char *,但是delete []被调用为const unsigned char *。不过我没有遇到任何分段错误。

【问题讨论】:

  • const 关键字只是告诉编译器和读取代码的人,您不应更改此成员/变量/参数。 (使用指针,const 的位置告诉内容是否不会改变,或者指针不应该改变,或两者兼而有之),但对于delete[],如果 const 存在与否,它不应该有所不同.在 c++ 中,您应该避免使用这种数组,而是使用 std::vector<unsigned char>
  • On destruction, it will reallocate the chunk of memory that the array occupied. 在我看来不是这样。另外,你的复制构造函数在哪里?为什么要使用原始指针?
  • @t.niese - 你提出了几个好观点,这几乎应该是一个答案而不是评论。
  • @LightnessRacesinOrbit - 感谢您的评论。我不是母语者,当然这个词应该是“deallocate”而不是“reallocate”,我会解决这个问题;)另外,我想保持代码示例简单,所以我省略了复制构造函数。我使用数组而不是 std::vector,因为我使用的是 C 库。
  • 您不能依靠分段错误来识别未定义的行为。

标签: c++


【解决方案1】:

这很好,标准中的非规范性文本暗示了同样的事情:

[C++11: 5.3.5/2]: 如果操作数具有类类型,则通过调用上述转换函数将操作数转换为指针类型,并在本节的其余部分使用转换后的操作数代替原始操作数。在第一种选择(delete object)中,delete 的操作数的值可能是一个空指针值,一个指向由前一个 new-expression 创建的非数组对象的指针,或指向表示此类对象的基类的子对象(1.8)的指针(第 10 条)。如果不是,则行为未定义。 在第二种选择(删除数组)中,delete 的操作数的值可以是空指针值或由前一个数组产生的指针值new-expression . 如果不是,则行为未定义。 [注意:这意味着delete-expression的语法必须匹配new分配的对象的类型,而不是new-的语法表达式—结束注] [注:指向 const 类型的指针可以是 delete-expression 的操作数;在将指针表达式用作 delete-expression 的操作数之前,不必丢弃指针表达式的 constness (5.2.11)。 ——尾注]


以下段落可能引起争议:

[C++11: 5.3.5/3]:在第一种选择(delete object)中,如果要删除的对象的静态类型与其动态类型不同,则静态类型应为动态的基类要删除的对象的类型和静态类型应具有虚拟析构函数或行为未定义。 在第二种选择中(删除 array) 如果要删除的对象的动态类型与其静态类型不同,则行为未定义。

虽然在我看来,这篇文章的意图只是处理多态性,但结合下面的文章,它可能会被解释为意味着,严格来说,你的代码实际上会调用未定义的行为:

[C++11: 3.9.3/1]: [..] 类型的 cv 限定或 cv 非限定版本是不同的类型; [..]

但是,我有理由相信这可以被视为标准中的措辞缺陷;我的意图似乎很清楚。


同样,这条规则甚至可能暗示程序无法编译:

[C++11: 12.5.4]: [..] 如果删除表达式以一元 :: 运算符开头,则在全局范围内查找释放函数的名称。否则,如果 delete-expression 用于释放静态类型具有虚拟析构函数的类对象,则释放函数是在定义动态类型的虚拟析构函数时选择的函数(12.4) .否则,如果delete-expression用于释放类T的对象或其数组,对象的静态和动态类型应该相同并且释放在 T 的范围内查找函数的名称。如果此查找未能找到该名称,则在全局范围内查找该名称。如果查找的结果不明确或无法访问,或者查找选择了一个放置解除分配函数,则程序是非良构的。

但同样,这不是该规则的意图,它解决了在没有虚拟析构函数的情况下的多态性,以及跨多个类声明的真正歧义。


总而言之,在解释这些规则的措辞时,您最好的选择仍然是我们开始时的非规范性但非常明确的注释。

【讨论】:

    【解决方案2】:

    应该没有任何问题,因为“unsigned char”(或任何数据类型)上的“const”-ness 使其成为只读数据。

    在删除中使用“[]”表示必须删除一个数组。

    要了解“删除”与“删除 []”背后的含义,请阅读 this

    【讨论】:

    • 只是一些吹毛求疵:P 它标志着它应该只读使用......但你仍然有讨厌的const_cast(但无论如何,这不是问题的一部分)
    • 我认为您并没有真正解决这个问题。您只是陈述了与constdelete[] 的基本功能无关的事实。
    • @轨道中的 Lightness Races,感谢您提供对标准的一些参考。 @t.niese,我读了你的回答,它只是解释了“const”-ness,也不比任何网络挑选更好:P
    【解决方案3】:

    首先你应该避免在c++中使用这种数组,你应该使用std::vector<unsigned char>

    关于const 关键字。它只是告诉编译器和读取代码的人不应更改此成员/变量/参数。

    使用指针,const 关键字的位置很重要:

    编辑:找到Greyson的答案,我借用了这部分)
    const 关键字将其左侧的部分标记为常量(如果它在开头,则表示紧随其后的类型,因此 const unsigned charunsigned char const 相等)

    要将指针标记为 const,您可以这样做(内容仍然可以更改):

    unsigned char * const aConstantPointerToAMutableContent;
    

    要将内容标记为 const,您将执行以下操作:

    const unsigned char * aPointerToAConstantContentA;
    unsigned char const * aPointerToAConstantContentB;
    

    将两者都标记为常量:

    const unsigned char * const aConstantPointerToAConstantContent;
    

    这是对编译器和用户的提示,以便清楚地知道对数据做了什么或不做什么。如果参数是const unsigned char *,那么用户就知道这个内容如果传递了就不会改变。

    因为 const 关键字只是一个标记,如果某些东西是可变的,它对大小本身没有影响,所以对于 delete 它应该没有影响。 (请参阅Lightness Races in Orbit 的答案和 cmets 以及 "Is const_cast safe?" 可能是出于兴趣)。

    但我不喜欢你的代码的地方在于构造函数是公开的。我可以直接调用它,但是因为参数是const unsigned char* array,我不希望该类会更改内容或在销毁时将其删除。 (我不希望直接创建对象与使用工厂的行为方式不同。)

    所以我会将工厂方法设为类的staticmethod,并将构造函数设为protected

    【讨论】:

    • 为什么您的问题包含对const 功能的描述?这个问题并不要求那个。 delete 的工作原理不仅仅是“大小本身”。
    猜你喜欢
    • 1970-01-01
    • 2011-10-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-19
    相关资源
    最近更新 更多