我遇到过这样的情况:跨平台代码在基本赋值语句中的行为不同。
一个编译器首先计算左值,然后计算右值,最后计算赋值。
另一个编译器先执行rvalue,然后执行lvalue,最后执行赋值。
在左值影响右值的情况下,这可能会产生影响,如下所示:
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);
}
上面的例子,行
pmm->m = helper(&mm);
,取决于求值的顺序。如果首先计算左值,则than pmm->m等于mm.m,如果首先计算rvalue than pmm->m等于在堆上分配的mm实例。我的问题是是否有一个C标准来确定计算顺序(没有找到),或者每个编译器都可以选择要做什么。
还有其他类似的陷阱我应该知道吗?
最佳答案
计算=
表达式的语义包括
更新左操作数的存储值的副作用是在左操作数和右操作数的值计算之后排序的。操作数的求值是不排序的。
(C2011,6.5.16/3;增加重点)
强调的规定明确允许您观察到程序在由不同编译器编译时的行为差异。此外,未排序意味着,除其他外,即使在同一程序构建的不同运行中,也允许以不同的顺序进行评估。如果出现未排序求值的函数被多次调用,则允许在同一程序执行过程中的不同调用期间以不同顺序进行求值。
这已经回答了问题,但重要的是要看大局。修改对象或调用这样做的函数是一种副作用(c211,5.1.2.3/2)。因此,这一关键条款开始发挥作用:
如果标量对象上的副作用相对于同一标量对象上的不同副作用或使用同一标量对象的值进行的值计算是未排序的,则行为是未定义的。
(C2011,6.5/2)
被调用函数的副作用是修改存储在main()
变量pmm
中的值,赋值的左侧操作数的计算涉及使用pmm
值的值计算,并且这些值是未排序的,因此行为是未定义的。
要不惜一切代价避免未定义的行为。因为程序的行为是未定义的,所以不限于您观察到的两个选项(以防这还不够糟糕)。c标准对它的功能没有任何限制。相反,它可能会崩溃,清空硬盘的分区表,或者,如果你有合适的硬件,召唤鼻涕恶魔。或者别的什么。其中大多数都不太可能,但最好的观点是,如果程序有未定义的行为,那么程序是错误的。