编译器可以在您最初的示例中优化分配,但这不是必需的。根据标准 §1.9,尤其是通常称为“as-if规则”的部分,编译器甚至可以在EDIT1示例中进行更多优化:
符合要求的实现只需要模拟抽象机器的可观察行为,具体如下所述:
[3页条件]
可在
cppreference.com上找到更易读的表述。
相关要点包括:
- 您没有使用任何volatile关键字,因此1)和2)不适用。
- 您没有输出/写入任何数据或提示用户,因此 3)和4)不适用。但即使是这样,它们在EDIT1中也将明显得到满足(从纯理论角度看,原始示例也属于非法操作,因为程序流程和输出从理论上讲是不同的,但请参阅下面两段落)。
异常,即使是未捕获的异常,也是定义良好的(而不是未定义的!)行为。但严格来说,如果new引发异常(不太可能发生,下一段还有说明),那么可观察的行为将是不同的,包括程序的退出代码和随后可能出现在程序中的任何输出。
现在,在特定情况下,即小额单个分配的情况下,您可以给编译器一个“怀疑的好处”,即它可以保证分配不会失败。
即使在内存压力非常大的系统下,当可用内存小于最小分配粒度时,甚至无法启动进程,并且堆也会在调用main之前设置。因此,如果这个分配失败了,程序将在main被调用之前永远不会启动或已经遇到了不优雅的结束。
就此而言,即使分配理论上可能引发异常,假设编译器知道这一点,甚至可以优化原始示例,因为编译器可以实际保证它不会发生。
另一方面,编译器不允许(并且您可以观察到这是编译器错误)在EDIT2示例中优化掉分配。该值被使用以产生外部可观察效果(返回代码)。
请注意,如果您将
new(std::nothrow)int[1000]
替换为
new(std::nothrow) int[1024 * 1024 * 1024 * 1024ll]
(即4TiB分配!),它仍会优化掉此调用。换句话说,尽管您编写了必须输出0的代码,但它仍会返回1。
@Yakk提出了一个很好的反驳观点:只要不触及内存,就可以返回指针,而不需要实际的RAM。因此,在EDIT2中优化掉分配甚至是合法的。我不确定谁是对的,谁是错的。
在没有至少两位数千兆字节数量的RAM的机器上,进行4TiB的分配几乎肯定会失败,因为操作系统需要创建页表。当然,C++标准并不关心页表或者操作系统为提供内存所做的工作,这是真的。
但另一方面,“如果不触及内存,这将起作用”的假设确实依赖于这样一个细节和操作系统提供的东西。假设如果未触及内存,则实际上不需要RAM,这只有在操作系统提供虚拟内存的情况下才是正确的。这意味着操作系统需要创建页表(我可以假装我不知道,但这并不改变我仍然依赖它的事实)。
因此,我认为首先假设一件事,然后说“但我们不关心其他事情”并不完全正确。
因此,是的,编译器可以假设在未触及内存的情况下,4TiB的分配通常是完全可能的,并且可以假设成功通常是可能的。它甚至可以假设它很可能成功(即使它实际上不会)。但是,我认为在任何情况下,当存在失败的可能性时,您永远不允许假设某些内容必须工作。而且,在那个例子中,失败甚至是更有可能的情况。