vlambda博客
学习文章列表

C语言头文件定义技巧以及注意事项

 

 原创文章,欢迎分享,转载请注明来源,并给出原始链接,未经书面允许,请勿用于商业用途。

   在一篇中我们详细的介绍了C语言开发过程中经常使用到的一些宏名称及其意义,并且简单展示了其作用。

   在一个大的项目开发过程中,我们的源文件,头文件一般都会按照模块划分,形成多个文件。同时,各种宏定义,变量定义,函数定义等就会相当的多,这样一来,在我们引用一个定义的时候,就要去包含非常多的头文件进来。在一个C文件前面列出来一大堆引用的头文件,除了带来不必要的工作,而且有时候经常把先后顺序搞错,带来一些编译错误,给排查和开发带来了一些困扰,那么有没有什么办法可以解决这个问题呢?答案是肯定的,下面我们就示例一下如何来解决这个问题,并且对一些关键问题提出警示。

   假定我们有如下几个文件:

   key.h的内容如下:

#ifndef__KEY_H__#define __KEY_H__ #ifdef __cplusplus extern "C" {#endif /*__cplusplus */ #define KEY_DOWN 0x0001//按键按下#define KEY_UP        0x0002//按键弹起 #ifdef __cplusplus}#endif /*__cplusplus */ #endif


led.h的内容如下:

#ifndef__LED_H__#define __LED_H__ #ifdef __cplusplus extern "C" {#endif /*__cplusplus */ #define LED_GREEN 0x0001//绿色led#define LED_RED        0x0002//红色led #ifdef __cplusplus}#endif /*__cplusplus */ #endif


在main.c文件里面,我需要引用两个变量,KEY_DOWN和 LED_RED,那么我们就要如下方式使用:

 


#include"config.h"#include<stdarg.h>#include<stdio.h>#include<stdint.h>#include"stm32f10x.h"#include “led.h” //引用宏LED_RED#include “key.h”//引用宏KEY_DOWN int main(void){ int key; while(1 { key = GetKey(); if(key == KEY_DOWN) LedOn(LED_RED); delay(10); }}


单独从这几个文件来看,这是正确的处理方式,也没有任何毛病。但是我们延伸一下,假如我们要引用的头文件更多呢?比如很多全局的宏定义,变量定义等等,那么我们就要在c文件前面罗列一大堆文件来表明我们的引用来源。

更好的解决办法是我们定义另外一个头文件,把项目全部的头文件(有时候可能极个别例外)都按照一定顺序放到这个文件里面,在其他的c文件前面只需要放一个这个头文件就可以了。

比如定义一个header。h

 


#ifndef__LED_H__#define __LED_H__ #include"config.h"#include<stdarg.h>#include<stdio.h>#include<stdint.h>#include"stm32f10x.h" #include “led.h”#include “key.h”#endif


在C文件里面就修改为如下,这样是不是看起来清爽多了:


#include"header.h" int main(void){ int key; while(1 { key = GetKey(); if(key == KEY_DOWN) LedOn(LED_RED); delay(10); }}


当然,在这样处理的过程中我们要注意几个地方:

1.header.h里面的头文件要安照引用的先后顺序排列,只能后面文件里面的引用前面的文件里面的定义,不可以颠倒过来引用。

2.每一个头文件前面都要有防止重复引用的标识符号定义,如#define __KEY_H__

在头文件定义的过程中,我们使用了两个技巧:

技巧一:


#ifndef__LED_H__ //检查是否定义宏__LED_H__#define __LED_H__ //如果没有定义,就定义一个宏//#ifndef和#endif是配对使用的,在这之间的代码只有在__LED_H__宏没有被定义的时候才会被编译进去,否则会忽略掉。#endif


在每一个头文件的最开始,声明了一个和文件名称一样的标识宏(**建议这样定义宏名称,以保持整个项目里面宏名字的唯一性**),起什么作用呢?

我们知道,如前一章所描述,预处理器在处理预处理命令的时候,会按照文件顺序展开文件,并且将内容集中到一个预编译文件里面(我们可以想象,编译器最后会将每一个C文件引用到的外部文件按照预编译命令放置的地方(命令在哪里就在哪里展开引用的文件)展开为一个长长的C文件)。我们在main.c里面包含了header.h,假如先编译main.c文件,那么在led.h和key.h里面定义的宏就会被定义一次(因为两个宏__LED_H__和__KEY_H__还没有被定义)。main.c的内容大致就是如下

 


//此处省略了其他头文件的变量//由于led.h被先引用,所以该文件的内容放到前面#define LED_GREEN 0x0001//绿色led#define LED_RED 0x0002//红色led//由于key.h被后引用,所以该文件的内容放到后面#define KEY_DOWN 0x0001//按键按下#define KEY_UP 0x0002//按键弹起//根据这样的文件放置顺序原则,我们在key.h里面就不能去引用led.h里面定义的宏。int main(void){ int key; while(1 { key = GetKey(); if(key == KEY_DOWN) LedOn(LED_RED); delay(10); }}


接下来我们在编译其他c文件或者再次包含这两个头文件的时候,由于宏已经被定义过,里面的内容就不会被展开,避免了重复定义的错误。

技巧二:


#ifdef __cplusplus extern "C" { //告诉C++编译器,下面所有代码安照C的规范来使用#endif /*__cplusplus */ #endif


我们添加了一个extern "C" 的声明,起什么作用呢?

因为我们在开发一个大型工程的时候,可能是C和C++语言混合开发的,那么由于C语言和C++语言编译器在把代码编译为汇编语言的时候,各自会把函数名称按照自己的规则命名为另外的名字,或者两个编译器对默认返回值,参数传递顺序可能不一致,从而导致链接的时候找不到符合或者传递参数错误等等情况发生。该语法extern "C" 的声明就告诉了C++编译器,下面的函数定义要按照c的规则来编译和链接,保持两者的一致性。