术语"运算符优先级"和"求值顺序"是编程中非常常用的术语,对于程序员来说非常重要.而且,据我所知,这两个概念是紧密联系在一起的,在谈到表达时,一个离不开另一个.

让我们举一个简单的例子:

int a=1;  // Line 1
a = a++ + ++a;  // Line 2
printf("%d",a);  // Line 3

显然,Line 2会导致未定义的行为,因为100包括:

  1. 在对&和amp;的左操作数和右操作数求值之间(逻辑 AND)、||(逻辑或)和逗号 操作员.例如,在 表达式*p++ != 0 && *q++ != 0,全部 子表达式的副作用 *p++ != 0在任何try 访问q之前完成.

  2. 在三元数的第一个操作数的求值之间

  3. 在完整表达的结尾.这一类包括表达

  4. 在函数调用中输入函数之前.顺序

  5. 在函数返回时,将返回值复制到 调用上下文.(此序列点 仅在C++标准中指定; 它仅隐式存在于 (C.)

  6. 在初始值设定项结束时;例如,在求值为5之后 在第int a = 5;号声明中.

因此,通过第三点:

At the end of a full expression. This category includes expression statements (such as the assignment a=b;), return statements, the controlling expressions of if, switch, while, or do-while statements, and all three expressions in a for statement.

Line 2显然会导致未定义的行为.这说明Undefined BehaviourSequence Points是如何紧密耦合的.

现在让我们再举一个例子:

int x=10,y=1,z=2; // Line 4
int result = x<y<z; // Line 5

现在很明显,Line 5将使变量result存储1.

现在,Line 5中的表达式x<y<z可以求值为:

x<(y<z)(x<y)<z.在第一种情况下,值result将是0,在第二种情况下,值result将是1.但我们知道,当Operator PrecedenceEqual/Same-Associativity开始发挥作用时,因此被评为(x<y)<z.

MSDN Article句话是这样说的:

The precedence and associativity of C operators affect the grouping and evaluation of operands in expressions. An operator's precedence is meaningful only if other operators with higher or lower precedence are present. Expressions with higher-precedence operators are evaluated first. Precedence can also be described by the word "binding." Operators with a higher precedence are said to have tighter binding.

现在,关于上面的文章:

It mentions "Expressions with higher-precedence operators are evaluated first."

这听起来可能不正确.但是,如果我们认为()也是操作员的话,我认为这篇文章并不是说错了,x<y<z(x<y)<z是一样的.我的推理是,如果结合性没有发挥作用,那么完整表达式的求值将变得不明确,因为<不是Sequence Point.

另外,我在Operator Precedence and Associativity上找到的另一个链接是这样的:

This page lists C operators in order of precedence (highest to lowest). Their associativity indicates in what order operators of equal precedence in an expression are applied.

int result=x<y<z的第二个例子为例,我们可以看到在所有3个表达式中,xyz,因为最简单的表达式形式是由一个文本常量或对象组成的.因此,表达式xyz的结果将为rvalues,即分别为1012.因此,现在我们可以将x<y<z解释为10<1<2.

现在,结合性不是开始发挥作用了吗,因为我们现在有两个表达式要计算,要么是10<1,要么是1<2,而且由于运算符的优先级是相同的,they are evaluated from left to right

以最后这个例子作为我的论点:

int myval = ( printf("Operator\n"), printf("Precedence\n"), printf("vs\n"),
printf("Order of Evaluation\n") );

现在在上面的示例中,由于comma运算符具有相同的优先级,因此表达式被求值left-to-right,并且最后printf()的返回值被存储在myval中.

SO/IEC 9899:201x岁以下的J.1 Unspecified behavior岁以下,它提到:

The order in which subexpressions are evaluated and the order in which side effects take place, except as specified for the function-call (), &&, ||, ?:, and comma operators (6.5).

现在我想知道,这样说是否不对:

Order of Evaluation depends on the precedence of operators, leaving cases of Unspecified Behavior.

如果我在问题中说的话有任何错误,我希望得到纠正. 我贴这个问题的原因是因为MSDN的文章在我的脑海里创造了一个念力.是不是Error美元?

推荐答案

是的,MSDN文章是错误的,至少在标准C和C++1方面是错误的.

话虽如此,让我先从一个术语开始说明:在C++标准中,它们(主要是有几个错误)使用"判断"来引用操作数,而"价值计算"则指执行一个操作.因此,当(例如)执行a + b时,ab中的每一个都将被计算,然后执行值计算以确定结果.

很明显,值计算的顺序(主要)是由优先级和关联性控制的——控制值计算基本上就是定义什么是优先级和关联性are.这个答案的其余部分使用"求值"来表示操作数的求值,而不是值计算.

现在,关于判断顺序是由优先级决定的,不是的!就这么简单.举个例子,让我们考虑一下你的x<y<z个例子.根据关联性规则,它解析为(x<y)<z.现在,考虑在堆栈机器上计算该表达式.它完全可以做这样的事情:

 push(z);    // Evaluates its argument and pushes value on stack
 push(y);
 push(x);
 test_less();  // compares TOS to TOS(1), pushes result on stack
 test_less();

这在xy之前计算z,但仍然计算(x<y),然后将比较结果与z进行比较,正如它应该做的那样.

总结:求值顺序与关联性无关.

优先级也是如此.我们可以将表达式更改为x*y+z,并且仍然在xy之前计算z:

push(z);
push(y);
push(x);
mul();
add();

摘要:判断顺序与优先级无关.

当/如果我们加入副作用,这是不变的.我认为,把副作用想象成是由一个单独的执行线程执行的,在下一个序列点(例如,表达式的末尾)有一个join是有教育意义的.所以像a=b++ + ++c;这样的东西可以这样执行:

push(a);
push(b);
push(c+1);
side_effects_thread.queue(inc, b);
side_effects_thread.queue(inc, c);
add();
assign();
join(side_effects_thread);

这也说明了为什么明显的依赖关系也不一定会影响判断的顺序.即使a是赋值的目标,这仍然是判断bca before.还要注意,虽然我在上面把它写成了"线程",但这也可能是pool个线程,所有线程都是并行执行的,所以您也得不到关于一个增量相对于另一个增量的顺序的任何保证.

除非硬件具有对线程安全队列的直接(和cheap)支持,否则这很可能不会在实际实现中使用(即使那样也不太可能).将某些内容放入线程安全队列通常比执行单个增量的开销要大得多,因此很难想象有人会在现实中做到这一点.然而,从概念上讲,这个 idea 符合标准的要求:当您使用前/后递增/递减操作时,您指定的操作将在表达式的该部分求值之后的某个时间发生,并将在下一个序列点完成.

编辑:虽然它不完全是线程,但有些架构确实允许这种并行执行.举几个例子,英特尔安腾和VLIW处理器(如一些DSP)允许编译器指定多条并行执行的指令.大多数VLIW机器都有特定的指令"数据包"大小,限制了并行执行的指令数量.安腾也使用指令包,但在指令包中指定一位,表示当前包中的指令可以与下一个包中的指令并行执行.使用这样的机制,可以并行执行指令,就像在我们大多数人都比较熟悉的架构上使用多个线程一样.

总结:判断顺序与明显的依赖性无关

在下一个序列点之前使用该值的任何try 都会产生未定义的行为——特别是,"另一个线程"在这段时间内(可能)正在修改该数据,您有no种方法可以与另一个线程同步访问.任何使用它的try 都会导致未定义的行为.

举个(无可否认,现在有些牵强)的例子,想象一下你的代码在64位虚拟机上运行,但真正的硬件是一个8位处理器.当您递增一个64位变量时,它会执行一个类似以下的序列:

load variable[0]
increment
store variable[0]
for (int i=1; i<8; i++) {
    load variable[i]
    add_with_carry 0
    store variable[i]
}

如果您读取该序列中间的某个值,您可能只会得到一些修改过的字节,因此您得到的既不是旧值nor,也不是新值nor.

这个确切的例子可能相当牵强,但是一个不那么极端的版本(例如,32位机器上的64位变量)实际上相当常见.

结论

判断的顺序确实not取决于优先级、关联性或(必然)明显的依赖性.试图在表达式的任何其他部分使用已应用前/后递增/递减的变量确实会产生completely个未定义的行为.虽然实际崩溃的可能性不大,但您肯定会得到旧值或新值--您可能会得到完全不同的结果.


1我没有阅读过这篇文章,但相当多的MSDN文章讨论了Microsoft的托管C++和/或C++/CLI(或特定于它们的C++实现),但很少或根本没有指出它们不适用于标准C或C++.这可能会给人一种错误的印象,即他们声称他们决定应用于自己语言的规则实际上适用于标准语言.在这些情况下,这些文章在技术上并不是错误的--它们只是与标准C或C++没有任何关系.如果您try 将这些语句应用于标准C或C++,则结果为FALSE.

C++相关问答推荐

有什么方法可以从Linux中未剥离的二进制文件中取回源代码吗?

命名信号量不会像进程之间同步中假设的那样工作

CC crate 示例不会与C函数链接

C:gcc返回多个错误定义,但msvc—不""'

什么C代码将确定打开的套接字正在使用的网络适配器?

找出文件是否包含给定的文件签名

单指针和空参数列表之间的函数指针兼容性

ESP32在vTaskDelay上崩溃

为什么我不能只在内存地址中添加一个int来寻址任何数组?

对重叠字符串使用MemMove

在C中访问数组中的特定值

如何在GET_STRING输入后对少数几个特定字符串进行C判断?

将变量或参数打包到 struct /联合中是否会带来意想不到的性能损失?

OSDev--双缓冲重启系统

从BIOS(8086)中读取刻度需要多少?

如何摆脱-WIMPLICIT-Function-声明

将size_t分配给off_t会产生符号转换错误

将char*数组深度复制到 struct 中?

C语言中的指针和多维数组

如何为avr atmega32微控制器构建C代码,通过光电二极管捕获光强度并通过串行通信传输数据