考虑以下使用new
的简单代码(我知道没有delete[]
,但它与此问题无关):
int main()
{
int* mem = new int[100];
return 0;
}
是否允许编译器优化new
调用?
在我的研究中,g++ (5.2.0)和Visual Studio 2015不会优化new
来电,while clang (3.0+) does。所有测试都是在启用完全优化的情况下完成的(-O3用于g ++和clang,用于Visual Studio的发布模式)。
是不是new
进行系统调用,使编译器无法(并且非法)优化它?
编辑:我现在已经从程序中排除了未定义的行为:
#include <new>
int main()
{
int* mem = new (std::nothrow) int[100];
return 0;
}
clang 3.0 does not optimize that out了,但later versions do。
EDIT2 :
#include <new>
int main()
{
int* mem = new (std::nothrow) int[1000];
if (mem != 0)
return 1;
return 0;
}
答案 0 :(得分:45)
历史似乎是clang遵循N3664: Clarifying Memory Allocation中规定的规则,允许编译器优化内存分配,但是Nick Lewycky points out:
Shafik指出,似乎违反了因果关系,但N3664的起点是N3433,我很确定我们先写了优化,然后再写论文。
因此clang实现了优化,后来成为C ++ 14的一部分实现的提案。
基本问题是这是否是N3664
之前的有效优化,这是一个棘手的问题。我们必须转到草案C ++标准部分1.9
程序执行中所涵盖的as-if rule,其中包含(强调我的):
本国际标准中的语义描述定义了一个 参数化非确定性抽象机。这个国际 标准对符合结构没有要求 实现。特别是,他们不需要复制或模仿 抽象机器的结构。相反,符合实现 需要模仿(仅)抽象的可观察行为 机器,如下所述。 5
注意5
说:
此规定有时称为“as-if”规则,因为 实施可以自由地忽视任何要求 国际标准只要结果就好像要求一样 从可观察的角度来看,已经服从了 该计划的行为。例如,实际的实施需要 如果它可以推导出它的值,则不评估表达式的一部分 未使用,没有影响可观察行为的副作用 制作该节目。
由于new
可以抛出一个具有可观察行为的异常,因为它会改变程序的返回值,这似乎反对 as-if规则允许。
虽然,可以说它是实现细节何时抛出异常,因此即使在这种情况下clang也可以决定它不会导致异常,因此忽略new
调用不会违反 as-if规则。
在 as-if规则下似乎也有效,以优化对非投掷版本的调用。
但是我们可以在不同的翻译单元中使用替换全局运算符new,这可能会导致这会影响可观察行为,因此编译器必须有某种方式证明这不是这种情况,否则它将无法在不违反 as-if规则的情况下执行此优化。以前版本的clang确实在这种情况下优化为this godbolt example shows,它是通过Casey here提供的,采用此代码:
#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
这确实看起来过于激进,但后来的版本似乎没有这样做。
答案 1 :(得分:18)
N3664允许这样做。
允许实现省略对可替换全局分配函数的调用(18.6.1.1,18.6.1.2)。当它这样做时,存储由实现提供,或者通过扩展另一个新表达式的分配来提供。
此提议是C ++ 14标准的一部分,因此在C ++ 14中,编译器 允许优化new
表达式(即使它可能会抛出)。
如果您查看Clang implementation status,请明确说明他们确实实施了N3664。
如果在C ++ 11或C ++ 03中编译时发现此行为,则应填写错误。
请注意,在C ++ 14之前,动态内存分配是程序的可观察状态的一部分(虽然我目前找不到该引用),所以一致的实现不是在这种情况下允许应用 as-if 规则。
答案 2 :(得分:10)
请记住,C ++标准告诉了正确的程序应该做什么,而不是它应该如何做。它根本无法告诉后者,因为在编写标准并且标准必须对它们有用之后,新架构能够并且确实出现了。
new
不一定是一个系统调用。有些计算机可以在没有操作系统的情况下使用,也没有系统调用的概念。
因此,只要结束行为没有改变,编译器就可以优化任何一切。包括public class MyException extends Exception {
public MyException (Exception ex) {
super(ex);
}
public MyException (String message) {
super(message);
}
public MyException (Exception ex,String moduleKey) {
super(ex, moduleKey);
}
public MyException (Exception ex, String moduleKey, String message) {
super(ex, moduleKey, message);
}
有一点需要注意
可以在不同的翻译单元中定义替换全局运算符new
在这种情况下,新的副作用可能是无法优化的。但是,如果编译器可以保证新操作符没有副作用,如果发布的代码是整个代码就是这种情况,那么优化是有效的。
新的可以抛出std :: bad_alloc不是必需的。在这种情况下,当new被优化时,编译器可以保证不会抛出任何异常并且不会发生副作用。
答案 3 :(得分:7)
完全允许(但不要求)编译器优化原始示例中的分配,在标准的§1.9的EDIT1示例中更是如此,这通常是称为 as-if规则:
需要符合实现来模拟(仅)抽象机器的可观察行为,如下所述:
[3页条件]
cppreference.com提供了一种更易于阅读的表示形式。
相关要点是:
一个例外,即使是未被捕获的例外,也是明确定义的(未定义!)行为。但是,严格来说,如果new
抛出(不会发生,请参见下一段),可观察的行为将会有所不同,包括程序的退出代码和程序后面可能跟随的任何输出
现在,在单个小分配的特定情况下,您可以为编译器提供“怀疑的好处”它可以保证分配不会失败。
即使在内存压力非常大的系统上,当您的可用最小分配粒度小于最小分配粒度时,也无法启动进程,并且在调用main
之前也会设置堆。因此,如果此分配失败,程序将永远不会启动,或者在main
被调用之前就已经遇到了不合适的结束。
假设编译器知道这一点,即使分配理论上可以抛出,甚至优化原始示例也是合法的,因为编译器可以实际上保证它不会发生。
&lt;稍微犹豫不决&gt;
另一方面,它是不允许(并且你可以观察到,编译器错误)来优化你的EDIT2示例中的分配。消耗该值以产生外部可观察的效果(返回码)
请注意,如果将new (std::nothrow) int[1000]
替换为new (std::nothrow) int[1024*1024*1024*1024ll]
(这是4TiB分配!),即 - 在当前计算机上 - 保证失败,它仍会优化呼叫。换句话说,尽管您编写了必须输出0的代码,但它返回1。
@Yakk提出了一个很好的论据:只要永远不会触及内存,就可以返回指针,而不需要实际的RAM。在EDIT2中优化分配甚至是合理的。我不确定谁是对的,谁在这里错了。
由于操作系统需要创建页表,因此在没有至少两位数GB RAM的机器上进行4TiB分配几乎可以保证失败。当然,C ++标准并不关心页表或操作系统正在做什么来提供内存,这是事实。
但另一方面,假设“如果没有触及内存,这将有效”确实依赖确切地说明这样的细节以及操作系统提供的内容。假设如果实际上不需要未触及它的RAM,则仅为真,因为 OS提供虚拟内存。这意味着操作系统需要创建页面表(我可以假装我不知道它,但这并不会改变我依赖它的事实。)
因此,我认为首先假设一个然后说“但我们不关心另一个”并非100%正确。
所以,是的,编译器可以假设只要没有触及内存就可以完全实现4TiB分配,并且它可以假设它通常是有可能成功。它甚至可能认为它可能会成功(即使它没有)。但我认为,无论如何,当有可能出现故障时,你永远不会认为必须有效。并且不仅存在失败的可能性,在该示例中,失败甚至是更可能的可能性。
&lt; /略有未定&gt;
答案 4 :(得分:2)
您的代码段中可能发生的最糟糕情况是new
抛出std::bad_alloc
,这是未处理的。然后发生的是实现定义。
最好的情况是无操作,最坏的情况没有定义,编译器可以将它们分解为不存在。现在,如果您真的尝试捕获可能的异常:
int main() try {
int* mem = new int[100];
return 0;
} catch(...) {
return 1;
}