參考自:函數實現不放在頭文件的原因,及何時可以放頭文件的情況(綠色冰點)
對於C/C++我們很早就被告知不要在頭文件裏定義函數,這樣不好。今天學習Skynet時,發現裏面有很多函數都定義在頭文件裏?很是疑問,於是有了這篇文章。
把函數定義在頭文件有哪些主要缺點?
1、不利於代碼理解和維護:
通常,頭文件被用來唯一指定接口,且多少提供一些文檔來說明如何使用在該文件中聲明的組件。若把所有變量和函數的定義都寫在頭文件中,往往頭文件異常的大,不利於代碼理解;推薦把不同功能的函數分佈在不同的源文件中,共享一個頭文件;
2、函數細節在頭文件中被暴露;
3、導致鏈接衝突:
頭文件被多個源文件包含,頭文件裏的函數,變量在鏈接時發生衝突;
4、增加編譯時間:
頭文件被包含到多個源文件中,該頭文件就會被編譯到多個源文件裏,增加了編譯時間,也增大了執行程序的體積。相比之下,如果函數的實現在源文件裏,而源文件則單獨編譯一份。(在C和C++中有個例外,即內聯函數。內聯函數通常放在頭文件中,因爲大多數實現如果不知道其定義,在編譯時便無法適當的展開內聯函數。)
如果看重缺點2,則應該在第一時間放棄在頭文件中定義函數。
缺點3是可以克服的。如下一段代碼:
#ifndef _A_H_
#define _A_H_
int gint = 0;
int myAdd(const int a, const int b)
{
return a + b;
}
#endif
在同一工程中,兩個以上的源文件包含了此頭文件,則在鏈接時期就會發生衝突,因爲在兩個源文件編譯得到的目標文件中都有一份 myAdd 的函數實現,鏈接器不知道對於調用了此函數的調用者,應該使用哪一個副本。這一問題對於全局變量也是如此。
解決的途徑:一個是 inline(注意:在C下,內聯一般使用 static inline) ,另一個是 static 。使用這兩個關鍵字的任意一個來修飾全局函數,都會消除上述的衝突問題,然而本質卻大不相同。
如果使用 inline ,則意味着編譯器會在調用此函數的地方把函數的目標代碼直接插入,而不是放置一個真正的函數調用,實際作用就是這個函數事實上已經不再存在,而是像宏一樣被就地展開了。使用 inline 的副作用,首先在於毋庸置疑地,代碼的體積變大了;其次則是,這個關鍵字嚴格算起來並不是 C 語言的關鍵字,使用它多少會帶來一些移植性方面的風險,儘管主流的 C 語言編譯器都可以支持 inline 。對於 GCC
, inline 功能關鍵字就是 inline 本身,而對於微軟的編譯器,應該是 __inline (注意有兩個前導下劃線)。而且,根據慣例, inline 通常都是對編譯器的某種暗示而非強制要求,編譯器有權力在你不知情的情況下把它實現爲非 inline 的狀態(可能的原因有,函數太大或者複雜度過高)。這樣的後果不是我們願意的。
如果使用 static ,那麼至少結果是可預料的。所有包含此頭文件的源文件中都會存在此函數的一份副本。雖然代碼也有一定程度的膨脹,但好就好在互相不衝突,因爲 static 關鍵字保證了該函數的可見度爲單個源文件之內。注意:該方法對於全局變量來說是毀滅性的,因爲變量也只是在單個源文件裏可見!
a.h
#ifndef _A_H_
#define _A_H_
static int gint = 0;
static int myAdd(const int a, const int b)
{
return a + b;
}
#endif
a.c
#include "a.h"
#include <stdio.h>
int funcA()
{
printf("%d from A\n", gint);
gint++;
printf("%d from A\n", gint);
return myAdd(1, 2);
}
b.c
#include "a.h"
#include <stdio.h>
int funcB()
{
printf("%d from B\n", gint);
return myAdd(1, 2);
}
main.c
#include <stdio.h>
extern int funcA();
extern int funcB();
int main()
{
printf("%d %d\n", funcB(), funcA());
return 0;
}
輸出如下:
0 from A
1 from A
0 from B
3 3
發現使用 static 確實可以解決函數的鏈接問題,對於全局變量則並不適合,兩份變量都只在本地源文件可見。
但是:參見知乎上的大牛說法static函數在頭文件中定義有什麼好處麼?,函數定義在頭文件還是慎用。
以上的討論雖然看起來主要聚焦在 C 語言上,但由於 C++ 是 C 語言的超集,並且在這些方面並沒有做太多的修改,因此討論結果同樣也適用於 C++ 。對於C++可以進一步討論,參見本文的文獻。