“*s = 0”被优化掉。可能的 GCC 13 错误?还是一些未定义的行为?

"*s = 0" being optimized out. Possible GCC 13 bug? Or some undefined behaviour?

提问人:tueda 提问时间:11/2/2023 最后编辑:Charles Duffytueda 更新时间:11/2/2023 访问量:340

问:

在 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 行以某种方式进行了优化。该代码适用于 GCC 12.3。*s = 0

c gcc 未定义行为

评论

2赞 Adrian Mole 11/2/2023
我看不出这怎么可能只是一个错误。这条线很重要,并且具有清晰可见的效果。因此,它不应该被优化出来。我只能猜测编译器对先前出现的相同 C 代码感到“困惑”。*s = 0;
0赞 Adrian Mole 11/2/2023
另请注意,注释掉紧接在后面的 4 行(在您的简化代码中,没有净效应)似乎可以修复编译器的输出。s = SkipAName(s);
3赞 Lundin 11/2/2023
如果它可以得出结论,这个内存位置无论如何都不会被使用,则不需要执行。编译器可以通过内联或类似的优化来预测。我懒得对所有这些优化进行逆向工程......话虽如此,gcc 13 是一个完整的错误盛宴,离稳定版本还很远。它不应该被使用。*s = 0StrICmp
1赞 Fedor 11/2/2023
它类似于 gcc.gnu.org/bugzilla/show_bug.cgi?id=111519,将在 GCC 13.3 中修复
3赞 Lundin 11/2/2023
@Fedor 事实上,Godbolt 上的 gcc(trunk) 添加了一个在 13.2 中不存在的。GCC 13.2 将“每周 GCC 错误”改为“每日 GCC 错误”。我记得有一次我们只有“年度 gcc 错误”......每个人都应该认真考虑退回到 gcc 7 左右,这是最后一个符合 (C11) 标准的稳定版本。mov BYTE PTR [rbp+0], 0

答:

5赞 John Bollinger 11/2/2023 #1

我想知道这是否是 GCC 13 中的错误,或者我是否遇到了一些未定义的行为。

  • 和的参数应该是已转换为无符号字符类型的 s(在自动转换为以匹配参数类型之前)。例:。该问题可能会导致类似的程序显示 UB,但示例中特定程序提供的输入不会产生这种效果。isalnum()tolower()charintisalnum((unsigned char) *s)

  • 该代码假定大写拉丁字母在执行字符集中编码为连续的数字范围,小写拉丁字母也是如此。C 不保证会是这样,如果不是这样,那么程序将无法按预期工作。但是,在测试环境中,该问题根本不可能成为您的问题。

  • SkipAName()对于它认为要跳过的名称,似乎也有奇怪的规则,但这并没有错。

  • TestName()没有调用方可观察的效果。但这并不是错误的,如果观察到的不当行为取决于该函数以及程序中存在的对它的调用,那么这非常奇怪。

  • StrICmp()可以从第二个字符串的末尾运行,从而产生 UB,但这不会发生在您的特定输入中。

  • 修改其输入有点令人讨厌,尤其是它不会始终如一地逆转其更改,因为它们似乎只是为了服务于其内部目的。除其他外,这使得与字符串文字一起使用是不安全的。但是您的示例输入不是字符串文字,并且修改它本身并不是错误的。DoTable()DoTable()

  • 我不喜欢使用参数。我并不完全反对函数修改其参数,但我非常赞成使用描述性名称。目前还不清楚“”这个名字的含义是什么,它既有充分的描述性,又与它的所有用途一致。但是,当然,这并不意味着代码是错误的。DoTable()ss

总的来说,这段代码有一些问题,我总体上不喜欢它,但我没有看到它呈现的未定义行为。在更高的优化级别下,您为 GCC 13 版本显示的输出是错误的。也就是说,它们反映了 GCC 13 中的一个错误。

评论

0赞 Blindy 11/2/2023
“你在更高的优化级别上为 GCC 13 构建显示的输出是错误的。也就是说,它们反映了 GCC 13 中的一个错误“——但你验证过他的说法吗?
0赞 John Bollinger 11/2/2023
什么说法,@Blindy?您的意思是优化的可执行文件不执行其中一项赋值?我认为OP已经充分证明了这一点。无论如何,它都不是问题的核心,从根本上说,这个问题是“这个程序是否有未定义的行为?当然,正如OP所观察到的那样,UB将是GCC构建程序在不同优化级别上表现出行为差异的主要合理理由。*s
0赞 John Bollinger 11/2/2023
或者您的意思是编译的程序确实显示了所描述的行为变化?是的,我已经确认(在 GodBolt 上)该程序在 GCC 13.2 的输出中表现出所描述的与优化级别相关的差异。
0赞 tueda 11/3/2023
根据 gcc.gnu.org/bugzilla/show_bug.cgi?id=112346 的说法,这是一个 GCC 错误,将在 13.3.0 中修复。