将“p = p +(* p)++ * 3 + c;”导致未定义的行为?

时间:2012-10-10 16:02:12

标签: c standards undefined-behavior

  

可能重复:
  Undefined Behavior and Sequence Points

如标准中所定义的,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?

国际海事组织,这条规则没有被违反。

4 个答案:

答案 0 :(得分:3)

不,p = p + (*p)++ * 3 + c不会导致任何未定义的行为,假设p未指向c

在这种情况下,可疑部分是对表达式中*p的值的读取和修改。但是,读取该值是为了确定p的新值(p的新值对*p中读取的值的直接数据依赖性,所以它没有违反要求。

我猜测编译器中的错误实际上源于未指定情况下的错误行为。请注意,该表达式有两个副作用:将新值存储在p中,并将新值存储在*p中。没有说明这些副作用发生的顺序。但是,在评估(*p)++子表达式期间,编译器应该“修复”++的特定左值参数,以确保新的(递增的)值存储在该精确对象中。看起来编译器的旧版本没有这样做,即首先评估p的新值,然后通过 new 存储新值*pp。这显然是不正确的。

答案 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' * 3E1 = E1 + E2形式的

  • 在右侧,您有一个指针(地址变量)
  • 在左侧增加此指针寻址的变量。

编辑:发现p+=

仍然没有未定义,因为无论E1 = E1 + E2右侧每个表达式的评估顺序如何,p的值都保持不变。