C语言多文件编程中调用的函数又调用了其他的函数
在C语言项目中,我们经常会遇到别人提供给我们的库文件和头文件,我们添加好库路径和包含路径之后,通过头文件中的函数声明和变量声明来使用这些函数和变量。
在这个过程中,我一直有一个疑惑,那就是当我们调用头文件中声明的函数时,如果该函数调用了其他函数,而且该函数没有在该头文件中声明,那么会报错吗?
下面我们来实验一下,测试项目总共包含三个项目
main.c
int main() {
callfunction();
}
call.c
#include <stdio.h>
void callagainfunction(void);
callfunction() {
printf("hello world\n");
callagainfunction();
}
void callagainfunction(void) {
printf("hello world again\n");
}
call.h
extern callfunction();
编译我们发现是可以正常编译运行的,我们只extern callfunction();没有extern callagainfunction();
这说明当我们调用其他库文件的函数时,我们只需要在头文件中包含需要调用的接口函数就行了,调用的函数如果又调用了其他的函数,我们是不需要在头文件中加入这个函数的外部文件声明的。
其实往深处将,C语言编译的过程其实是以一个.c文件为单位的,每个.c文件中包含的头文件会在预编译的时候展开,将包含的头文件的内容复制过来,然后编译成一个.o文件,每个.c文件可以编译成.o文件的前提是,你这这个.c文件中使用的所有函数和变量都要是声明或者定义过的,比如我们在一个.c文件中定义了一个整型或者定义了一个结构体,那我们在这个.c文件中是可以使用这个整型或者结构体的,当我么要调用一个函数时,我们可以在这个.c文件下声明这个函数或者定义这个函数。定义这个函数很容易理解,就是我们要使用的这个函数的函数体定义都在该文件下;另外一种情况就是声明这个函数,因为可能这个函数的定义不是在这个.c文件下,因此需要告诉编译器,这个函数我们先声明一下,你就当这个函数存在了,编译的时候不要报未定义这个函数的错误了。
这时候其实就存在了两种.o文件,一种是其使用的所有函数或者变量都是在自己文件下声明的,也就是说这个文件的实现不依赖其他任何文件。这种情况一般是出现在单文件编程中,或者是那种独立的功能模块,供其他模块调用;第二种就是依赖其他文件的.o文件,因为在这个.o文件中是没有某些函数的定义的,当时我们使用这个函数的时候知识对其进行了声明,并没有对其进行定义,因此这个.o文件中该函数的定义是要依赖其他文件来提供的,这要靠链接来解决。
下面就到了链接的步骤了,对于第一种.o文件没什么好说的,对于第二种.o文件,那就需要在链接过程中找到这些未定义的函数的定义。
讲清楚了编译链接的过程,那我们继续讲一下为啥我们不需要在头文件中加入调用函数调用的函数外部函数声明的。
因为在编译阶段,我们的.c文件中没有直接出现这个调用函数调用的函数,实际上在编译阶段,我们根本不知道调用函数的函数体,我们只是告诉编译器,这个函数存在,并不知道这个函数是干嘛的,也不知道这个函数包括了啥。因此我们不需要在头文件中加入调用函数调用的函数外部函数声明的。
这边还要进行一个说明,那就是在编译过程中,变量和函数都会被编译器管理起来,形成符号表,每个符号都代表一个变量或者函数。
到了链接阶段,链接器会根据符号表去找到那些缺失了函数定义的符号,然后将这个符号和函数定义(代码段)关联起来,这样所有的.o文件的依赖问题都解决了,最终形成一个可执行文件。
ps:今天写这篇文章意外纠正了自己之前的一个错误理解。
我当时错误的理解是:模块化编程,每个模块都包括一个.c和.h文件,并且 .c文件一定需要包含这个.h文件。
现在发现其实这个理解是错误的,其实.这个.h文件是对这个.c文件对这个模块的一个功能描述和接口描述,这个.h文件是供了解和使用的,因此该模块的.c文件不一定需要包含.h文件。
那问题又来了,那为啥我们见到的很多模块都是.c文件会包含.h文件了,这是因为随着编程项目越来越复杂,每个模块包含的东西也越来越多,我们经常要在.h文件中定义很多宏定义,很多结构体的声明等,而这些宏定义可能我们自己模块要使用,可能其他模块也可以使用,因此我们就医案会让.c文件包含.h文件了。除此之外,.h文件中一般还需要有
#define num 5 //宏定义
extern int a; //变量声明
extern void test();/函数声明
通过包含该头文件,我们就可以使用这些宏定义,变量,函数。
看下面的代码,我的call.c没有包含call.h,可以正常编译运行
main.c
int main() {
callfunction();
}
call.c
#include <stdio.h>
void callagainfunction(void);
callfunction() {
printf("hello world\n");
callagainfunction();
}
void callagainfunction(void) {
printf("hello world again\n");
}
call.h
extern callfunction();
当然你也可以在call.c中包含call.h,但是为了防止多次包含的情况发生,我们一般需要在头文件中加入防止多次包含的宏定义。
C语言多文件编程:头文件与函数声明的奥秘

本文探讨了C语言中多文件编程时头文件的使用,解释了如何仅需在头文件中声明调用的函数,而无需声明其内部调用的其他函数。通过实例展示了编译和链接过程,强调了每个.c文件的独立性和链接器在解决函数定义中的作用。同时澄清了.c文件并不一定要包含对应的.h文件,头文件主要作为接口描述,防止重复包含的宏定义也是头文件中的常见实践。

被折叠的 条评论
为什么被折叠?



