考虑以下来源: 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.c
和print_hello
中的hello.c
的签名是不同的。为什么它工作正常?
答案 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.o
和hello.o
中的两个名称(或符号)都是print_hello
,因此它们被链接在一起。使用nm
查询ELF对象或可执行文件中的名称。当然,在运行时会发生的事情是undefined behavior,但ABI(和x86)的x86-64约定会启用您期望的行为。重要的是,ELF不会在目标文件中保留输入或签名元数据。
使用C ++,它有点不同:编译器正在通过name mangling转换函数名称(因此ELF *.o
文件中的名称不仅仅是C ++源名称),因此链接器看起来不同名字(并且会失败)。