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

时间:2009-04-01 01:22:05

标签: c++ arrays pointers new-operator delete-operator

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

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和非{{1的对比)终止的字符串 - 它将一直持续到达0)。

那么他们的工作是记住这些事情吗?操作系统是否在后台保留某种类型的记录? (我的意思是,我意识到我开始这篇文章时说过发生的事情是未定义的,但事实是,'杀戮狂欢'的情况不会发生,所以因此在实际世界中某人是记住。)

16 个答案:

答案 0 :(得分:102)

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

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

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

delete

然后为Foo* foo = new Foo; 分配的内存空间不应包含支持foo数组所需的任何额外开销。

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

的原因
Foo

而不仅仅是

delete[] bar;

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

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

答案 1 :(得分:97)

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

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

答案 2 :(得分:27)

是的,操作系统会在“后台”中保留一些内容。例如,如果您运行

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

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

答案 3 :(得分:13)

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

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

通过执行以下代码

可以在某些实现中查看
int* pArray = new int[5];
int size = *(pArray-1);

答案 4 :(得分:9)

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

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

答案 5 :(得分:6)

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

答案 6 :(得分:5)

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

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

我已经了解到,答案与课程析构函数有关。

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

classes = new MyClass[3];

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

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

答案 7 :(得分:3)

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

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

答案 8 :(得分: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 :(得分:1)

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

C ++要求程序员根据释放的主题选择正确版本的delete运算符:数组或单个对象。

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

答案 10 :(得分:1)

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

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

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

答案 11 :(得分:0)

您不能对阵列使用 delete ,也不能将 delete [] 用于非阵列。

答案 12 :(得分:0)

“未定义的行为”仅表示语言规范对即将发生的事情没有保证。这并不意味着一定会发生不好的事情。

  

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

这里通常有两层。底层的内存管理器和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);

答案 13 :(得分:-1)

嘿,这很好取决于你在类型或类/结构中分配构建数组时使用new []表达式分配的内容,并且你不提供构造函数和析构函数,运算符会将其视为一个大小& #34;的sizeof(对象)* numObjects&#34;而不是对象数组,因此在这种情况下,分配对象的数量将不会存储在任何地方,但是如果您分配对象数组并且在对象中提供构造函数和析构函数而不是行为更改,则新表达式将分配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 );
}
///////////////////////////////////////////

答案 14 :(得分:-2)

只需在类中定义析构函数并使用语法

执行代码
delete pointer

delete [] pointer

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

答案 15 :(得分:-3)

答案:

int * pArray = new int [5];

int size = *(pArray-1);

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