使用不带参数的向前声明的C函数时,究竟是什么未定义?

Nic*_*gny 0 c x86 gcc arm clang

摆弄C的旧奇怪兼容行为时,我最终得到了这段代码:

#include <stdio.h>
int f();
int m() {
    return f();
}
int f(int a) {
    return a;
}
int main() {
    f(2);
    printf("%i\n", m());
}
Run Code Online (Sandbox Code Playgroud)

我确定f()in 的调用m()是未定义的行为,因为f()应该只使用一个参数,但是:

  • 在x86上,除非使用GCC和-Wextra-Weverything否则GCC 9.1和clang 8.0.1均不显示任何警告(也不显示in 或其他内容)-O3。然后输出为2,不带-O30,带0。在Windows上,MSVC不会输出任何错误,程序只会输出随机数。
  • 在ARM(Raspberry Pi 3),GCC 6.3.0和clang 3.8.1上,我观察到相同的错误行为,该选项-O3仍输出0,但正常编译会导致GCC为2,而...则为66688。

当出现错误消息时,几乎是您所期望的:(非常有趣,因为a在打印行中没有出现)

foo.c: In function ‘m’:
foo.c:4:9: warning: ‘a’ is used uninitialized in this function [-Wuninitialized]
  return f();
         ^~~
foo.c: In function ‘main’:
foo.c:11:2: warning: ‘a’ is used uninitialized in this function [-Wuninitialized]
  printf("%i\n", m());
Run Code Online (Sandbox Code Playgroud)

我的猜测是,这-O3会导致GCC内联这些呼叫,从而使他们了解发生了问题。并且将堆栈或寄存器中的残差用作调用的参数。但是如何仍然可以编译?这真的是(意外的)行为吗?

Eri*_*hil 6

违反的特定规则是C 2018 6.5.2.2(函数调用)6:

如果表示被调用函数的表达式的类型不包含原型,则对每个参数执行整数提升,并将具有该类型的参数float提升为double。这些称为默认参数提升。如果参数数量不等于参数数量,则行为是不确定的。…

由于这不是约束,因此不需要编译器产生诊断信息-C标准完全没有定义行为,这意味着该标准完全没有要求。

由于该标准没有任何要求,因此忽略问题(或无法识别问题)和诊断问题都是允许的。