简体   繁体   English

C赋值语句的评估顺序

[英]C order of evaluation of assignment statement

I've been encountered on a case where cross-platform code was behaving differently on a basic assignment statement. 我遇到过跨平台代码在基本赋值语句中表现不同的情况。

One compiler evaluated the Lvalue first, Rvalue second and then the assignment. 一个编译器首先评估Lvalue,然后评估Rvalue,然后评估分配。

Another compiler did the Rvalue first, Lvalue second and then the assignment. 另一个编译器首先执行Rvalue,然后执行Lvalue,然后执行赋值。

This may have impact in case Lvalue influence the value of Rvalue as shown in the following case: 如果Lvalue影响Rvalue的值,这可能会产生影响,如下例所示:

struct MM {
    int m;
}
int helper (struct MM** ppmm ) { 
    (*ppmm) = (struct MM *) malloc (sizeof (struct MM)); 
    (*ppmm)->m = 1000;
    return 100;
}

int main() { 
    struct MM mm = {500};
    struct MM* pmm = &mm
    pmm->m = helper(&pmm);
    printf(" %d %d " , mm.m , pmm->m);
}

The example above, the line pmm->m = helper(&mm); 上面的例子,行pmm->m = helper(&mm); , depend on the order of evaluation. ,取决于评估的顺序。 if Lvalue evaluated first, than pmm->m is equivalent to mm.m, and if Rvalue calculated first than pmm->m is equivalent to the MM instance that allocated on heap. 如果首先评估左值,则pmm-> m等于mm.m,如果首先计算的Rvalue大于pmm-> m,则等于堆上分配的MM实例。

My question is whether there's a C standard to determine the order of evaluation (didn't find any), or each compiler can choose what to do. 我的问题是,是否有一个C标准来确定评估的顺序(没有找到任何),或者每个编译器都可以选择做什么。 are there any other similar pitfalls I should be aware of ? 还有其他类似的陷阱我应该知道吗?

The semantics for evaluation of an = expression include that 评估=表达式的语义包括

The side effect of updating the stored value of the left operand is sequenced after the value computations of the left and right operands. 在左右操作数的值计算之后,对更新左操作数的存储值的副作用进行排序。 The evaluations of the operands are unsequenced. 对操作数的评估是不确定的。

(C2011, 6.5.16/3; emphasis added) (C2011,6.5.16 / 3;重点补充)

The emphasized provision explicitly permits your observed difference in the behavior of the program when compiled by different compilers. 强调的规定明确允许您在不同编译器编译时观察到程序行为的差异。 Moreover, unsequenced means, among other things, that it is permissible for the evaluations to occur in different order even in different runs of the very same build of the program. 此外, 无序表示,除其他外,即使在程序的同一构建的不同运行中,也允许评估以不同的顺序发生。 If the function in which the unsequenced evaluations appear were called more than once, then it would be permissible for the evaluations to occur in different order during different calls within the same execution of the program. 如果多次调用出现未序列评估的函数,则允许在同一程序执行期间的不同调用期间以不同的顺序进行评估。

That already answers the question, but it's important to see the bigger picture. 这已经回答了这个问题,但重要的是要看到更大的图景。 Modifying an object or calling a function that does so is a side effect (C2011, 5.1.2.3/2). 修改对象或调用执行此操作的函数是副作用(C2011,5.1.2.3 / 2)。 This key provision therefore comes into play: 因此,这项关键条款发挥作用:

If a side effect on a scalar object is unsequenced relative to either a different side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined. 如果相对于对同一标量对象的不同副作用或使用相同标量对象的值进行值计算,对标量对象的副作用未被排序,则行为未定义。

(C2011, 6.5/2) (C2011,6.5 / 2)

The called function has the side effect of modifying the value stored in main() 's variable pmm , evaluation of the left-hand operand of the assignment involves a value computation using the value of pmm , and these are unsequenced, therefore the behavior is undefined. 被调用的函数具有修改存储在main()的变量pmm的值的副作用,赋值的左侧操作数的评估涉及使用pmm的值进行的值计算,并且这些是未排序的,因此行为是未定义。

