在一个联合体里对指针进行删除操作

6

我尝试了一些有趣的代码(至少对我来说是这样!)。 这是它。

#include <iostream>

struct myStruct{
    int one;

    /*Destructor: Program crashes if the below code uncommented*/
    /*
    ~myStruct(){
        std::cout<<"des\n";
    } 
    */
};
struct finalStruct {
    int noOfChars;
    int noOfStructs;

    union { 
        myStruct *structPtr;
        char *charPtr;
    }U;

};
int main(){
    finalStruct obj;
    obj.noOfChars = 2;
    obj.noOfStructs = 1;
    int bytesToAllocate = sizeof(char)*obj.noOfChars 
                        + sizeof(myStruct)*obj.noOfStructs;

    obj.U.charPtr = new char[bytesToAllocate];
    /*Now both the pointers charPtr and structPtr points to same location*/

    delete []obj.U.structPtr;
}

我已经为charPtr分配了内存,并使用structPtr删除了它。如果我在myStruct中添加一个析构函数,程序就会崩溃,否则没有问题。

这里到底发生了什么?据我所知,delete[]将调用与new[]中给定的数字相同次数的析构函数。为什么当myStruct中没有析构函数时,程序不会崩溃呢?

4个回答

8
首先,按你现在的方式存储一个结构体联合体的成员然后读取另一个是未定义的行为。这种做法是错误的,任何事情都可能发生。
除此之外,你尝试使用联合体进行类型游戏的方式很可能实际上可行(但请记住这并不是保证),具体如下:
你分配了一个char类型对象的长度为bytesToAllocate的数组,并将其地址存储在联合指针中。
然后,您以类似myStruct*的方式调用联合指针上的delete[]函数。这意味着函数假定它是一个myStruct对象的数组,并对每个对象调用析构函数。然而,数组中不包含任何myStruct对象,它只包含char对象。实际上,该数组的字节大小甚至不是myStruct的大小的倍数!delete实现必须非常困惑。它可能会将第一个sizeof(myStruct)字节解释为一个myStruct对象,并在这些字节上调用析构函数。然后,它仍然有少于sizeof(myStruct)字节剩余,但仍有一些字节留下,所以在这些不完整的字节上调用析构函数时会越过数组,结果非常有趣。
当然,由于这只是未定义的行为,我对上述行为的猜测可能完全错误。简单的说,你已经把它搞混了,所以它的行为也就变得混乱不清。

内存管理器很可能根本不会感到困惑。它是完全无知的。 - Peter - Reinstate Monica
不好意思,您的回答主要观点是错误的:没有调用析构函数,也没有混淆free。没有将任何字节解释为其他内容。 - Peter - Reinstate Monica
@PeterSchneider OP说:“当我取消注释dtor时,它会崩溃。” 所以我是在评论存在dtor的情况。那么为什么它不会被调用呢? - Angew is no longer proud of SO
哦。鉴于OP并没有对存在用户定义的dtor时的崩溃感到困惑,我认为(认真地)我们正在回答OP的问题:“为什么在myStruct中没有析构函数时不会崩溃?”,假设您谈论的是错误的默认dtor。如果有dtor,则您的答案是有效的,确实如此。也许您可以更清楚地表达这一点。但这显然不是OP所问的。 - Peter - Reinstate Monica
如果我没记错的话,使用char*进行别名处理是有保证的。 - Lightness Races in Orbit
@LightningRacisinObrit 在哪里?我知道你可以将一个对象作为char(或unsigned char)数组访问,但我仍然认为不能通过联合中的这种指针访问。 - Angew is no longer proud of SO

2

delete操作包括两件事情,调用析构函数和释放内存。

你为一种类型分配了内存,但是使用delete时却使用了另一种类型。这样做是不正确的。在C/C++中有很多事情可以做,可以看看IOCCC以获取更多灵感 :-)

C++中的一个结构体如果没有任何函数并且只有plain old data,则它本身就是POD。创建/删除时甚至不会调用标准构造函数/析构函数,仅出于性能考虑。

一个具有(EDIT)自定义拷贝赋值运算符、虚函数或析构函数的结构体,在内部稍微复杂一些。它具有成员函数指针表。

如果你使用char分配内存块,则该表不会被初始化。当使用非POD类型删除该内存块时,首先调用析构函数。由于析构函数指针未初始化,它会调用内存空间中的任何内容,以为那是函数,这就是为什么会导致崩溃的原因。


1
你确定成员函数指针表格没问题吗?(用于单态类。)我打赌,如果你添加一个非虚拟成员函数,结构体的大小不会改变,而且它仍然可以与其C等效版本在相同编译器系列下实现二进制兼容。 - Peter - Reinstate Monica
@PeterSchneider 你说得完全正确。标准布局结构体可以拥有任意数量的非虚拟成员函数,其大小/布局不会受到影响。 - Angew is no longer proud of SO
好的,我没有具体说明。在C++中,“普通数据结构”是一个聚合类,仅包含PODS作为成员,没有用户定义的析构函数......静态函数不会改变大小,非虚函数可能会。我会检查一下。谢谢! - Valentin H
1
@ValentinHeinitz 非虚非特殊成员函数不会影响结构体的POD状态。C++14 [class]/10。 - Angew is no longer proud of SO

2
它之所以有效,是因为myStruct没有析构函数。[编辑:我现在看到你尝试过了,它会崩溃。我觉得很有趣的问题是,为什么它会因为那个析构函数而崩溃,因为析构函数并没有访问对象的任何内存。]
正如其他人所说,free[]的第二个功能除了可能调用元素的析构函数(在这里不会发生,如上所述),还包括释放内存。
在您的实现中,这完全有效,因为通常的自由存储实现只是为此目的分配一个内存块,其大小保留在该内存中的簿记位置中。一旦分配,大小(不依赖于指针类型)与free无关。请参阅delete[]如何“知道”操作数数组的大小?。像malloc一样的类型不可知分配器返回内存块,并且很高兴。
请注意,当然,您所做的是错误的,请不要在家里这样做,不要发布它并使人们签署非责任协议,并且不要在核设施中使用它,并始终从main()返回int。

"myStruct没有析构函数",是有的。 - Lightness Races in Orbit
不在所呈现的代码中,或者是我漏看了什么? - Peter - Reinstate Monica
@LightningRacisinObrit 嗯,我猜你指的是自动生成的默认析构函数。现在这几乎是一个本体论问题,即是否存在不留下可观察痕迹的东西。我们可以显式地调用它,所以我想从技术上讲它是存在的,不过这点我承认。 - Peter - Reinstate Monica

1
问题在于obj.U.structPtr指向一个结构体,该结构体可能有构造函数和析构函数。 delete也需要正确的类型,否则它无法调用析构函数。
因此,使用new创建char数组并将其作为struct指针删除是非法的。
如果使用mallocfree,那么就可以了。这不会调用构造函数和析构函数。

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