为什么 C 函数的无效使用可以在没有任何警告的情况下正常编译?

Why does an invalid use of C function compile fine without any warnings?

我更喜欢 C++ 而不是 C,但是这个简单的代码示例让我大吃一惊:

int foo() {
    return 3;
}

int main() {
    int x;
    foo(&x);
    return x;
}

https://godbolt.org/z/4ov9nTzjM

没有警告,没有链接问题,但此代码仍然无效。

当一些初学者提出问题时代码有这个奇怪的问题时,我偶然发现了这个问题。有人在其他网站(none 英文网站)上提出了问题,我想向他提供更好的解释为什么它编译(并学习一些关于 C 的知识)。他的 original code 如果有人需要看的话。

我怀疑这个 sis 与隐式函数声明有某种关系,但我不完全确定。为什么编译器不抱怨 foo 是用参数调用的?

更新:

这里是an assembly:

foo:
        push    rbp
        mov     rbp, rsp
        mov     eax, 3
        pop     rbp
        ret
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        lea     rax, [rbp-4]
        mov     rdi, rax
        mov     eax, 0
        call    foo
        mov     eax, DWORD PTR [rbp-4]
        leave
        ret

如您所见,x 仍未初始化。

这个函数是 100% 正确的。

在C语言中int foo() {表示:定义函数foo返回int并取未指定个参数。

您的困惑来自 C++ plus,其中 int foo(void)int foo() 的意思完全相同。

C语言中定义不接受任何参数的函数你需要将其定义为:

int foo(void) {

编译器应该为这个程序发出一条消息

int foo() {
    return 3;
}

int main() {
    int x;
    foo(&x);
    return x;
}

因为函数 foo 使用参数调用,尽管它的标识符列表是空的。所以程序有未定义的行为。

根据 C 标准 *6.7.6.3 函数声明符(包括原型) )

14 An identifier list declares only the identifiers of the parameters of the function. An empty list in a function declarator that is part of a definition of that function specifies that the function has no parameters. The empty list in a function declarator that is not part of a definition of that function specifies that no information about the number or types of the parameters is supplied.

所以程序无效

您可以通过以下方式使其成为有效程序

int foo();

int main() {
    int x;
    foo(&x);
    return x;
}

int foo( int *p ) {
    return 3;
}

尽管编译器可以发出警告,指出未使用参数 p

在这种情况下,函数声明不是它的定义意味着没有关于参数数量和类型的信息。

此声明与 C++ 中的 C 相反

int foo();

相当于

int foo( void );

空括号是不提供类型检查的函数声明的旧 C 语法的一部分。在创建带有类型的参数列表之前,函数定义的形式如下:

int foo(a, b, c)
char  a;
int  *b;
float c;
{
    // body of function
}

然后不是定义的函数声明将只使用 (),如 int foo();。所以函数定义会显示函数期望的参数,包括参数的数量和类型,但声明不会。

当然,函数定义在编译源文件时通常是不可见的,因为定义在其他文件中,尽管当然可以提供声明,通常在头文件中。所以,当一个函数被调用时,编译器通常不会看到定义,也不会知道函数需要什么参数。所以它无法检查参数。因此,不要求编译器提供与使用此旧语法声明的函数不匹配的诊断消息。

程序员负责使参数匹配。函数定义确实指定了函数有多少参数并指定了它们的类型,并且 C 标准没有定义不匹配时的行为。但那是 运行 时间的问题;它不是编译器需要诊断的东西,如果函数定义对它不可见,编译器也不能诊断它。

当定义可见时,编译器可以发出诊断,尤其是当定义出现在调用之前时。虽然 GCC 不诊断这个,Clang does,如果定义先出现。所以这对 GCC 开发人员来说是一个错失的机会。

创建函数原型(带有参数类型的函数声明)的语法时,() 不能用于指示函数没有参数,因为它已经用于旧语法.所以符号 (void) 就是为此发明的;要在定义之外声明函数不带参数,您可以使用 (void),如 int foo(void);。这也可以用在函数定义中,int foo(void) { … }.

当函数声明具有这种新形式时,编译器需要检查函数调用中参数的数量和类型,并在不匹配时发出诊断。

函数

int foo() {
    return 3;
}

具有空参数列表在 C 中意味着 具有未定义参数列表的函数。它是 ANSI 之前时代的遗留语法。今天它被认为是一个不完整的函数定义(因为您稍后可以提供更精确的定义)。

编译器没有发出警告,因为只有在没有定义函数接口的函数原型并且编译器不检查传递给函数的参数时,这种构造才可用。

它是旧的、遗留的 C 语法用法,如下所示(实际上今天仍然被接受,并且当许多语言专家发现它仍然被接受时感到惊讶):

/* no function return type, defaults to int */
main(argc, argv)
/* no definition for argc, defaults to int */
char *argv[]; /* parameters are defined between the parameter list
               * and the function body block */
{
    /* ... */
}

这是 fortran 的回忆。

定义无参函数的正确方法是:

int foo(void)
{
    /* ... */
}

这会使编译器在您尝试使用参数调用它时发出错误(而不是警告)。