首页 文章

delete []如何知道它是一个数组?

提问于
浏览
131

好吧,我想我们都同意以下代码所发生的事情是未定义的,具体取决于传递的内容,

void deleteForMe(int* pointer)
{
     delete[] pointer;
}

指针可能是各种不同的东西,因此对它执行无条件的 delete[] 是未定义的 . 但是,让我们假设我们确实传递了一个数组指针,

int main()
{
     int* arr = new int[5];
     deleteForMe(arr);
     return 0;
}

我的问题是,在这种情况下,指针 is 是一个数组,谁知道这个?我的意思是,从语言/编译器的角度来看,它不知道 arr 是否是一个数组指针而不是指向单个int的指针 . 哎呀,它甚至不知道 arr 是否是动态创建的 . 但是,如果我做以下事情,

int main()
{
     int* num = new int(1);
     deleteForMe(num);
     return 0;
}

操作系统非常智能,只删除一个int,而不是通过删除超出该点的内存的其余部分来继续某种类型的'killing spree'(与 strlen 和非 \0 -终止的字符串形成对比 - 它将继续运行直到它命中0) .

那么他们的工作是记住这些东西吗?操作系统是否在后台保留某种类型的记录? (我的意思是,我意识到我开始这篇文章时说过发生的事情是未定义的,但事实是,'killing spree'场景不会发生,因此在实际世界中有人记得 . )

