【问题标题】:C Function with parameter without type indicator still works?带有没有类型指示符的参数的C函数仍然有效吗?
【发布时间】:2013-08-14 15:56:31
【问题描述】:

代码如下:

int func(param111)
{
    printf("%d\n", param111);
    return param111;
}

int main()
{
    int bla0 = func(99);
    int bla1 = func(10,99);
    int bla2 = func(11111110,99,10001);
    printf("%d, %d, %d\n", bla0, bla1, bla2);
}

编译结果:

zbie@ubuntu:~$ gcc -Wall -g -std=c99 -O2 zeroparam.c

zeroparam.c: In function ‘func’:

zeroparam.c:2: warning: type of ‘param111’ defaults to ‘int’

运行结果:

zbie@ubuntu:~$ ./a.out

99

10

11111110

99, 10, 11111110

我知道如果 func 的参数为零,例如 int func() 它将接受 any 输入,则代码应该没问题。但是这段代码是如何编译并成功运行的呢?

【问题讨论】:

  • 取决于编译器; Apple LLVM 5.0 会生成许多关于附加参数的警告。
  • 还有一个隐含的 int 问题,我删除了我的帐户... >.
  • 哦,顺便说一句,定义一个不带参数的函数然后将参数传递给它是未定义的行为,根据规范 (6.9.1/8):“如果一个函数接受可变数量的参数的定义没有以省略号结尾的参数类型列表,则行为未定义。”因此,要正确接受可变数量的参数,必须有一个正确的参数和一个省略号 (...)。
  • @H2CO3,这可能很烦人,但它提供了谷歌索引。一个人问它,多年来会帮助几个人。
  • @JoachimPileborg,对于每个花时间在 SO 上写问题的人,无论多么幼稚,我敢打赌,谷歌会有更多随机关键词。

标签: c


【解决方案1】:

此行为是为了向后兼容该语言的旧版本,即该语言的 K&R 版本。当 GCC 遇到“旧样式”函数时,它符合旧的 K&R C 行为,这意味着在这种情况下没有警告。

确实,如果您将函数更改为:int func(int param111),您确实会收到预期的警告:

x.c: In function ‘main’:
x.c:11:5: error: too many arguments to function ‘func’
x.c:2:5: note: declared here
x.c:12:5: error: too many arguments to function ‘func’
x.c:2:5: note: declared here
x.c:14:1: warning: control reaches end of non-void function [-Wreturn-type]

(使用 GCC 4.7.3 和“gcc -std=c99 -Wall x.c && ./a.out”测试)

或者引用 cmets 中的 JeremyP“在 K&R C 中,调用具有任意数量参数的函数是非常好的,因为那时还没有发明省略号表示法。”.

请注意,编译器可以显示任意数量的额外警告,并且仍然符合标准。例如,Apple 的编译器会对此代码发出警告。

