【问题标题】:Is `while(*p++ = *(p+1));` undefined behavior?`while(*p++ = *(p+1));` 是未定义的行为吗?
【发布时间】:2017-12-16 09:02:00
【问题描述】:

我有代码使用单行 while 循环语句来操作 C 字符串。 使用 MSVC2015 编译时它可以完美运行,但使用 TDM-GCC (gcc (tdm-1) 5.1.0) 编译时会产生不同的结果。

这是一个显示问题的最小示例。代码用下一个字符覆盖当前字符,一遍又一遍地重复,直到将当前字符设置为\0

#include <stdio.h>

int main()
{
    char buf[999] = "Foobar", *p = buf;
    while(*p++ = *(p+1));
    printf("buf = %s\n", buf);
    return 0;
}

使用 MSVC2015 编译代码时,输​​出如预期的那样为buf = oobar。然而,使用 TDM-GCC,输出为 buf = obar

如果我将 while 语句更改为 while(*p = *(p+1)) { ++p; },两个编译器都会给出预期的结果 buf = oobar。似乎通过将后增量运算符放在表达式中,我以某种方式触发了未定义的行为。

我的问题是,为什么代码在使用不同的编译器编译时表现不同?将增量运算符放在非平凡的 while 语句中是错误的(或非标准的)吗?我是否触发了未定义的行为?如果是这样,代码应该如何根据 C 标准表现?如果不是,这应该怪谁? TDM-GCC? MSVC?

更新:对于那些和我有同样疑问的人,答案是:是的,代码调用了 UB。 定义明确的方法是这样做:while(*p = *(p+1)){++p;}


有人问我们为什么要这样编码。这是这个习语可能有用的场景。

#include <stdio.h>
#include <Windows.h>

static void EscapeDquote(char * const sz)
{
    char *p = sz;
    BOOL bs = FALSE;
    for (; *p; ++p)
    {
        if (*p == '\\') {
            bs = !bs;
            continue;
        }
        if (*p == '\"') {
            if (bs) {
                /*
                    discard prev char (backslash before dquote)
                    overwrite with next char until null-termi
                */
                char *q = --p;
                /* OLD version, not OK for GCC */
                /* while(*q++ = *(q+1)); */
                /* Safer version, works in GCC as well: */
                while(*q = *(q+1)){++q;}
            }
        }
        bs = FALSE;
    }
}

int main()
{
    /* "call \"D:\foo bar.exe\" */
    char szTest[] = "call \\\"D:\\foo bar.exe\\\"";
    printf("Before = %s\n", szTest);
    EscapeDquote(szTest);
    printf("After  = %s\n", szTest);
    return 0;
}

【问题讨论】:

  • 为什么一开始就这样写代码?
  • 可能在某处遗漏了一个序列点,但想知道原因让我头疼。当您编写这样的代码时,您的脑海中已经存在未定义的行为。如果您想要速度,为什么不尝试组装?至少它是确定性的
  • @rsp p 在表达式中只修改一次
  • “post”的实际定义,就像在后增量中一样,显然有些悬而未决。一些编译器在语句被解析后应用它,而其他编译器在左侧之后应用它。为了安全起见,请将 p++ 放在 while 块中,这样您就可以保证您将得到什么。
  • @MartinJames 你是在暗示如果我对这样的代码有疑问,我不会在 SO 上问它吗?严重地?我知道我的代码可能是 UB,这就是为什么我问,“这是 UB 吗?”,但你说的好像我在这里问它是在宣传糟糕的代码。

标签: c while-loop undefined-behavior post-increment


【解决方案1】:

如果您使用的是 GCC 编译器,请使用 -Wall。在 C 和 C++ 中,这确实是一种未定义的行为。

观看现场演示here

查看编译器给出的诊断

main.cpp: In function 'int main()':

main.cpp:6:13: warning: operation on 'p' may be undefined [-Wsequence-point]

     while(*p++ = *(p+1));

            ~^~

【讨论】:

  • 我的 TDM-GCC 只会使用相同的命令行显示 warning: suggest parentheses around assignment used as truth value [-Wparentheses]。你用的是什么版本的 gcc?
【解决方案2】:

这是未定义的行为,因为以下两个操作是未排序的:

  • p++ 中写入p
  • p(p+1) 中的读取

【讨论】:

  • 谢谢,所以如果我将增量​​运算符放在 while 块中,代码行为将是明确定义的,对吧?
  • @raymai97 如果您的意思是 while( p[0] = p[1] ) { ++p; } 是的,那将是明确定义的(并且可读性更高)。我不确定您是否打算在p[0] == 0 的情况下增加p;如果是这样,那么你需要写一些不同的东西
【解决方案3】:

是的,这是未定义的行为,因为Clang 编译器给出以下错误:

source_file.cpp:6:13: warning: unsequenced modification and access to 'p' [-Wunsequenced]
    while(*p++ = *(p+1));
            ^      ~

C11:6.5 表达式:

如果标量对象的副作用是未排序的 对同一标量对象或值的不同副作用 使用相同标量对象的值进行计算,行为是 未定义。如果有多个允许的排序 表达式的子表达式,如果这样的表达式的行为是未定义的 未排序的副作用发生在任何排序中

【讨论】:

  • 我想我需要一个更好的编译器。即使使用-Wall,我的 TDM-GCC 也不会告诉我这一点。谢谢你给我看这个。
【解决方案4】:

这是未定义的行为,因为有未定义的序列点。

如果您希望它在单行中,解决方法是 while ((*p = *(p + 1)) &amp;&amp; p++);

现在您将首先设置您的指针,如果分配的值是非零,您将继续定义序列到p++ 语句。如果赋值为,while循环将结束意味着字符串被移位。

【讨论】:

  • 如果我这样做,行为会很明确,对吧?
  • @raymai97 这是在 C 中定义的方式
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-21
  • 1970-01-01
  • 1970-01-01
  • 2015-10-08
相关资源
最近更新 更多