我剛開始學(xué)C語言和單片機,最刺激的時刻莫過于編譯程序。
編譯的時候各種報錯,什么"重復(fù)定義"、"類型沖突"、"未聲明"...搞了一整天都不知道從哪下手。
error: redefinition of type "GPIO_TypeDef"
error: conflicting types for 'HAL_Init'
error: 'TIM2' undeclared
我當(dāng)時就懵了:這些錯誤是啥意思?怎么改?
后來經(jīng)驗豐富了,硬著頭皮看了STM32標(biāo)準(zhǔn)庫的頭文件,才發(fā)現(xiàn):大廠的頭文件寫得是真嚴(yán)謹(jǐn)。
照著那個規(guī)范改了一遍,編譯世界瞬間清靜了。
很多初學(xué)者都會遇到這個問題。
你定義了一個結(jié)構(gòu)體:
// types.h
typedefstruct{
int x;
int y;
} Point_t;
然后在多個文件里include:
// main.c
#include"types.h"
#include"utils.h"
voidmain(void)
{
Point_t p;
// ...
}
編譯器直接報錯:
error: redefinition of type "Point_t"
為啥?因為types.h被包含了兩次!編譯器看到了兩次Point_t的定義,以為你重復(fù)定義了。
很多人第一反應(yīng)是:"我沒重復(fù)定義啊!"
其實問題出在頭文件的包含方式上。
解決重復(fù)定義最簡單的辦法,就是加"頭文件保護"。
給個例子:
// types.h
#ifndef_TYPES_H_
#define_TYPES_H_
typedefstruct{
int x;
int y;
} Point_t;
#endif// _TYPES_H_
看明白了嗎?
第一次include這個頭文件的時候,_TYPES_H_沒定義,所以會定義_TYPES_H_,然后編譯后面的內(nèi)容。
第二次include的時候,_TYPES_H_已經(jīng)定義了,#ifndef判斷失敗,后面的內(nèi)容全部跳過。
這樣就能保證頭文件只會被編譯一次。
這是最經(jīng)典、最常用的頭文件保護方式,幾乎所有C項目的頭文件都是這么寫的。
除了#ifndef方式,還有一種更簡潔的寫法:
// types.h
#pragmaonce
typedefstruct{
int x;
int y;
} Point_t;
#pragma once的作用和#ifndef一樣:保證頭文件只被編譯一次。
區(qū)別是:
#ifndef是C標(biāo)準(zhǔn),所有編譯器都支持#pragma once不是C標(biāo)準(zhǔn),但主流編譯器都支持我的建議:用#ifndef,兼容性最好。大廠的頭文件基本都是用#ifndef。
給你看個STM32標(biāo)準(zhǔn)庫的頭文件例子:
// 來源于STM32標(biāo)準(zhǔn)庫 stm32f10x.h
// 功能:芯片類型定義和頭文件包含
#ifndef__STM32F10x_H
#define__STM32F10x_H
#ifdef__cplusplus
extern"C"{
#endif
/* Includes ------------------------------------------------------------------*/
/* Depending on the device you are using, you can define the right chip */
#ifdefined(STM32F10X_LD)||defined(STM32F10X_LD_VL)
#include"stm32f10x_ld.h"
#elifdefined(STM32F10X_MD)||defined(STM32F10X_MD_VL)
#include"stm32f10x_md.h"
#elifdefined(STM32F10X_HD)||defined(STM32F10X_HD_VL)
#include"stm32f10x_hd.h"
#elifdefined(STM32F10X_XL)
#include"stm32f10x_xl.h"
#else
#error"Please select first the target STM32F10x device used in your application (in stm32f10x.h file)"
#endif
#ifdef__cplusplus
}
#endif
#endif/* __STM32F10x_H */
看懂了嗎?大廠的頭文件有幾個關(guān)鍵點:
#ifndef__STM32F10x_H
#define__STM32F10x_H
// ... 頭文件內(nèi)容
#endif/* __STM32F10x_H */
#ifdef__cplusplus
extern"C"{
#endif
// ... 頭文件內(nèi)容
#ifdef__cplusplus
}
#endif
這段代碼是給C++用的。如果你的C頭文件要在C++項目里用,加上extern "C"能保證C++編譯器用C的方式編譯這些代碼。
#ifdefined(STM32F10X_LD)||defined(STM32F10X_LD_VL)
#include"stm32f10x_ld.h"
#elifdefined(STM32F10X_MD)||defined(STM32F10X_MD_VL)
#include"stm32f10x_md.h"
// ...
根據(jù)不同的芯片型號,包含不同的頭文件。這樣一套代碼就能適配多種芯片。
給你總結(jié)一個通用的頭文件模板:
#ifndef_FILENAME_H_
#define_FILENAME_H_
#ifdef__cplusplus
extern"C"{
#endif
/* Includes ------------------------------------------------------------------*/
// 這里include你需要的頭文件
/* Exported types ------------------------------------------------------------*/
// 這里導(dǎo)出的類型定義
/* Exported constants --------------------------------------------------------*/
// 這里導(dǎo)出的常量定義
/* Exported macro ------------------------------------------------------------*/
// 這里導(dǎo)出的宏定義
/* Exported functions prototypes ---------------------------------------------*/
// 這里導(dǎo)出的函數(shù)聲明
#ifdef__cplusplus
}
#endif
#endif/* _FILENAME_H_ */
按照這個模板寫頭文件,基本不會出錯。
除了頭文件本身的規(guī)范,include的順序也有講究。
錯誤示范:
// main.c
#include"my_driver.h"
#include"stm32f10x.h"
#include<stdio.h>
問題在哪?
你的my_driver.h可能用到了STM32的類型定義,但你先include了my_driver.h,編譯器看到GPIO_TypeDef的時候還不知道這是個啥。
正確寫法:
// main.c
// 1. 先include標(biāo)準(zhǔn)庫頭文件
#include<stdio.h>
#include<string.h>
// 2. 再include芯片/廠商庫頭文件
#include"stm32f10x.h"
// 3. 最后include你自己的頭文件
#include"my_driver.h"
#include"utils.h"
原則:依賴者include被依賴者。
如果你的my_driver.h依賴stm32f10x.h,那應(yīng)該先includestm32f10x.h。
還有一個很惡心的問題:循環(huán)依賴。
給個例子:
// a.h
#include"b.h"
voidfunc_a(void);
// b.h
#include"a.h"
voidfunc_b(void);
a.hinclude了b.h,b.h又include了a.h,編譯器直接懵圈。
解決方案:使用前置聲明
// a.h
#ifndef_A_H_
#define_A_H_
// 不include b.h,用前置聲明
typedefstructb_tb_t;
voidfunc_a(b_t*p);
#endif// _A_H_
// b.c
#include"b.h"
#include"a.h"http:// 在.c文件里include
voidfunc_b(void)
{
func_a(...);
}
原則:頭文件里盡量用前置聲明,實在不行再include。
給你整理一個常見錯誤對照表:
redefinition of type | #ifndef | |
conflicting types | ||
undeclared identifier | ||
expected ')' before | ||
implicit declaration |
編譯報錯搞死人?多半是頭文件沒寫規(guī)范。
照著大廠的頭文件規(guī)范改一遍,基本能解決90%的編譯錯誤:
#ifndef或#pragma onceextern "C"