代码之家  ›  专栏  ›  技术社区  ›  GRB

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

  •  148
  • GRB  · 技术社区  · 17 年前

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

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

    delete[]

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

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

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

    strlen 和一个非- \0 -终止字符串——它将继续运行,直到达到0)。

    那么,谁的工作是记住这些事情呢?操作系统是否在后台保存某种类型的记录?(我的意思是,我意识到我在开始这篇文章时说,发生的事情是不确定的,但事实是,“疯狂杀戮”的情况不会发生,因此在现实世界中 某人 正在回忆。)

    16 回复  |  直到 10 年前
        1
  •  112
  •   Dan Breslau    17 年前

    到目前为止给出的答案似乎没有解决一个问题:如果运行时库(实际上不是操作系统)可以跟踪数组中的事物数量,那么我们为什么需要 delete[] 语法?为什么一个都不能 delete

    这个问题的答案可以追溯到C++作为C兼容语言的根源(它不再真正努力成为C兼容语言。)Stroustrup的哲学是程序员不应该为他们不使用的任何功能付费。如果他们不使用数组,那么他们就不必为每个分配的内存块承担对象数组的成本。

    也就是说,如果你的代码只是

    Foo* foo = new Foo;
    

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

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

    delete[] bar;
    

    而不是仅仅

    delete bar;
    

    如果bar是一个指向数组的指针。

        2
  •  105
  •   Fred Larson    11 年前

    编译器不知道这是一个数组,它信任程序员。删除指向单个对象的指针 int 随着 delete [] 将导致未定义的行为。第二 main()

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

        3
  •  30
  •   bsdfish    17 年前

    是的,操作系统在后台保留了一些东西。例如,如果你跑步

    int* num = new int[5];
    

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

        4
  •  13
  •   Community Mohan Dere    9 年前

    this 问题,它有很多你想要的细节。

    但可以说,跟踪这些并不是操作系统的工作。实际上是运行时库或底层内存管理器将跟踪数组的大小。这通常是通过预先分配额外的内存并将数组的大小存储在该位置来实现的(大多数使用头节点)。

    通过执行以下代码,可以在某些实现上查看这一点

    int* pArray = new int[5];
    int size = *(pArray-1);
    
        5
  •  9
  •   Benoît    17 年前

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

    不管怎样,混合 new/new[] 和 delete/delete[] 可能是UB。

        6
  •  6
  •   eduffy    17 年前

    它不知道这是一个数组,这就是为什么你必须提供 delete[] 而不是普通的老 delete .

        7
  •  5
  •   ProdigySim    16 年前

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

    我了解到,答案与类析构函数有关。

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

    classes = new MyClass[3];
    

    使用delete删除它,您可能只会得到调用MyClass的第一个实例的析构函数。如果您使用delete[],可以确信会对数组中的所有实例调用析构函数。

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

        8
  •  3
  •   Uri    17 年前

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

    然而,运行时不够智能,无法检测它是数组还是指针,你必须通知它,如果你错了,你要么没有正确删除(例如,用ptr代替数组),要么你最终为大小取了一个无关的值并造成了重大损害。

        9
  •  3
  •   Avt    12 年前

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

    示例如何完成: 这里

    int* i = new int[4];
    

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

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

    4 首先 sizeof(int) 字节

    *temp = 4;
    

    并设置 i

    i = temp + 1;
    

    所以 我 指向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)
    
        10
  •  1
  •   user686898 mloskot    15 年前

    从语义上讲,C++中两个版本的delete运算符都可以“吃掉”任何指针;但是,如果给一个指向单个对象的指针 delete[] ,则将产生UB,这意味着可能会发生任何事情,包括系统崩溃或根本什么都没有。

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

        11
  •  1
  •   Don Wakefield    15 年前

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

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

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

        12
  •  1
  •   plugwash    5 年前

    那么,谁的工作是记住这些事情呢?操作系统是否在后台保存某种类型的记录?(我的意思是,我意识到我在这篇文章的开头说,发生的事情是不确定的,但事实是,“疯狂杀戮”的情况不会发生,因此在现实世界中,有人会记住。)

    这里通常有两层。底层内存管理器和C++实现。

    大多数内存管理器都是为满足C语言的需求而设计的。在C中,“free”函数不要求用户指定块的大小。因此,内存管理器将记住(除其他事项外)分配的内存块的大小。这可能比C++实现所要求的块大。通常,内存管理器会在分配的内存块之前存储元数据。

    C++有一种“你只为你使用的东西付费”的文化。因此,C++实现通常只会在出于自身目的需要时记住数组的大小,这通常是因为该类型具有非三价析构函数。

    因此,对于具有平凡析构函数的类型,“delete”和“delete[]”的实现通常是相同的。C++实现只是将指针传递给底层内存管理器。有点像

    free(p)
    

    另一方面,对于具有非平凡析构函数的类型,“delete”和“delete[]”可能会有所不同。“delete”类似于(其中T是指针指向的类型)

    p->~T();
    free(p);
    

    size_t * pcount = ((size_t *)p)-1;
    size_t count = *count;
    for (size_t i=0;i<count;i++) {
      p[i].~T();
    }
    char * pmemblock = ((char *)p) - max(sizeof(size_t),alignof(T));
    free(pmemblock);
    
        13
  •  0
  •   Don Wakefield    15 年前

    您不能使用 删除 对于数组,您不能使用 删除[] 对于非数组。

        14
  •  -1
  •   Rafal Rebisz    10 年前

    嘿,好吧,这取决于你用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 );
    }
    ///////////////////////////////////////////
    
        15
  •  -2
  •   bubu    10 年前

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

    delete pointer
    
    delete [] pointer
    

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

        16
  •  -3
  •   Evgeni Raikhel    11 年前

    答案是:

    int大小=*(pArray-1);

    上面发布的内容不正确,并产生无效值。 在64位Windows操作系统上,正确的缓冲区大小位于Ptr-4字节地址中