分配内存然后释放是否构成C ++程序的副作用?

时间:2011-07-08 11:52:10

标签: c++ memory memory-management

this question的启发,关于编译器是否可以优化掉对函数的调用而没有副作用。假设我有以下代码:

delete[] new char[10];

没有任何用处。但它有副作用吗?堆分配后立即释放被认为是副作用吗?

5 个答案:

答案 0 :(得分:10)

由实施决定。分配和释放内存不是“可观察的行为”,除非实现决定它是可观察的行为。

实际上,您的实现可能链接到某种类型的C ++运行时库,并且在编译TU时,编译器必须识别对该库的调用可能具有可观察到的影响。据我所知,这不是标准规定的,它只是通常的工作方式。如果优化器可以某种方式确定某些调用或调用组合实际上不会影响可观察行为,那么它可以删除它们,所以我相信一个特殊情况来发现你的示例代码并删除它会符合。

另外,我不记得用户定义的全局new[]delete[]如何工作 [我已被提醒]。由于代码可能会在另一个用户定义的TU中调用这些内容的定义,这些TU稍后会链接到此TU,因此无法在编译时优化调用。如果结果表明操作符不是用户定义的(尽管那时候运行库的内容适用),或者是用户定义的但是没有副作用(一旦它们一对),它们可以在链接时删除内联 - 这在合理的实现中似乎非常难以置信,实际上是[*])。

我很确定你不能依赖来自new[]的例外来“证明”你是否已经耗尽内存。换句话说,仅仅因为new char[10]没有抛出这个时间,并不意味着它在你释放内存并再试一次后就不会抛出。而且仅仅因为它最后一次投掷而你从未放过任何东西,并不意味着它会抛出这个时间。所以我认为没有任何理由说明为什么这两个调用无法消除 - 没有标准保证new char[10]将抛出的情况,所以不需要实现来确定它是否会或不。据您所知,系统上的其他一些进程在调用new[]之前释放了10个字节,并在调用delete[]之后立即分配。

[*]

或许不是。如果new没有检查空间,可能依赖于保护页面,但只是递增一个指针,而delete通常什么也不做(依靠进程退出释放内存),但在特殊情况下释放的块是分配的最后一个块,它递减指针,你的代码可以等同于:

// new[]
global_last_allocation = global_next_allocation;
global_next_allocation += 10 + sizeof(size_t);
char *tmp = global_last_allocation;
*((size_t *)tmp) = 10; // code to handle alignment requirements is omitted
tmp += sizeof(size_t);

// delete[]
tmp -= sizeof(size_t);
if (tmp == global_last_allocation) {
    global_next_allocation -= 10 + *((size_t*)tmp);
}

假设没有任何东西是易变的,几乎所有人都可以删除,只需离开global_last_allocation = global_next_allocation;。您也可以通过将last的先前值与大小一起存储在块头中,并在释放最后一个分配时恢复该先前值来消除这种情况。这是一个非常极端的内存分配器实现,但是,你需要一个单线程程序,一个速度恶魔程序员,他相信程序不会通过比开始时更多的内存流失。

答案 1 :(得分:3)

new[]delete[]最终可能导致系统调用。此外,new[]可能会抛出。考虑到这一点,我没有看到new-delete序列如何合法地被认为没有副作用并且被优化掉了。

(在此,我假设不涉及new[]delete[]的重载。)

答案 2 :(得分:2)

没有。也不应该被编译器删除,也不应认为是副作用。请考虑以下事项:

struct A {
  static int counter;
  A () { counter ++; }
};

int main ()
{
  A obj[2]; // counter = 2
  delete [] new A[3]; // counter = 2 + 3 = 5
}

现在,如果编译器将此作为副作用删除,则逻辑出错。因此,即使您没有做任何事情,编译器也会始终假设正在发生一些有用的事情(在构造函数中)。这就是原因;

A(); // simple object allocation and deallocation

没有优化。

答案 3 :(得分:1)

编译器无法看到delete []和new []的实现,必须假设它。

如果你在它上面实现了delete []和new [],编译器可以完全内联/优化这些函数。

答案 4 :(得分:1)

通常情况下,

newdelete将导致对操作系统堆管理器的调用,这很可能会产生一些副作用。如果您的程序只有一个线程,那么您显示的代码应该没有副作用,但是我在Windows上的观察(主要是在32位平台上)表明,至少大量分配和随后的解除分配通常会导致'堆争用'即使所有内存都已释放。另见this related post on MSDN

如果多个线程正在运行,可能会出现更复杂的问题。虽然您的代码在此期间释放内存,但是另一个线程可能已分配(或释放)内存,并且您的分配可能会导致进一步的堆碎片。这一切都是理论上的,但有时可能会出现。

如果您对new的调用失败,则根据您使用的编译器版本,可能会抛出exception bad_alloc,这当然会产生副作用。