使用 GCC 13.2,以下代码的输出取决于优化级别:
#include <ctype.h>
#include <stdio.h>
char *SkipAName(char *s) {
if (('A' <= *s && *s <= 'Z') || ('a' <= *s && *s <= 'z') || *s == '_' ||
*s == '$') {
if (*s == '$') {
s++;
}
while (isalnum(*s)) {
s++;
}
if (*s == '_') {
s++;
}
}
return s;
}
int TestName(char *name) {
while (*name) {
name++;
}
return 0;
}
int StrICmp(char *s1, char *s2) {
while (*s1 && tolower(*s1) == tolower(*s2)) {
s1++;
s2++;
}
return tolower(*s1) - tolower(*s2);
}
int DoTable(char *s) {
char *name, c;
do {
name = s;
s = SkipAName(s);
c = *s;
*s = 0;
TestName(name);
*s = c;
if (*s == '(') {
break;
}
if (*s != ',') {
printf("Error 1\n");
return 1;
}
*s = 0;
if (StrICmp(name, "sparse") == 0) {
} else {
printf("Error 2\n");
return 1;
}
*s++ = ',';
while (*s == ',') {
s++;
}
} while (*s);
printf("OK\n");
return 0;
}
int main() {
char buf[] = "sparse,C(1)";
DoTable(buf);
return 0;
}
$ gcc-13 -O0 test.c && ./a.out
OK
$ gcc-13 -O1 test.c && ./a.out
OK
$ gcc-13 -O2 test.c && ./a.out
Error 2
$ gcc-13 -O3 test.c && ./a.out
Error 2
代码来自这个项目,我尝试制作一个最小的可重现示例;这就是为什么这段代码可能看起来很尴尬。另请参阅此问题。
我想知道这是否是 GCC 13 中的一个错误,或者我是否遇到了一些未定义的行为。使用 Compiler Explorer,看起来第 52 行的
*s = 0
已经被优化掉了。该代码在 GCC 12.3 上运行良好。
我想知道这是否是 GCC 13 中的一个错误,或者我是否遇到了一些未定义的行为。
isalnum()
和tolower()
的参数应该是已转换为char
类型的unsigned char
s (在自动转换为int
以匹配参数类型之前)。例如:isalnum((unsigned char) *s)
。该问题可能会导致类似的程序显示 UB,但示例中特定程序提供的输入不会产生这种效果。
代码假设大写拉丁字母被编码为执行字符集中的连续数字范围,小写拉丁字母也是如此。 C 不保证一定会如此,如果不是这样,那么程序将无法按预期运行。但是,在您的测试环境中,这个问题根本不可能成为您的问题。
SkipAName()
对于它认为要跳过的名称似乎也有奇怪的规则,但这并不是错误的。
TestName()
没有调用者可观察到的效果。但这并没有错,如果观察到的不当行为取决于该函数以及程序中存在的对该函数的调用,那么这就非常奇怪了。
StrICmp()
可以从第二个字符串的末尾运行,从而产生 UB,但对于您的特定输入不会发生这种情况。
DoTable()
修改其输入有点令人讨厌,尤其是它并没有始终如一地逆转其更改,因为它们似乎只是为了服务于其内部目的。除此之外,这使得使用字符串文字变得不安全。但是您的示例输入不是字符串文字,并且 DoTable()
修改它并不是本质上错误的。
我不喜欢
DoTable()
使用参数s
。我并不完全反对函数修改其参数,但我非常赞成使用描述性名称。完全不清楚“s
”这个名字的含义是什么,它既具有充分的描述性,又与它的所有用途相一致。但当然,这并不会使代码出错。
总的来说,这段代码有一些问题,我总体上不喜欢它,但我发现其中没有未定义的行为。您在更高优化级别上为 GCC 13 构建显示的输出是错误的。也就是说,它们反映了 GCC 13 中的一个错误。