本帖最后由 2558736068 于 2022-4-3 12:45 编辑
本文主要以跑马灯-库函数版实验为例进行分析。一、c语言.h .c 和main.c之间的关系
1)、led.c文件主要是定义了(或者说实现了)一个外部函数——void LED_Init(void),该函数主要用来使能I/O口时钟和初始化I/O口。
led.h文件主要是进行函数声明【void LED_Init(void)】以及宏定义。
main.c主函数文件则是函数主体,通过操作I/O口,实现跑马灯。
2)、整个逻辑总结以后就是:led.h里定义了一个函数的声明,然后我在led.h的同一个目录下建立led.c,led.c里定义了这个函数的实现,然后是在main函数所在.c文件里(也就是main.c文件,其实可以任意取名)
#include “led.h”,然后我就可以使用这个函数了。main在运行时就会找到这个定义了这个函数的led.c文件。
底层原因是:
main函数为标准C/C++的程序入口,编译器会先找到该函数所在的文件。
假定编译程序编译main.c(其中含main())时,发现它include了led.h(其中声明了函数void LED_Init(void)),那么此时编译器将按照事先设定的路径(Include路径列表及代码文件所在的路径)查找与之同
名的实现文件(扩展名为.cpp或.c,此例中为led.c),如果找到该文件,并在其中找到该函数(此例中为void LED_Init(void))的实现代码,则继续编译;如果在指定目录找不到实现文件(.c文件),或者在该文
件及后续的各include文件中未找到实现代码,则返回一个编译错误.其实include的过程完全可以"看成"是一个文件拼接的过程,将声明和实现分别写在头文件及C文件中,或者将二者同时写在头文件中,理论上没
有本质的区别。
3)、也就是说头文件用来存放函数原型(函数声明)。那么头文件是如何来关联源文件的呢?
换言之,已知头文件"led.h"声明了一个函数(仅有函数原型,没有函数实现),"led.c"中实现了这个函数,那么如果我想在"main.c"中使用"led.h"中声明的这个在"led.c"中实现的函数,通常都是在"main.c"中
使用#include"led.h",那么main.c是怎样找到led.c中的实现呢? 其实.c和.h文件名称没有任何直接关系,很多编译器都可以接受其他扩展名。 编译器预处理时,#include "xx.h" 这个宏其实际意思就是把当前这一行删掉,把 xx.h中的内容原封不动的插入在当前行的位置。这也就是为什么很多编译器并不care到底这个文件的后缀名是什么----因为
#include预处理就是完成了一个"复制并插入代码"的工作。
程序编译的时候,并不会去找led.c文件中的函数实现,只有在link(连接)的时候才进行这个工作。我们在led.c或main.c中用#include"led.h"实际上是引入相关声明(宏定义、函数声明等),使得编译可
以通过,程序并不关心实现是在哪里,是怎么实现的。源文件编译后成生了目标文件(.o或.obj文件),目标文件中,这些函数和变量就视作一个个符号。在link的时候,需要在makefile里面说明需要连接哪个
.o或.obj文件(在这里是led.c生成的.o或.obj文件),此时,连接器会去这个.o或.obj文件中找在led.c中实现的函数,再把他们build到makefile中指定的那个可以执行文件中。
在VC中,一帮情况下不需要自己写makefile,只需要将需要的文件都包括在project中,VC会自动帮你把makefile写好。
通常,编译器会在每个.o或.obj文件中都去找一下所需要的符号,而不是只在某个文件中找或者说找到一个就不找了(遍历所有文件)。因此,如果在几个不同文件中实现了同一个函数,或者定义了同一
个全局变量,链接的时候就会提示"redefined"(重复定义)
二、led.h内容剖析
#ifndef __LED_H
#define __LED_H
#include "sys.h"
//LED端口定义
#define LED0 PBout(5) //DS0
#define LED1 PEout(5) //DS1
void LED_Init(void); //初始化(函数声明)
#endif
1)、#define __LED_H的意义
其本质是一个宏名。用来防止发生重复定义或声明。(下划线“__”属于编程风格的内容,对程序没有影响。不用下划线也可以,用几个下划线也由个人习惯。)
假设你的头文件名为led.h,根据习惯,我们声明一个宏__LED_H对应这个头文件,在头文件中开始的地方和结尾的地方加上对__LED_H的声明和判断,头文件led.h如下:
#ifndef __LED_H
#define __LED_H
……(头文件内容)
#endif
意思就是,如果led.h第一次被引用,那么__LED_H没有被定义,所以#define __LED_H和#endif 中间的头文件内容就被执行,也就是头文件内容被定义。那么如果led.h第二次被引用,由于__LED_H已经
被第一次引用时定义了,所以不会执行#define __LED_H和#endif 中间的头文件内容,也就是不会重复定义头文件内容。
这样,可以避免头文件的内容被重复定义。头文件中定义的变量不存在重复声明或定义。
声明:本贴仅作学习笔记使用,文中内容主要参考两天文章:
1、C语言中.h和.c文件解析(很精彩)-OpenEdv-开源电子网
2、CSDN博主「吮指原味张」的原创文章,原文链接:https://blog.csdn.net/mahoon411/article/details/107975549
|