【问题标题】:What is exactly undefined when using a C function forwardly declarated without its arguments?当使用没有参数的 C 函数前向声明时,究竟什么是未定义的?
【发布时间】:2019-09-06 12:24:10
【问题描述】:

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

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

我确信在m() 中对f() 的调用是一种未定义的行为,因为f() 应该只接受一个参数,但是:

  • 在 x86 上,GCC 9.1 和 clang 8.0.1 都不会显示任何警告(-Wextra-Weverything 或其他任何内容),除非使用 GCC 和 -O3。没有-O3 的输出是 2,有它的输出是 0。在 Windows 上,MSVC 不会打印任何错误,并且程序只会输出随机数。
  • 在 ARM (Raspberry Pi 3)、GCC 6.3.0 和 clang 3.8.1 上,我观察到相同的错误行为,选项 -O3 仍然输出 0,但正常编译导致 2 与 GCC 和... 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());

我的猜测是-O3 引导 GCC 内联调用,从而使其了解发生了问题;并且堆栈或寄存器中的剩余部分被用作调用的参数。但它怎么还能编译呢?这真的是(未)预期的行为吗?

【问题讨论】:

  • 你试过-Wstrict-prototypes吗?无原型声明是 C 语言的过时错误功能,永远不应该使用。尝试对行为进行推理确实没有意义,因为它不适用于任何正确编写的真实世界代码库。
  • 这确实有效,并且对我大喊大叫,因为我已经声明了没有原型的所有函数......谢谢!
  • 考虑到 C89 引入的原型,'-Wstrict-prototypes' 不是默认的 30 年后有点遗憾。
  • 关于“函数声明和定义中空括号是什么意思”的一般性讨论see this QA

标签: c gcc x86 arm clang


【解决方案1】:

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

如果表示被调用函数的表达式的类型不包含原型,则对每个参数执行整数提升,并且类型为 float 的参数提升为 double。这些被称为默认参数提升。如果参数的数量不等于参数的数量,则行为未定义。...

由于这不是一个约束,编译器不需要产生诊断——行为完全没有被 C 标准定义,这意味着该标准根本没有强加任何要求。

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

【讨论】:

  • 说的有道理,但是我转发声明它怎么可能没有原型呢? int f();是什么意思(特别是为什么和int f(void);不一样)?
  • @NicolasDerumigny int f() 原型说 f 可以采用任意数量的未指定参数。 int f(void) 表示函数 f 完全没有参数。
  • @NicolasDerumigny:函数原型是带有参数类型的声明。在 C 中,int f() 是没有原型的声明;它现在使参数类型灵活/未指定。 (这与 C++ 不同,其中 int f() 表示该函数不带参数。)在 C 中,int f(void) 表示该函数不带参数,因此它与 int f() 不同。
猜你喜欢
  • 2010-10-14
  • 1970-01-01
  • 2021-08-03
  • 2021-03-07
  • 2014-08-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多