145

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

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

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

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

我的问题是,在这种情况下,指针一个数组,谁知道这一点?我的意思是,从语言/编译器的角度来看,它不知道arr是数组指针还是指向单个 int 的指针。哎呀,它甚至不知道是否arr是动态创建的。但是,如果我改为执行以下操作,

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

操作系统足够聪明,只删除一个 int 而不会通过删除超出该点的其余内存来进行某种类型的“杀戮狂欢”(与strlen\0终止字符串对比——它将一直持续到它命中 0)。

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

4

16 回答 16

113

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

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

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

Foo* foo = new Foo;

那么分配的内存空间foo不应该包括支持Foo.

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

delete[] bar;

而不仅仅是

delete bar;

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

对于我们大多数人(包括我自己)来说,这些天对额外的几个字节内存的大惊小怪似乎很古怪。但是在某些情况下,节省几个字节(可能是非常多的内存块)可能很重要。

于 2009-04-01T01:49:16.230 回答
105

编译器不知道它是一个数组,它信任程序员。删除指向单个intwith的指针delete []会导致未定义的行为。您的第二个main()示例是不安全的,即使它不会立即崩溃。

编译器确实必须跟踪需要以某种方式删除多少对象。它可以通过过度分配足够的空间来存储数组大小来做到这一点。有关更多详细信息,请参阅C++ 超级常见问题解答

于 2009-04-01T01:30:15.450 回答
30

是的,操作系统在“背景”中保留了一些东西。例如,如果您运行

int* num = new int[5];

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

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

于 2009-04-01T01:34:01.463 回答
13

这与这个问题非常相似,它包含您正在寻找的许多细节。

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

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

int* pArray = new int[5];
int size = *(pArray-1);
于 2009-04-01T01:33:45.130 回答
9

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

反正混和new/new[]大概delete/delete[]就是UB。

于 2009-04-16T10:44:09.527 回答
6

它不知道它是一个数组,这就是为什么你必须提供delete[]而不是常规的 old delete

于 2009-04-01T01:31:22.530 回答
6

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

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

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

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

classes = new MyClass[3];

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

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

于 2010-01-28T00:46:46.183 回答
3

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

但是,运行时还不够聪明,无法检测它是数组还是指针,你必须通知它,如果你弄错了,你要么没有正确删除(例如,ptr 而不是数组),要么您最终会获得与大小无关的值并造成重大损害。

于 2009-04-01T01:32:35.040 回答
3

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

示例如何完成:这里

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)
于 2014-02-24T09:59:46.603 回答
1

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

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

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

于 2010-01-28T00:54:35.230 回答
1

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

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

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

于 2011-05-06T09:49:48.983 回答
1

“未定义的行为”仅仅意味着语言规范不保证会发生什么。这并不一定意味着会发生不好的事情。

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

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

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

C++ 有一种文化“你只需为你使用的东西付费”。因此,C++ 实现通常只会记住数组的大小,如果它出于自己的目的需要这样做,通常是因为该类型具有非平凡的析构函数。

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

free(p)

另一方面,对于具有非平凡析构函数的类型,“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);
于 2019-03-08T13:05:26.937 回答
0

您不能将delete用于数组,也不能将delete []用于非数组。

于 2009-04-10T17:18:49.590 回答
-1

遍历对象数组并为每个对象调用析构函数。我创建了这个简单的代码,它重载了 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 );
}
///////////////////////////////////////////
于 2016-01-27T16:15:08.273 回答
-2

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

delete pointer

delete [] pointer

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

于 2016-09-08T16:56:36.337 回答
-3

答案:

int* pArray = new int[5];

整数大小 = *(pArray-1);

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

于 2015-05-27T09:41:01.920 回答