【讨论】:

  • OP 询问为什么他在func(10, 99); 上没有收到错误或警告
  • 我投了反对票,因为这甚至没有试图回答这个问题。
  • @NPE,一次一件事,我只能写这么快...... :)“比如 int func() 它将接受任何输入”也可能意味着“为什么它接受一个int and 更多参数"
  • 对于正确答案的反对票很多。函数定义是 K&R 风格的定义(参数没有类型说明符。在 K&R C 中,调用具有任意数量参数的函数是非常好的,因为当时还没有发明省略号表示法。
  • @nneonneo:标准从不说编译器不应该发出警告。在某些情况下,它需要诊断(可能是致命错误或警告),但编译器总是可以打印额外的警告。
【解决方案2】:

函数声明被解释为 K&R 风格的函数声明,因为它缺少类型。在标准术语中,这称为带有标识符列表的函数声明,而不是像通常声明中的参数类型列表

根据 C99 规范 6.9.1/7,只有具有参数类型列表的函数定义被认为是函数原型。 K&R 样式使用标识符列表,因此不被视为具有原型。

不检查对没有原型的函数的函数调用的参数计数或类型(根据 6.5.2.2/8,“参数的数量和类型不会与不包含函数的函数定义中的参数的数量和类型进行比较原型声明符”)。因此,使用任何数量和类型的参数调用以 K&R 样式声明的函数是合法的,但根据 6.5.2.2/9,使用无效类型的调用将产生未定义的行为。

作为说明,以下代码将在没有任何警告的情况下编译(gcc -Wall -Wextra -pedantic -std=c99 -O):

#include <stdio.h>

void *func(param111)
char *param111;
{
    printf("%s\n", param111);
    return param111;
}

int main()
{
    void *bla0 = func();
    void *bla1 = func(99);
    void *bla2 = func(11111110,99);
    printf("%p, %p, %p\n", bla0, bla1, bla2);
    return 0;
}

尽管参数类型和计数显然不正确。

【讨论】:

  • 标准语言+1。我猜这意味着 GCC 在这种情况下实际上符合 C99?
  • 是的。 Clang 也是如此,但 Clang 选择警告错误类型和参数计数(除非您指定 -Werror,但 Clang 仍会编译程序)。
  • 谢谢,我这样解释您的回答,但不是 100% 确定。也感谢您提到 Clangs 警告,我也想知道这一点。我想编译器可以随意发出警告。
【解决方案3】:

正如其他人所解释的那样,它被解释为 K&R C。值得注意的是,它在 ANSI C 中是未定义的行为:

C11 6.9.1 函数定义第 9 节

如果一个接受可变数量参数的函数被定义为没有参数 以省略号结尾的类型列表,则行为未定义。

所以一个可变数量参数函数必须以...作为参数结束,比如printf

int printf( const char *format ,...);

【讨论】:

    【解决方案4】:

    我可以解释为什么会这样,但不能解释为什么编译器不警告它。

    有几个calling conventions,它们指定参数的排序方式和放置位置。 C 调用约定允许传递额外的参数而不会产生副作用,因为调用者清理它们,而不是调用的函数,并且它们都在堆栈上传递:

    对于使用 func(10, 99) 的情况,“main”按以下顺序(从右到左)将值推入堆栈:

    99
    10
    

    "func" 只知道一个值,它从最后获取它们,所以param111 == 10

    然后“main”知道两个参数被压入,将它们取回,从而清理堆栈。

    【讨论】:

    • 并非所有架构都将所有参数都推送到堆栈上。只要调用者知道会发生什么,实现就可以将参数放在它想要的任何位置(例如寄存器、堆栈、覆盖在其他参数上、完全消除)。在某些架构上,使用错误数量的参数调用函数会使堆栈失衡,并导致运行时堆栈上溢/下溢。
    【解决方案5】:

    代码中的func 函数只有函数定义,没有函数声明符。在 C99 6.5.2.2(Function Calls) 中,它统计:

    “没有隐式执行其他转换;特别是, 参数不与函数定义中的参数进行比较 不包括函数原型声明符。”

    func(10,99)func(11111110, 99, 10001)被调用时,编译器不会将参数的数量和类型与函数定义中的参数进行比较。您甚至可以通过func("abc") 调用它。但是,如果您在代码中添加以下 func 函数声明:

    int func(int);
    

    (声明int fun(int)是因为C99标准会将para111隐式提升为int类型),编译器会发送以下错误:

    zeroparam.c:在函数“main”中:
    zeroparam.c:15:13: 错误:函数‘func’的参数太多
    zeroparam.c:6:5: 注意:在这里声明
    zeroparam.c:16:17: 错误:函数‘func’的参数太多

    顺便说一句:我认为这不是“K&R 程序”问题,因为您在命令中明确指定了“-std=c99”。

    【讨论】:

    • 它确实有一个函数声明器;它是函数定义的一部分。
    【解决方案6】:

    如果您没有收到该代码的任何警告,那是因为您的编译器没有强制执行 C99 规则(调用 printf 或任何函数,没有可见的声明是违反约束的)。通过将正确的选项传递给编译器,您可能至少会收到一些警告。如果您使用 gcc,请尝试 gcc -std=c99 -pedantic -Wall -Wextra

    所谓的 K&R C,即 1978 年第一版 Kernighan 和 Ritchie 的经典著作 The C Programming Language 所描述的语言,没有函数原型。 (原型是一个函数声明,它指定了它的参数类型。)一个函数定义仍然必须定义它的参数(也许是隐式的),但是一个声明没有—— - 并且典型的编译器不会检查参数(在函数调用中)与参数(在函数定义中)的正确匹配。

    如果您调用具有错误数量和/或类型的参数的函数会发生什么并不完全清楚。用现代术语来说,这是未定义的行为,但较旧的编译器通常会让你玩花样。

    1989 年的 ANSI C 标准(作为 1990 年的 ISO C 标准重新发布)引入了原型(从早期的 C++ 中借用),但并不要求它们。但它确实明确指出,使用错误数量或类型的参数调用函数会导致未定义的行为;编译器不需要警告你,但是当你运行它时,程序可以做任何事情。

    1999 年 ISO C 标准删除了“隐式 int”规则,并规定调用没有可见声明的函数是非法的(违反约束)——但它仍然允许旧式函数声明和定义。所以在 K&R1 和 C89/C90 规则下,你的函数定义:

    int func(param111)
    {
        printf("%d\n", param111);
        return param111;
    }
    

    有效,param111 的类型为 int。在 C99 规则下,它是无效的,但是这个:

    int func(param111)
    int param111;
    {
        printf("%d\n", param111);
        return param111;
    }
    

    仍然合法(即使在 2011 年标准下仍然合法)。

    从 C99 和 C11 开始,如果您调用一个可见声明不是原型的函数,则完全取决于您是否正确设置参数;如果你弄错了,编译器不需要警告你。

    这就是为什么您应该始终对所有函数声明和定义使用原型。如今,几乎不存在编写使用前 ANSI 编译器编译的代码的需要。很难找到至少不支持 C89/C90 的编译器。

    哦,你需要添加

    #include <stdio.h>
    

    到源文件的顶部,因为您正在调用printf。在 C89/C90 规则下,在没有可见声明的情况下调用 printf 具有未定义的行为(因为 printf 采用可变数量的参数)。在 C99 及更高版本下,这是违反约束的,需要编译时诊断。

    我一直在挑剔缺少的参数声明。你的程序的一个稍微改变的变体:

    #include <stdio.h> /* add this line */
    
    int func(param111)
    int param111;      /* add this line */
    {
        printf("%d\n", param111);
        return param111;
    }
    
    int main(void)     /* add "void" */
    {
        int bla0 = func(99);
        int bla1 = func(10,99);
        int bla2 = func(11111110,99,10001);
        printf("%d, %d, %d\n", bla0, bla1, bla2);
    }
    

    不违反任何需要在 C90、C99 或 C11 中进行编译时诊断的规则,但对 func 的第二次和第三次调用具有未定义的行为。

    请注意,编译器实际上有足够的信息来警告您对func 的调用不正确。刚刚看到func 的定义,它应该知道,任何没有传递一个可隐式转换为int 的类型的参数的任何调用都是无效的。不需要警告,但编译器总是可以打印他们喜欢的任何额外警告。显然 gcc(以及您使用的任何编译器)的作者认为,不值得努力警告对具有旧式声明和/或定义的函数的不匹配调用。

    【讨论】:

    • “如果您没有收到该代码的任何警告,那是因为您的编译器没有执行 C99 规则。” 真的如此吗?
    • @Prof.Falken:是的,因为对 printf 的调用没有可见的声明。
    【解决方案7】:

    如果您在编译时检查警告,您会看到以下消息:

    zeroparam.c:2:警告:“param111”类型默认为“int”

    这告诉您,没有类型的参数默认为整数。就像定义一个没有返回类型的函数一样,它也会默认为int

    【讨论】:

    • OP 询问为什么他在func(10, 99); 上没有收到错误或警告
    • 原因在 C99 中:“参数的数量和类型不与不包含函数原型声明符的函数定义中的参数的数量和类型进行比较。”更多信息包含在我的回答帖子中。
    猜你喜欢
    • 2014-01-29
    • 1970-01-01
    • 2014-01-17
    • 1970-01-01
    • 2018-01-03
    • 2013-08-05
    • 1970-01-01
    • 1970-01-01
    • 2015-09-03
    相关资源
    最近更新 更多