C++ C+合法吗+;优化器是否重新排序对clock()的调用?

C++ C+合法吗+;优化器是否重新排序对clock()的调用?,c++,optimization,clock,C++,Optimization,Clock,第四版,第225页写道:只要结果与简单的执行顺序相同,编译器可以对代码重新排序以提高性能。一些编译器,例如VisualC++在发布模式下,将重新排序此代码: #include <time.h> ... auto t0 = clock(); auto r = veryLongComputation(); auto t1 = clock(); std::cout << r << " time: " << t1-t0 << endl;

第四版,第225页写道:只要结果与简单的执行顺序相同,编译器可以对代码重新排序以提高性能。一些编译器,例如VisualC++在发布模式下,将重新排序此代码:

#include <time.h>
...
auto t0 = clock();
auto r  = veryLongComputation();
auto t1 = clock();

std::cout << r << "  time: " << t1-t0 << endl;
#包括
...
自动t0=时钟();
自动r=veryLongComputation();
自动t1=时钟();

std::cout是的,这是合法的-如果编译器可以看到
时钟()调用之间发生的全部代码。

那么,C标准[ISO/IEC 9899:2011]中有一个叫做
的子条款规定:

在抽象机器中,所有表达式都按照 语义。实际的实现不需要评估 表达式,如果它可以推断其值未使用且 产生所需的副作用(包括调用 函数或访问易失性对象)

因此,我真的怀疑这种行为——您描述的行为——符合标准

此外,重组确实会对计算结果产生影响,但如果你从编译器的角度来看,它存在于
int main()
世界中,在进行时间测量时,它会向外窥视,要求内核给出当前时间,回到主世界,外面世界的实际时间并不重要时钟()本身不会影响程序和变量,程序行为也不会影响该时钟()函数。

时钟值用于计算它们之间的差异-这是您要求的。如果在两个测量之间发生了什么事情,从编译器的角度来看是不相关的,因为您要求的是时钟差,测量之间的代码不会影响测量过程

然而,这并不能改变这样一个事实,即所描述的行为非常令人不快

尽管不准确的测量令人不快,但它可能变得更糟,甚至更危险

考虑以下代码取自:


正常编译时,一切正常,但如果应用了优化,memset调用将被优化,这可能导致严重的安全漏洞。为什么它会得到优化?很简单,;编译器再次在它的
main()
世界中思考,并认为memset是一个死存储器,因为变量
pwd
在以后不会使用,并且不会影响程序本身。

至少在我看来,不,这是不允许的。本标准的要求为(§1.9/14):

与完整表达式关联的每个值计算和副作用在与要计算的下一个完整表达式关联的每个值计算和副作用之前排序

编译器可以自由重新排序的程度超出“仿佛”规则(§1.9/1)定义的范围:

本国际标准对一致性实施的结构没有要求。 特别是,它们不需要复制或模拟抽象机器的结构。相反,符合 实现需要模拟(仅)抽象机器的可观察行为,如下所述

这就留下了一个问题:所讨论的行为(由
cout
编写的输出)是否是官方可观察的行为。简而言之,答案是肯定的(§1.9/8):

一致性实施的最低要求是:
[…]
-在程序终止时,写入文件的所有数据应与根据抽象语义执行程序可能产生的结果之一相同

至少在我读到的时候,这意味着与长时间计算的执行相比,对
时钟的调用可以重新安排,如果且仅当它仍然产生与按顺序执行调用相同的输出

但是,如果您想采取额外措施确保正确的行为,您可以利用另一条规定(也包括§1.9/8):

-对易失性对象的访问严格按照抽象机器的规则进行评估

要利用这一点,您需要稍微修改代码,使其类似于:

auto volatile t0 = clock();
auto volatile r  = veryLongComputation();
auto volatile t1 = clock();

现在,我们不必把结论建立在标准的三个独立部分的基础上,仍然只有一个相当确定的答案,我们只需看一句话,就可以得到一个绝对确定的答案——通过这段代码,重新排序
clock
vs。,很明显,长时间的计算是被禁止的。

它肯定是不被允许的,因为它改变了程序的可观察行为(不同的输出)(我不讨论
veryLongComputation()的假设情况)
可能不会消耗任何可测量的时间——给定函数的名称,情况大概不是这样。但即使是这样,也没什么关系)。您不会期望允许重新排序
fopen
fwrite
,是吗

t0
t1
都用于输出
t1-t0
。因此,必须执行
t0
t1
的初始值设定项表达式,并且必须遵循所有标准规则。函数的结果已被使用,因此不可能优化函数调用,尽管它不直接依赖于
t1
,反之亦然,因此人们可能天真地认为将其移动是合法的,为什么不呢。可能在初始化
t1
之后,这与计算无关?
然而,间接地,
t1
的结果当然取决于
veryLongComputation()
的副作用
void GetData(char *MFAddr) {
    char pwd[64];
    if (GetPasswordFromUser(pwd, sizeof(pwd))) {
        if (ConnectToMainframe(MFAddr, pwd)) {
              // Interaction with mainframe
        }
    }
    memset(pwd, 0, sizeof(pwd));
}
auto volatile t0 = clock();
auto volatile r  = veryLongComputation();
auto volatile t1 = clock();