【Skynet】C頭文件不要定義函數?

參考自:函數實現不放在頭文件的原因,及何時可以放頭文件的情況(綠色冰點)


對於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++可以進一步討論,參見本文的文獻。


發佈了129 篇原創文章 · 獲贊 47 · 訪問量 22萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章