15 回答

  • 13

    编译器没有't know it'是一个数组,它信任程序员 . 使用 delete [] 删除指向单个 int 的指针将导致未定义的行为 . 您的第二个 main() 示例是不安全的,即使它没有立即崩溃 .

    编译器必须跟踪需要以某种方式删除的对象数 . 它可以通过过度分配来存储数组大小来实现 . 有关更多详细信息,请参阅C++ Super FAQ .

  • 98

    到目前为止给出的答案似乎没有解决的一个问题是:如果运行时库(不是OS,真的)可以跟踪数组中的事物数量,那么为什么我们需要 delete[] 语法呢?为什么不能使用单个 delete 表单来处理所有删除?

    对此的回答可以追溯到C作为C兼容语言的根源(它不再是真正的努力 . )Stroustrup的理念是程序员不应该为他们没有使用的任何功能付费 . 如果他们不使用数组,那么他们不应该为每个分配的内存块承担对象数组的成本 .

    也就是说,如果您的代码只是这样做

    Foo* foo = new Foo;
    

    那么为 foo 分配的内存空间不应包含支持 Foo 数组所需的任何额外开销 .

    由于只设置了数组分配来携带额外的数组大小信息,因此您需要告诉运行时库在删除对象时查找该信息 . 这就是我们需要使用的原因

    delete[] bar;
    

    而不仅仅是

    delete bar;
    

    如果bar是指向数组的指针 .

    对于我们大多数人(包括我自己)来说,关于几个额外字节的内存的烦恼现在看起来很古怪 . 但是仍然存在一些情况,即保存几个字节(从可能是非常多的内存块)可能很重要 .

  • 4

    是的,操作系统会在“背景”中保留一些内容 . 例如,如果你跑

    int* num = new int[5];
    

    操作系统可以分配4个额外字节,在分配的内存的前4个字节中存储分配的大小并返回偏移指针(即,它分配内存空间1000到1024但指针返回指向1004,位置1000- 1003存储分配的大小) . 然后,当调用delete时,它可以在指针传递给它之前查看4个字节以查找分配的大小 .

    我确信还有其他方法可以跟踪分配的大小,但这是一种选择 .

  • 5

    这与this问题非常相似,它有许多您正在寻找的细节 .

    但足以说明,追踪任何此类操作系统并不是操作系统的工作 . 它实际上是运行时库或底层内存管理器,它们将跟踪数组的大小 . 这通常通过预先分配额外内存并将阵列的大小存储在该位置(大多数使用头节点)来完成 .

    通过执行以下代码,可以在某些实现中查看

    int* pArray = new int[5];
    int size = *(pArray-1);
    
  • 4

    deletedelete[] 可能都会释放分配的内存(指向内存),但最大的区别是数组上的 delete 不会调用数组中每个元素的析构函数 .

    无论如何,混合 new/new[]delete/delete[] 是可能是UB .

  • 2

    它没有't know it'是一个数组,这就是为什么你必须提供 delete[] 而不是常规的旧 delete .

  • 0

    我有一个类似的问题 . 在C中,使用malloc()(或其他类似函数)分配内存,并使用free()将其删除 . 只有一个malloc(),它只分配一定数量的字节 . 只有一个free(),它只是将一个指针作为它的参数 .

    那么为什么在C中你可以将指针移交给free,但在C中你必须告诉它它是一个数组还是一个变量?

    我已经知道,答案与类析构函数有关 .

    如果你分配一个MyClass类的实例...

    classes = new MyClass[3];
    

    并使用delete删除它,您可能只获得调用的MyClass的第一个实例的析构函数 . 如果使用delete [],则可以确保将为数组中的所有实例调用析构函数 .

    这是重要的区别 . 如果您只是使用标准类型(例如int),您将不会真正看到此问题 . 另外,你应该记住在new []和delete []上使用delete的行为是未定义的 - 它可能在每个编译器/系统上的工作方式不同 .

  • -1

    这取决于负责内存分配的运行时,就像你可以使用free删除在标准C中使用malloc创建的数组一样 . 我认为每个编译器实现的方式不同 . 一种常见的方法是为数组大小分配一个额外的单元格 .

    但是,运行时不够智能,无法检测它是否是数组或指针,您必须通知它,如果您错了,您要么不能正确删除(例如,ptr而不是数组),或者你最终得到一个不相关的大小值并造成重大损害 .

  • -3

    编译器的一种方法是分配更多的内存并在head元素中存储元素的数量 .

    示例如何完成:这里

    int* i = new int[4];
    

    编译器将分配sizeof(int)* 5个字节 .

    int *temp = malloc(sizeof(int)*5)
    

    4 存储在第一个 sizeof(int) 字节中

    *temp = 4;
    

    并设置 i

    i = temp + 1;
    

    所以 i 指向4个元素的数组,而不是5个 .

    delete[] i;
    

    将按照以下方式处理

    int *temp = i - 1;
    int numbers_of_element = *temp; // = 4
    ... call destructor for numbers_of_element elements if needed
    ... that are stored in temp + 1, temp + 2, ... temp + 4
    free (temp)
    
  • 9

    同意编译器不知道它是否是数组 . 这取决于程序员 .

    编译器有时通过过度分配来存储数组大小来跟踪需要删除的对象数,但并不总是必要的 .

    有关分配额外存储的完整规范,请参阅C ABI(如何实现编译器):Itanium C++ ABI: Array Operator new Cookies

  • -2

    从语义上讲,C语言中的两个版本的delete运算符都可以"eat"任何指针;但是,如果指向单个对象的指针被赋予 delete[] ,则会产生UB,这意味着可能发生任何事情,包括系统崩溃或根本没有 .

    C要求程序员根据释放的主题选择正确版本的删除操作符:数组或单个对象 .

    如果编译器可以自动确定传递给delete运算符的指针是否是指针数组,那么C中只有一个删除运算符,这对两种情况都是足够的 .

  • 1

    您不能对数组使用delete,也不能将delete []用于非数组 .

  • 101

    嘿,这取决于你在类型或类/结构中分配构建数组时使用new []表达式分配的内容,并且你没有提供构造函数和析构函数,运算符会将其视为大小“sizeof(object)* numObjects“而不是对象数组,因此在这种情况下,分配对象的数量不会存储在任何地方,但是如果您分配对象数组并且在对象中提供构造函数和析构函数而不是行为更改,则新表达式将分配4个字节以及存储数量前4个字节的对象因此可以调用每个字节的析构函数,因此new []表达式将返回向前移动4个字节的指针,而当返回内存时,delete []表达式将首先调用函数模板,迭代通过对象数组并为每个对象调用析构函数 . 我已经创建了这个简单的代码,它会重载new []和delete []表达式,并提供一个模板函数来释放内存并在需要时调用每个对象的析构函数:

    // overloaded new expression 
    void* operator new[]( size_t size )
    {
        // allocate 4 bytes more see comment below 
        int* ptr = (int*)malloc( size + 4 );
    
        // set value stored at address to 0 
        // and shift pointer by 4 bytes to avoid situation that
        // might arise where two memory blocks 
        // are adjacent and non-zero
        *ptr = 0;
        ++ptr; 
    
        return ptr;
    }
    //////////////////////////////////////////
    
    // overloaded delete expression 
    void static operator delete[]( void* ptr )
    {
        // decrement value of pointer to get the
        // "Real Pointer Value"
        int* realPtr = (int*)ptr;
        --realPtr;
    
        free( realPtr );
    }
    //////////////////////////////////////////
    
    // Template used to call destructor if needed 
    // and call appropriate delete 
    template<class T>
    void Deallocate( T* ptr )
    {
        int* instanceCount = (int*)ptr;
        --instanceCount;
    
        if(*instanceCount > 0) // if larger than 0 array is being deleted
        {
            // call destructor for each object
            for(int i = 0; i < *instanceCount; i++)
            {
                ptr[i].~T();
            }
            // call delete passing instance count witch points
            // to begin of array memory 
            ::operator delete[]( instanceCount );
        }
        else
        {
            // single instance deleted call destructor
            // and delete passing ptr
            ptr->~T();
            ::operator delete[]( ptr );
        }
    }
    
    // Replace calls to new and delete
    #define MyNew ::new
    #define MyDelete(ptr) Deallocate(ptr)
    
    // structure with constructor/ destructor
    struct StructureOne
    {
        StructureOne():
        someInt(0)
        {}
        ~StructureOne() 
        {
            someInt = 0;
        }
    
        int someInt;
    };
    //////////////////////////////
    
    // structure without constructor/ destructor
    struct StructureTwo
    {
        int someInt;
    };
    //////////////////////////////
    
    
    void main(void)
    {
        const unsigned int numElements = 30;
    
        StructureOne* structOne = nullptr;
        StructureTwo* structTwo = nullptr;
        int* basicType = nullptr;
        size_t ArraySize = 0;
    
    /**********************************************************************/
        // basic type array 
    
        // place break point here and in new expression
        // check size and compare it with size passed 
        // in to new expression size will be the same
        ArraySize = sizeof( int ) * numElements;
    
        // this will be treated as size rather than object array as there is no 
        // constructor and destructor. value assigned to basicType pointer
        // will be the same as value of "++ptr" in new expression
        basicType = MyNew int[numElements];
    
        // Place break point in template function to see the behavior
        // destructors will not be called and it will be treated as 
        // single instance of size equal to "sizeof( int ) * numElements"
        MyDelete( basicType );
    
    /**********************************************************************/
        // structure without constructor and destructor array 
    
        // behavior will be the same as with basic type 
    
        // place break point here and in new expression
        // check size and compare it with size passed 
        // in to new expression size will be the same
        ArraySize = sizeof( StructureTwo ) * numElements;
    
        // this will be treated as size rather than object array as there is no 
        // constructor and destructor value assigned to structTwo pointer
        // will be the same as value of "++ptr" in new expression
        structTwo = MyNew StructureTwo[numElements]; 
    
        // Place break point in template function to see the behavior
        // destructors will not be called and it will be treated as 
        // single instance of size equal to "sizeof( StructureTwo ) * numElements"
        MyDelete( structTwo );
    
    /**********************************************************************/
        // structure with constructor and destructor array 
    
        // place break point check size and compare it with size passed in
        // new expression size in expression will be larger by 4 bytes
        ArraySize = sizeof( StructureOne ) * numElements;
    
        // value assigned to "structOne pointer" will be different 
        // of "++ptr" in new expression  "shifted by another 4 bytes"
        structOne = MyNew StructureOne[numElements];
    
        // Place break point in template function to see the behavior
        // destructors will be called for each array object 
        MyDelete( structOne );
    }
    ///////////////////////////////////////////
    
  • 27

    只需在类中定义析构函数并使用两种语法执行代码

    delete pointer
    
    delete [] pointer
    

    根据输出你可以找到解决方案

  • 6

    答案:

    int * pArray = new int [5];

    int size = *(pArray-1);

    上面发布的不正确并产生无效值 . “-1”计算元素在64位Windows操作系统上,正确的缓冲区大小位于Ptr - 4字节地址中

相关问题