【发布时间】: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