参数传递给函数

时间:2014-01-01 07:10:34

标签: c linux

考虑以下来源: main.c中

int main(){
    print_hello("str");
    return 0;
}

和hello.c

#include<stdio.h>
void print_hello(){
    printf("Hello world\n");
}

编译并链接gcc -o hello hello.c main.c后,它的工作正常,但我预计会发生错误。因为print_hello中的main.cprint_hello中的hello.c的签名是不同的。为什么它工作正常?

3 个答案:

答案 0 :(得分:1)

您没有告诉编译器print_hello()函数在文件main.c中的含义。如果您有一个头文件hello.h,例如:

#ifndef HELLO_H_INCLUDED
#define HELLO_H_INCLUDED

extern void print_hello(void);

#endif

你在每个文件的顶部都有#include "hello.h",然后你会因误用print_hello()中的main.c而得到相关的编译器警告。

答案 1 :(得分:1)

您的编译器使用calling convention,它允许额外的参数传递给被调用的函数,而不会产生额外的影响。这在C的调用约定之间是典型的,因为它支持可变参数列表(如printf,scanf等)。在这些约定中,

  • 参数按照第一个参数最接近堆栈顶部(如果所有参数都放在堆栈上)或等效寄存器中的顺序传递,其他参数按顺序传递,
  • 调用者知道使用的堆栈大小并在函数调用后恢复堆栈指针。

X86 calling conventions中,这通常被命名为“cdecl”(可选地,带有1或2个下划线)。

这样的约定对于额外的参数是可以容忍的,但是,当然,不是由被调用函数使用的未指定的参数;后一种情况会导致参数中的垃圾。

通常不应该利用这样的运行时功能,但是有一些极端情况它很有用。

为防止函数使用与其定义不匹配,您应该对两者使用相同的声明。通常,它会在声明或调用此函数的所有源中包含相同的头文件。

答案 2 :(得分:1)

除了其他解释之外,在Linux上假设GCC 4.8或更好:

您应该使用gcc -Wall -std=c99进行编译以获取所有警告(-Wall)并告诉编译器您要遵守哪个标准(-std=c99)。通过这些设置,您会收到警告:

  main.c: In function 'main':
  main.c:2:5: warning: implicit declaration of function 'print_hello' 
              [-Wimplicit-function-declaration]
  print_hello("str");
 ^

至于为什么没有给出错误,它们只能在link时由链接器ld给出,由gcc调用。 Unix和Linux链接器(来自GNU binutils)只能使用名称:每个函数(更常见的是每个链接符号)都有一个名称(并且目标文件和可执行文件都在ELF中,它定义了如果两个名称相同,则链接器通过使它们引用相同的地址来解析(请参阅Levine的Linkers and Loaders)。因此,main.ohello.o中的两个名称(或符号)都是print_hello,因此它们被链接在一起。使用nm查询ELF对象或可执行文件中的名称。当然,在运行时会发生的事情是undefined behavior,但ABI(和x86)的x86-64约定会启用您期望的行为。重要的是,ELF不会在目标文件中保留输入或签名元数据。

使用C ++,它有点不同:编译器正在通过name mangling转换函数名称(因此ELF *.o文件中的名称不仅仅是C ++源名称),因此链接器看起来不同名字(并且会失败)。