C++ 是否允许编译器优化堆外内存分配?

C++ 是否允许编译器优化堆外内存分配?,c++,gcc,optimization,clang,language-lawyer,C++,Gcc,Optimization,Clang,Language Lawyer,考虑以下使用new的简单代码(我知道没有delete[],但它与此问题无关): 是否允许编译器优化new调用 在我的研究中,和Visual Studio 2015没有优化出新的调用。所有测试都启用了完全优化(-O3用于g++和clang,发布模式用于VisualStudio) 难道不是new在幕后进行系统调用,使得编译器无法(而且是非法的)优化它吗 编辑:我现在从程序中排除了未定义的行为: #include <new> int main() { int* mem = n

考虑以下使用
new
的简单代码(我知道没有
delete[]
,但它与此问题无关):

是否允许编译器优化
new
调用

在我的研究中,和Visual Studio 2015没有优化出
新的调用。所有测试都启用了完全优化(-O3用于g++和clang,发布模式用于VisualStudio)

难道不是
new
在幕后进行系统调用,使得编译器无法(而且是非法的)优化它吗

编辑:我现在从程序中排除了未定义的行为:

#include <new>  

int main()
{
    int* mem = new (std::nothrow) int[100];
    return 0;
}
#包括
int main()
{
int*mem=new(std::nothrow)int[100];
返回0;
}
不再是了,但是

EDIT2

#include <new>  

int main()
{
    int* mem = new (std::nothrow) int[1000];

    if (mem != 0)
      return 1;

    return 0;
}
#包括
int main()
{
int*mem=new(std::nothrow)int[1000];
如果(mem!=0)
返回1;
返回0;
}

谨记C++标准告诉一个正确的程序应该做什么,而不是应该如何做。它根本不能告诉后者,因为新的体系结构可以而且确实在标准编写之后出现,并且标准必须对他们有用

new
不必是引擎盖下的系统调用。有些计算机在没有操作系统和系统调用概念的情况下可用

因此,只要终端行为没有改变,编译器就可以优化任何内容。包括新的

有一个警告。
替换全局运算符new可以在不同的翻译单元中定义
在这种情况下,新产品的副作用可能无法优化消除。但是,如果编译器能够保证新操作符没有副作用,就像发布的代码是整个代码一样,那么优化是有效的。

新的can抛出std::bad_alloc不是一个要求。在这种情况下,当新的优化时,编译器可以保证不会抛出任何异常,也不会产生副作用。

记住C++标准告诉我们正确的程序应该做什么,而不是应该如何做。它根本不能告诉后者,因为新的体系结构可以而且确实在标准编写之后出现,并且标准必须对他们有用

new
不必是引擎盖下的系统调用。有些计算机在没有操作系统和系统调用概念的情况下可用

因此,只要终端行为没有改变,编译器就可以优化任何内容。包括新的

有一个警告。
替换全局运算符new可以在不同的翻译单元中定义
在这种情况下,新产品的副作用可能无法优化消除。但是,如果编译器能够保证新操作符没有副作用,就像发布的代码是整个代码一样,那么优化是有效的。

新的can抛出std::bad_alloc不是一个要求。在这种情况下,当new被优化时,编译器可以保证不会抛出异常,也不会发生副作用。

代码段中可能发生的最坏情况是
new
抛出
std::bad\u alloc
,这是未经处理的。然后发生的事情是实现定义的

在最佳情况为无操作,最坏情况未定义的情况下,允许编译器将它们考虑为不存在。现在,如果您确实尝试捕获可能的异常:

int main() try {
    int* mem = new int[100];
    return 0;
} catch(...) {
  return 1;
}

。。。然后。

在您的代码段中可能发生的最坏情况是
new
抛出未处理的
std::bad_alloc
。然后发生的事情是实现定义的

在最佳情况为无操作,最坏情况未定义的情况下,允许编译器将它们考虑为不存在。现在,如果您确实尝试捕获可能的异常:

int main() try {
    int* mem = new int[100];
    return 0;
} catch(...) {
  return 1;
}
。。。然后。

编译器完全可以(但不是必需的)优化原始示例中的分配,根据标准§1.9,在EDIT1示例中更是如此,这通常被称为“假设规则”:

一致性实现需要模拟(仅)抽象机器的可观察行为,如下所述:
[3页条件]

一种更具可读性的表示形式可在网站上找到

有关要点如下:

  • 您没有挥发物,因此1)和2)不适用
  • 您不输出/写入任何数据或提示用户,因此3)和4)不适用。但是,即使您这样做了,他们也会在EDIT1中感到满意(在最初的示例中也可以这样说,尽管从纯理论的角度来看,这是非法的,因为程序流和输出——理论上——不同,但请参见下面的两段)
一个异常,甚至是一个未捕获的异常,是定义良好(不是未定义!)的行为。但是,严格地说,如果
new
抛出(不会发生,另请参见下一段),可观察到的行为将不同,程序的退出代码和程序后面可能出现的任何输出都会不同

现在,在单个小分配的特殊情况下,您可以给编译器“怀疑的好处”,它可以保证分配不会失败。
即使在内存压力非常大的系统上,当可用的分配粒度小于最小分配粒度时,也不可能启动进程,堆也将在调用
main
之前设置好。因此,如果此分配失败,在调用
main
之前,程序将永远不会启动或已经遇到不正常的结束。
到目前为止,假设编译器知道这一点,即使在理论上分配可能抛出,它也是合法的
#include <cstddef>

extern void* operator new(std::size_t n);

template<typename T>
T* create() { return new T(); }

int main() {
    auto result = 0;
    for (auto i = 0; i < 1000000; ++i) {
        result += (create<int>() != nullptr);
    }

    return result;
}
main:                                   # @main
    movl    $1000000, %eax          # imm = 0xF4240
    ret