Undefined behavior is to be avoided at all costs. 不惜一切代价避免未定义的行为。 Because your program's behavior is undefined, is not limited to the two alternatives you observed (in case that wasn't bad enough). 由于您的程序行为未定义,因此不限于您观察到的两种替代方案(如果不够糟糕)。 The C standard places no limitations whatever on what it may do. C标准对其可能做的事情没有任何限制。 It might instead crash, zero out your hard drive's partition table, or, if you have suitable hardware, summon nasal demons. 它可能会崩溃,将您的硬盘驱动器的分区表归零,或者,如果您有合适的硬件,则召唤鼻子恶魔。 Or anything else. 或其他任何东西。 Most of these are unlikely, but the best viewpoint is that if your program has undefined behavior then your program is wrong . 其中大部分都不太可能,但最好的观点是,如果你的程序有不确定的行为,那么你的程序就错了

When using the simple assignment operator: = , the order of evaluation of operands is unspecified. 使用简单赋值运算符时: = ,未指定操作数的评估顺序。 There is also no sequence point in between the evaluations. 评估之间也没有序列点。

For example if you have two functions: 例如,如果您有两个功能:

*Get() = logf(2.0f);

It is not specified in which order they are called at any time, and yet this behavior is completely defined. 它没有指定在任何时候调用它们的顺序,但是这个行为是完全定义的。

A function call will introduce a sequence point. 函数调用将引入序列点。 It will happen after the evaluation of the arguments and before the actual call. 它将在评估参数之后和实际调用之前发生。 The operator ; 经营者; will also introduce a sequence point. 还将介绍一个序列点。 This is important because an object must not be modified twice without an intervening sequence point, otherwise the behavior is undefined. 这很重要,因为在没有插入序列点的情况下,对象不得修改两次,否则行为未定义。

Your example is particularly complicated due to unspecified behavior, and may have different results, depending the left or right operand is evaluated first. 由于未指定的行为,您的示例特别复杂,并且可能会有不同的结果,具体取决于首先计算左或右操作数。

  1. The left operand is evaluated first. 首先评估左操作数。

The left operand is evaluated and the pointer pmm will point to the struct mm . 计算左操作数,指针pmm将指向结构mm Then the function is called, and a sequence point occurs. 然后调用该函数,并出现一个序列点。 it modifies the pointer pmm by pointing it to allocated memory, followed by a sequence point because of the operator ; 它通过将指针指向已分配的内存来修改指针pmm ,然后由于操作符而后跟一个序列点; . Then it stores the value 1000 to the member m , followed by another sequence point because of ; 然后它将值1000存储到成员m ,然后是另一个序列点,因为; . The function returns 100 and assigns it to the left operand, but since the left operand was evaluated first, the value 100, it is assigned to the object mm , more specifically its member m . 该函数返回100并将其分配给左操作数,但由于左操作数首先被计算,值100,它被赋值给对象mm ,更具体地说是它的成员m

mm->m has the value 100 and ppm->m has the value 1000. This is defined behavior, no object is modified twice in-between sequence points. mm->m的值为100,而ppm->m的值为1000.这是定义的行为,在序列点之间没有对象被修改两次。

  1. The right operand is evaluated first. 首先评估右操作数。

The function is called first, the sequence point occurs, it modifies the pointer ppm by pointing it to new allocated struct, followed by a sequence point. 首先调用该函数,发生序列点,它通过将指针指向新分配的结构,然后是序列点来修改指针ppm Then it stores the value 1000 to the member m , followed by a sequence point. 然后它将值1000存储到成员m ,然后是序列点。 Then the function returns. 然后函数返回。 Then the left operand is evaluated, ppm->m will point to the new allocated struct, and its member m , is modified by assigning it the value 100. 然后评估左操作数, ppm->m将指向新分配的结构,并通过为其赋值100来修改其成员m

mm->m will have the value 500 since it was never modified, and pmm->m will have the value 100. No object was modified twice in-between sequence points. mm->m将具有值500,因为它从未被修改,并且pmm->m将具有值100.没有对象在序列点之间被修改两次。 The behavior is defined. 行为已定义。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM