这是内存泄漏吗,如果不是,标准是否保证?

Is this a memory leak, if not is it guaranteed by the standard?

提问人:Luchian Grigore 提问时间:1/19/2012 最后编辑:CommunityLuchian Grigore 更新时间:1/19/2012 访问量:166

问:

最近的一个问题相关,我编写了以下代码:

int main()
{
   char* x = new char[33];
   int* sz = (int*)x;
   sz--;
   sz--;
   sz--;
   sz--;
   int szn = *sz; //szn is 33 :)
}

我知道它不安全,也永远不会使用它,但它让我想起了一个问题:

以下安全吗?是内存泄漏吗?

char* allocate()
{
    return new char[20];
}

int main()
{
    char* x = allocate();
    delete[] x;
}

如果它是安全的,那不意味着我们实际上可以找到数组的大小吗?当然,不是以标准方式,但是编译器是否需要存储有关数组大小的信息?

我没有使用或计划使用此代码。我知道这是未定义的行为。我知道它不能得到任何保证。这只是一个理论问题!

C++语言

评论

6赞 James McNellis 1/19/2012
第一个代码片段与第二个代码片段有什么关系?为什么你认为第二个片段是不安全的?
0赞 Luchian Grigore 1/19/2012
@JamesMcNellis第一个代码段显示,该特定编译器在某处存储了有关数组大小的信息。这让人想起了第二个片段。
2赞 Paul Tomblin 1/19/2012
@JamesMcNellis,第一个代码片段说明了在某些实现中,至少分配的内存大小存储在内存块之前。这涉及到他的观点,即运行时在删除内存块时总是知道内存块的大小。
0赞 BЈовић 1/19/2012
Ubuntu 11.10,在 x64 上打印 szn 时输出 0。这是有道理的,因为它是UB
0赞 Luchian Grigore 1/19/2012
@VJovic我所期望的,这适用于带有 MSVS 2008 的 Win7x64,我怀疑它是否适用于许多其他平台。

答:

1赞 Reed Copsey 1/19/2012 #1

这是安全的,不是内存泄漏。这些标准要求通过任何数组分配来处理内存的释放。delete[]

如果它是安全的,那不意味着我们实际上可以找到数组的大小吗?

这些标准没有对分配大小的存储位置和方式提出具体要求。如上所示,这是可以发现的,但不同的编译器/平台也可以使用完全不同的方法。因此,依靠这种技术来发现大小是不安全的。

0赞 BЈовић 1/19/2012 #2
int main()
{
   char* x = new char[33];
   int* sz = (int*)x;
   sz--;
   sz--;
   sz--;
   sz--;
   int szn = *sz; //szn is 33 :)
}

这是一种未定义的行为,因为您访问了未分配的内存位置。

编译器是否需要存储有关数组大小的信息?

不。


如果它是安全的,那不意味着我们实际上可以找到数组的大小吗?

您在第二个代码snipet中没有做任何特别的事情,因此它是安全的。但是没有办法获得数组的大小。

评论

0赞 Luchian Grigore 1/19/2012
不是很有帮助。我知道这不是标准行为,正如我已经指出的那样。如果编译器不需要存储信息,它如何在第二个代码片段中释放整个数组?
1赞 matthias 1/19/2012
不需要在该位置存储信息,尽管有些需要。它是未定义的,因为你不能依赖该信息处于该位置,并且您可能正在访问完全不相关的数据。
0赞 R. Martinho Fernandes 1/19/2012
@Luchian:与释放分配了 .需要存储大小是因为它需要调用正确数量的析构函数。但是,调用 的析构函数是一个 noop,因此,按照 as-if 规则,编译器不能调用任何内容,因此不需要存储任何内容。new charchar
1赞 Mr Lister 1/19/2012
@LuchianGrigore 你说“编译器”,但编译器很多!而且它们的工作方式都不同!即使是同一编译器的不同版本也可以以不同的方式处理这个问题。不要试图让它起作用。请。
0赞 BЈовић 1/19/2012
@LuchianGrigore 所以,您的问题是特定于操作系统的。编译器不存储有关数组大小的信息
5赞 Pubby 1/19/2012 #3

以下安全吗?

是的,这当然是安全的。但是,第一个代码段具有 UB。

如果它是安全的,那不意味着我们实际上可以找到数组的大小吗?当然,不是以标准方式,但是编译器是否需要存储有关数组大小的信息?

是的,通常额外的数据存储在第一个元素之前。这用于调用正确数量的析构函数。访问这个是 UB。

是否需要存储有关数组大小的信息?

不。它只需要按预期工作。 可能只是一个普通的 malloc 调用,它不一定存储请求的大小 10。delete[]new int[10]

0赞 Renan Greinert 1/19/2012 #4

我不确定当数组被分配基本类型时,删除是否必须知道数组的大小(这不需要调用析构函数)。在 Visual Studio 编译器中,仅为用户定义的对象存储值(在这种情况下,delete[] 必须知道数组的大小,因为它必须调用其析构函数)。

在内存中分配大小的位置是未定义的(在 Visual Studio 中,它位于 gcc 的同一位置)。

http://www.parashift.com/c++-faq-lite/freestore-mgmt.html#faq-16.14

1赞 weston 1/19/2012 #5

我知道在 c 中,堆上任何一个的大小都位于指针之前。代码依赖于此。这在K&R中有所记录。mallocfree

但你不应该依赖它总是在那里或总是处于同一个位置。

如果你想知道数组的长度,那么我建议你创建一个类或结构来记录实际数组的容量,并将其传递给你的程序,而你以前只是传递一个.char*

0赞 Mark Ransom 1/19/2012 #6

有两种方法可以销毁数组,具体取决于数组的创建方式。在这两种情况下,编译器都需要为数组的每个元素调用析构函数,因此必须知道数组中的元素数。

如果数组是堆栈上的自动变量,则元素的数量在编译时是已知的。编译器可以对它发出的代码中的元素数量进行硬编码,以销毁数组。

如果数组是在堆上动态分配的,则必须有另一种机制来了解元素计数。该机制未在标准中指定,也没有以任何其他方式公开。我认为将计数放在数组前面的偏移量是一种常见的实现,但这肯定不是唯一的方法,实际的偏移量只是一个私有的实现细节。

由于编译器必须知道数组中有多少个元素,因此您会认为标准可以强制要求一种使该计数可供程序使用的方法。不幸的是,这是不可能的,因为计数只有在销毁时才知道。想象一下,该标准包括一个可以访问隐藏信息的函数:count_of

MyClass array1[33];
MyClass * array2 = new MyClass[33];
cout << count_of(array1) << count_of(array2); // outputs 33 33
Foo(array1);
Foo(array2);
MyClass * not_array = new MyClass;
Foo(not_array);

void Foo(MyClass * ptr)
{
    for (int i = 0; i < count_of(ptr); ++i) // how can count_of work here?
    ...
}

由于传递给的指针已经丢失了其所有上下文,因此编译器无法一致地知道数组中有多少个元素,甚至根本无法知道它是否是一个数组。Foo