如标准中所定义的,E1 + = E2几乎与E1 = E1 + E2相同,除了E1仅被评估一次。那么,另外,“p + =(* p)++ + c”;导致未定义的行为?
在gcc / g ++(4.7 / 4.4)中尝试以下代码。有两种结果:bxxxxx(g ++ 4.7)或axbxxx(gcc,g ++ 4.4)。如果我们在代码中执行(1)但不执行(2),我们只能获得axbxxx。
#include <stdio.h>
int main() {
char s[] = "axxxxx";
char *p = s;
printf("s = %s in the beginning.\n"
"p is pointed at the %d-th char.\n", s, p - s);
//p = p + (*p)++ * 3 + 2 - 'a' * 3; // (1)
p += (*p)++ * 3 + 2 - 'a' * 3; // (2)
printf("p is moved ahead by %d steps\n", p - s);
printf("s = %s after the operation.\n", s);
return 0;
}
我无法找到导致未定义行为的原因,也无法断言它是gcc的错误。
对于axbxxx结果,我也无法理解为什么操作数或后期++被评估两次(一旦获得值,然后保存它)。由于在标准中说“1 ...被添加到它”,我认为地址只应评估一次。如果post ++的操作数的地址只被评估一次,那么表达式的效果将是相同的,尽管执行赋值的顺序如何。
=== UPDATE ===
在阅读第一条评论中链接的文件后,我认为以下规则可能很重要:
“2)此外,只能访问先前值以确定要存储的值。”
那么,“p = p +(* p)++ * 3 + c”中p的访问是否会被视为* p的“先前值”的一部分,这与要存储的值无关在* p?
国际海事组织,这条规则没有被违反。
答案 0 :(得分:3)
不,p = p + (*p)++ * 3 + c
不会导致任何未定义的行为,假设p
未指向c
。
在这种情况下,可疑部分是对表达式中*p
的值的读取和修改。但是,读取该值是为了确定p
的新值(p
的新值对*p
中读取的值的直接数据依赖性,所以它没有违反要求。
我猜测编译器中的错误实际上源于未指定情况下的错误行为。请注意,该表达式有两个副作用:将新值存储在p
中,并将新值存储在*p
中。没有说明这些副作用发生的顺序。但是,在评估(*p)++
子表达式期间,编译器应该“修复”++
的特定左值参数,以确保新的(递增的)值存储在该精确对象中。看起来编译器的旧版本没有这样做,即首先评估p
的新值,然后通过 new 存储新值*p
值p
。这显然是不正确的。
答案 1 :(得分:1)
原则上,声明p += (*p)++ + c;
可能是正确的。它只是通过某个值推进指针(p
),这恰好由p
指向的变量确定。
您必须确保永远不会将p
增加到s + 7
之外。我没有仔细检查你的代码,看看是否是这种情况(但请注意你正在进行某些编码连续性假设)。
答案 2 :(得分:1)
请注意,p += x;
不等同于p = p + x;
,而是p = p + (x);
。首先评估x
,并将结果添加到p。在标题中给出的没有括号的公式中,p
的中间值可能指向数组之外,这实际上是未定义的行为。只要x
的结果在数组中,代码中的版本就可以了。
6.5.16.2.3 E1 op = E2形式的复合赋值与简单赋值表达式E1 = E1 op(E2)的区别仅在于 左值E1仅评估一次。
J.2未定义的行为 - 将指针加入或减去,或 就在此之外,数组对象和整数类型会产生结果 它没有指向同一个数组对象,或者只是指向同一个数组对象 (6.5.6)。
此UB定义不限于作业的最终结果。
答案 3 :(得分:0)
p += (*p)++ * 3 + 2 - 'a' * 3
是E1 = E1 + E2
形式的 不 。
编辑:发现p+=
仍然没有未定义,因为无论E1 = E1 + E2
右侧每个表达式的评估顺序如何,p
的值都保持不变。