4

可能重复:
未定义的行为和序列点

正如标准中所定义的,E1 += E2 与 E1 = E1 + E2 几乎相同,只是 E1 只计算一次。因此,此外,将 "p += (*p)++ + c"; 导致未定义的行为?

在 gcc/g++ (4.7 / 4.4) 中尝试以下代码。有 2 种结果: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 ...被添加到它”,我认为地址应该只被评估一次。如果后 ++ 的操作数的地址只计算一次,则表达式的效果将是相同的,尽管赋值执行的顺序如何。

=== 更新 ===

在阅读了第一条评论中链接的文档后,我认为以下规则可能很重要:

"2) 此外,只能访问先验值以确定要存储的值。.

那么,“p = p + (*p)++ * 3 + c”中p的访问是否会被认为是*p的“先验值”的一部分,它与要存储在*p中的值无关?

IMO,这条规则没有被违反。

4

4 回答 4

3

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

在这种情况下,有问题的部分是*p表达式内部值的读取和修改。但是,读取该值是为了确定 的新值(新值对读入的值p有直接的数据依赖性),因此它不违反要求。p*p

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

于 2012-10-10T16:10:14.393 回答
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 定义不限于分配的最终结果。

于 2012-10-11T05:24:59.000 回答
1

原则上,该陈述p += (*p)++ + c;可能是正确的。它所做的只是将指针 ( p) 推进某个值,而该值恰好由p指向的变量确定。

你只需要确保你永远不会p超过s + 7. 我没有仔细检查您的代码以查看是否是这种情况(但请注意,您正在做出某些编码连续性假设)。

于 2012-10-10T16:19:18.163 回答
0

p += (*p)++ * 3 + 2 - 'a' * 3不是形式E1 = E1 + E2

  • 在右侧你有一个指针(地址变量)
  • 在左侧,您递增由该指针寻址的变量。

编辑:发现p+=

仍然不是未定义的,因为无论 右侧的每个表达式的求值顺序如何E1 = E1 + E2, 的值都p不会改变。

于 2012-10-10T16:14:31.507 回答