1. 關於#和##
1.1).在C語言的宏中,#的功能是將其後面的宏參數進行字符串化操做(Stringfication),簡單說就是在對它所引用的宏變量經過替換後在其左右各加上一個雙引號。
好比在早期的VLC版本中,有以下宏定義: html
#define STRINGIFY(z) UGLY_KLUDGE(z)
#define UGLY_KLUDGE(z) #z
#define MODULE_STRING STRINGIFY(MODULE_NAME)
而在每一個各個modules模塊下又有對MODULE_NAME的定義,以live555爲例以下定義:
#define MODULE_NAME live555
而在vlc/include/vlc_plugin.h文件中,有以下宏定義:代碼1.1
#define vlc_module_begin() \
EXTERN_SYMBOL DLL_SYMBOL \
int CDECL_SYMBOL __VLC_SYMBOL(vlc_entry) (vlc_set_cb, void *); \
EXTERN_SYMBOL DLL_SYMBOL \
int CDECL_SYMBOL __VLC_SYMBOL(vlc_entry) (vlc_set_cb vlc_set, void *opaque) \
{ \
module_t *module; \
module_config_t *config = NULL; \
if (vlc_plugin_set (VLC_MODULE_CREATE, &module)) \
goto error; \
if (vlc_module_set (VLC_MODULE_NAME, (MODULE_STRING))) \
goto error;
那麼當在live555.cpp中調用vlc_module_begin()宏後,其展開(可經過預編譯查看)就爲以下形式:代碼1.2
extern "C" int vlc_entry__live555 (vlc_set_cb, void *);
extern "C" int vlc_entry__live555 (vlc_set_cb vlc_set, void *opaque)
{
module_t *module;
module_config_t *config = __null;
if (vlc_set (opaque, __null, VLC_MODULE_CREATE, &module)) goto error;
if (vlc_set (opaque, module, VLC_MODULE_NAME, ("live555"))) goto error;
從上面的可知,實際上MODULE_STRING最後就變成了"live555"。此爲#的用法。
1.2)##被稱爲鏈接符(concatenator),用來將兩個Token鏈接爲一個Token。注意這裏鏈接的對象是Token就行,而不必定是宏的變量。
一樣以vlc/include/vlc_plugin.h文件中的宏定義爲例,在該文件中有以下宏定義:
#define CONCATENATE( y, z ) CRUDE_HACK( y, z )
#define CRUDE_HACK( y, z ) y##__##z
#ifdef __PLUGIN__
# define __VLC_SYMBOL( symbol ) CONCATENATE( symbol, MODULE_SYMBOL )
#else
# define __VLC_SYMBOL( symbol ) CONCATENATE( symbol, MODULE_NAME )
#endif
一樣以live555.cpp爲例,咱們注意前面有以下的宏使用:
int CDECL_SYMBOL __VLC_SYMBOL(vlc_entry) (vlc_set_cb vlc_set, void *opaque)
那麼再結合上面的宏定義(注意爲CONCATENATE( symbol, MODULE_NAME )),再結合宏定義CRUDE_HACK(y, z) y##__##z,這裏對照y即爲vlc_entry,z即爲live555,則最終展開即爲vlc_entry__live555,能夠對其live555.cpp進行預編譯處理,查看結果便可知,最終結果如上面的代碼1.2所示。此爲##的使用。
2. 關於變參宏...
...在C宏中稱爲Variadic Macro,也就是變參宏。好比:
#define myprintf(templt,...) fprintf(stderr,templt,__VA_ARGS__)
或者
#define myprintf(templt,args...) fprintf(stderr,templt,args)
第一個宏中因爲沒有對變參起名,咱們用默認的宏__VA_ARGS__來替代它。第二個宏中,咱們顯式地命名變參爲args,那麼咱們在宏定義中就能夠用args來代指變參了。同C語言的stdcall同樣,變參必須做爲參數表的最有一項出現。當上面的宏中咱們只能提供第一個參數templt時,C標準要求咱們必須寫成:
myprintf(templt,);
的形式。這時的替換過程爲:
myprintf("Error!/n",);
替換爲:
fprintf(stderr,"Error!/n",);
這是一個語法錯誤,不能正常編譯。這個問題通常有兩個解決方法。首先,GNU CPP提供的解決方法容許上面的宏調用寫成:
myprintf(templt);
而它將會被經過替換變成:
fprintf(stderr,"Error!/n",);
很明顯,這裏仍然會產生編譯錯誤(非本例的某些狀況下不會產生編譯錯誤)。除了這種方式外,c99和GNU CPP都支持下面的宏定義方式:
#define myprintf(templt, ...) fprintf(stderr,templt, ##__VAR_ARGS__)
這時,##這個鏈接符號充當的做用就是當__VAR_ARGS__爲空的時候,消除前面的那個逗號。那麼此時的翻譯過程以下:
myprintf(templt);
被轉換爲:
fprintf(stderr,templt);
這樣若是templt合法,將不會產生編譯錯誤。
而在VLC源代碼中一樣有關於...的使用,好比vlc/include/vlc_plugin.h中有以下定義 android
#define vlc_plugin_set(...) vlc_set (opaque, NULL, __VA_ARGS__)
#define vlc_module_set(...) vlc_set (opaque, module, __VA_ARGS__)
#define vlc_config_set(...) vlc_set (opaque, config, __VA_ARGS__)
在代碼1.1中有關於其的使用,這裏以vlc_module_set爲例,代碼1.1中使用以下:
vlc_module_set (VLC_MODULE_NAME, (MODULE_STRING))
這裏VLC_MODULE_NAME和(MODULE_STRING)即替換變參宏...,那麼最終替換的結果即爲以下:
vlc_set (opaque, module, VLC_MODULE_NAME, ("live555"))
以上爲變參宏...的使用。
3. 關於typedef函數指針
3.1)簡單的函數指針的使用
形式1:返回類型(*函數名)(參數表)
char (*pFun)(int);
char glFun(int a){ return;}
void main()
{
pFun = glFun;
(*pFun)(2);
}
第一行定義了一個指針變量pFun。首先咱們根據前面提到的「形式1」認識到它是一個指向某種函數的指針,這種函數參數是一個int型,返回值是char類型。只有第一句咱們還沒法使用這個指針,由於咱們還未對它進行賦值。
第二行定義了一個函數glFun()。該函數正好是一個以int爲參數返回char的函數。咱們要從指針的層次上理解函數——函數的函數名實際上就是一個指針,函數名指向該函數的代碼在內存中的首地址
而後就是main()函數了,它的第一句您應該看得懂了——它將函數glFun的地址賦值給變量pFun。main()函數的第二句中「*pFun」顯然是取pFun所指向地址的內容,固然也就是取出了函數glFun()的內容,而後給定參數爲2。
3.2)使用typedef
形式1:typedef 返回類型(*新類型)(參數表) 編程
typedef char (*PTRFUN)(int);
PTRFUN pFun;
char glFun(int a){ return;}
void main()
{
pFun = glFun;
(*pFun)(2);
}
typedef的功能是定義新的類型。第一句就是定義了一種PTRFUN的類型,並定義這種類型爲指向某種函數的指針,這種函數以一個int爲參數並返回char類型。後面就能夠像使用int,char同樣使用PTRFUN了。
第二行的代碼便使用這個新類型定義了變量pFun,此時就能夠像使用形式1同樣使用這個變量了。
3.3)VLC中函數指針的使用
在VLC中大量使用了函數指針,仍然以vlc/include/vlc_plugin.h中爲例,有以下定義:
typedef int (*vlc_set_cb) (void *, void *, int, ...);
仍然以live555爲例,通過預編譯以後有以下代碼(即上面的代碼1.2):
extern "C" int vlc_entry__live555 (vlc_set_cb vlc_set, void *opaque)
{
module_t *module;
module_config_t *config = __null;
if (vlc_set (opaque, __null, VLC_MODULE_CREATE, &module)) goto error;
if (vlc_set (opaque, module, VLC_MODULE_NAME, ("live555"))) goto error;
那麼這裏vlc_set最終指向的函數究竟是誰呢?咱們從調用vlc_entry__live555()的地方開始。在VLC-Android中定位到vlc/src/modules/bank.c文件中有以下代碼:代碼1.3
extern vlc_plugin_cb vlc_static_modules[];
static void module_InitStaticModules(void)
{
if (!vlc_static_modules)
return;
for (unsigned i = 0; vlc_static_modules[i]; i++) {
module_t *module = module_InitStatic (vlc_static_modules[i]);
if (likely(module != NULL))
module_StoreBank (module);
}
}
而上面的vlc_static_modules[ ]在vlc-android/jni/libvlcjni.h中有以下的定義(這裏僅選取一段代碼):
...
int vlc_entry__live555 (int (*)(void *, void *, int, ...), void *);
...
const void *vlc_static_modules[] =
{...
vlc_entry__avformat,
vlc_entry__h264,
vlc_entry__voc,
vlc_entry__avi,
vlc_entry__live555,
...
}
這裏加入代碼1.3運行到vlc_static_modules[i]=vlc_entry_live555,則module_InitStatic的輸入參數即爲vlc_entry_live555,咱們再看module_InitStatic()的源代碼以下:
/**
* Registers a statically-linked plug-in.
*/
static module_t *module_InitStatic (vlc_plugin_cb entry)
{
/* Initializes the module */
module_t *module = vlc_plugin_describe (entry);
if (unlikely(module == NULL))
return NULL;
module->b_loaded = true;
module->b_unloadable = false;
return module;
}
可知代碼1.3中的參數符合module_InitStatic函數的輸入要求,這裏的entry實際上就指向vlc_entry__live555()函數,咱們再看vlc_plugin_describe()(在vlc/src/modules/entry.c文件中)的源代碼以下:
/**
* Runs a plug-in descriptor. This loads the plug-in meta-data in memory.
*/
module_t *vlc_plugin_describe (vlc_plugin_cb entry)
{
module_t *module = NULL;
vlc_object_t *debug=NULL;
if (entry (vlc_plugin_setter, &module) != 0)
{
if (module != NULL) /* partially initialized plug-in... */
{
vlc_module_destroy (module);
}
module = NULL;
}
return module;
}
注意上面的entry實際上指向vlc_entry__live555()函數,咱們再看代碼1.2中vlc_entry__live555()通過預編譯以後的函數定義,則上面的函數調用實際上就成爲以下所示:
vlc_entry__live555 (vlc_plugin_setter, &module)
那麼整個調用過程就已經明朗了,最終vlc_set指向的實際上爲vlc_plugin_setter()函數,若是咱們再回到vlc_entry__live555()的函數定義中,最終的調用代碼即爲以下:
extern "C" int vlc_entry__live555 (vlc_set_cb vlc_set, void *opaque)
{
module_t *module;
module_config_t *config = __null;
if (vlc_plugin_setter (opaque, __null, VLC_MODULE_CREATE, &module)) goto error;
if (vlc_plugin_setter (opaque, module, VLC_MODULE_NAME, ("live555"))) goto error;
咱們再看看vlc_plugin_setter()的函數定義(在vlc/src/modules/entry.c文件中):
/**
* Callback for the plugin descriptor functions.
*/
static int vlc_plugin_setter (void *plugin, void *tgt, int propid, ...)
{
module_t **pprimary = plugin;
module_t *module = tgt;
module_config_t *item = tgt;
va_list ap;
int ret = 0;
vlc_object_t* debug=NULL;
va_start (ap, propid);
switch (propid)
{
case VLC_MODULE_CREATE:
{
...
則最終調用的函數代碼執行過程就一目瞭然了。注意這裏涉及到變參函數取值的知識,如上面代碼中的va_list ap; va_start(ap, propid); 下面作一點解釋:
函數參數其實是以數據結構棧的形式存取,從右至左入棧,所以咱們只要知道第一個參數的地址,每個參數的類型,就能夠知道後面每個參數的地址,這也就是變參宏...的原理。
VA_LIST 是在C語言中解決變參問題的一組宏
它有這麼幾個成員:
1) va_list型變量:
#ifdef _M_ALPHA
typedef struct {
char *a0; /* pointer to first homed integer argument */
int offset; /* byte offset of next parameter */
} va_list;
#else
typedef char * va_list;
#endif
2)_INTSIZEOF 宏,獲取類型佔用的空間長度,最小佔用長度爲int的整數倍:
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
3)VA_START宏,獲取可變參數列表的第一個參數的地址(ap是類型爲va_list的指針,v是可變參數最左邊的參數):
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
4)VA_ARG宏,獲取可變參數的當前參數,返回指定類型並將指針指向下一參數(t參數描述了當前參數的類型):
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
5)VA_END宏,清空va_list可變參數列表:
#define va_end(ap) ( ap = (va_list)0 )
VA_LIST的用法:
(1)首先在函數裏定義一具VA_LIST型的變量,這個變量是指向參數的指針;
(2)而後用VA_START宏初始化變量剛定義的VA_LIST變量;
(3)而後用VA_ARG返回可變的參數,VA_ARG的第二個參數是你要返回的參數的類型(若是函數有多個可變參數的,依次調用VA_ARG獲取各個參數);
(4)最後用VA_END宏結束可變參數的獲取。
使用VA_LIST應該注意的問題:
(1)可變參數的類型和個數徹底由程序代碼控制,它並不能智能地識別不一樣參數的個數和類型;
(2)若是咱們不須要一一詳解每一個參數,只須要將可變列表拷貝至某個緩衝,可用vsprintf函數;
(3)由於編譯器對可變參數的函數的原型檢查不夠嚴格,對編程查錯不利.不利於咱們寫出高質量的代碼;
4. 關於extern"C"的使用
咱們在上面的代碼中看到關於以下的代碼:
extern "C" int vlc_entry__live555 (vlc_set_cb, void *);
那麼這個extern "C"到底表明什麼意思呢?這裏做以下解釋:
extern "C" 包含雙重含義,從字面上便可獲得:首先,被它修飾的目標是「extern」的;其次,被它修飾的目標是「C」的。讓咱們來詳細解讀這兩重含義。
(1)extern "C"限定的函數或變量是extern類型的;
extern是C/C++語言中代表函數和全局變量做用範圍(可見性)的關鍵字,該關鍵字告訴編譯器,其聲明的函數和變量能夠在本模塊或其它模塊中使用。記住,下列語句:
extern int a;
僅僅是一個變量的聲明,其並非在定義變量a,並未爲a分配內存空間。變量a在全部模塊中做爲一種全局變量只能被定義一次,不然會出現鏈接錯誤。
一般,在模塊的頭文件中對本模塊提供給其它模塊引用的函數和全局變量以關鍵字extern聲明。例如,若是模塊B欲引用該模塊A中定義的全局變量和函數時只需包含模塊A的頭文件便可。這樣,模塊B中調用模塊A中的函數時,在編譯階段,模塊B雖然找不到該函數,可是並不會報錯;它會在鏈接階段中從模塊A編譯生成的目標代碼中找到此函數。與extern對應的關鍵字是static,被它修飾的全局變量和函數只能在本模塊中使用。所以,一個函數或變量只可能被本模塊使用時,其不可能被extern 「C」修飾。
(2)被extern "C"修飾的變量和函數是按照C語言方式編譯和連接的;
未加extern 「C」聲明時的編譯方式
首先看看C++中對相似C的函數是怎樣編譯的。
做爲一種面向對象的語言,C++支持函數重載,而過程式語言C則不支持。函數被C++編譯後在符號庫中的名字與C語言的不一樣。例如,假設某個函數的原型爲:
void foo( int x, int y );
該函數被C編譯器編譯後在符號庫中的名字爲_foo,而C++編譯器則會產生像_foo_int_int之類的名字(不一樣的編譯器可能生成的名字不一樣,可是都採用了相同的機制,生成的新名字稱爲「mangled name」)。_foo_int_int這樣的名字包含了函數名、函數參數數量及類型信息,C++就是靠這種機制來實現函數重載的。例如,在C++中,函數void foo( int x, int y )與void foo( int x, float y )編譯生成的符號是不相同的,後者爲_foo_int_float。
一樣地,C++中的變量除支持局部變量外,還支持類成員變量和全局變量。用戶所編寫程序的類成員變量可能與全局變量同名,咱們以"."來區分。而本質上,編譯器在進行編譯時,與函數的處理類似,也爲類中的變量取了一個獨一無二的名字,這個名字與用戶程序中同名的全局變量名字不一樣。
未加extern "C"聲明時的鏈接方式
假設在C++中,模塊A的頭文件以下:
// 模塊A頭文件 moduleA.h
#ifndef MODULE_A_H
#define MODULE_A_H
int foo( int x, int y );
#endif
在模塊B中引用該函數:
// 模塊B實現文件 moduleB.cpp
#i nclude "moduleA.h"
foo(2,3);
實際上,在鏈接階段,鏈接器會從模塊A生成的目標文件moduleA.obj中尋找_foo_int_int這樣的符號!
加extern "C"聲明後的編譯和鏈接方式
加extern "C"聲明後,模塊A的頭文件變爲:
// 模塊A頭文件 moduleA.h
#ifndef MODULE_A_H
#define MODULE_A_H
extern "C" int foo( int x, int y );
#endif
在模塊B的實現文件中仍然調用foo( 2,3 ),其結果是:
(1)模塊A編譯生成foo的目標代碼時,沒有對其名字進行特殊處理,採用了C語言的方式;
(2)鏈接器在爲模塊B的目標代碼尋找foo(2,3)調用時,尋找的是未經修改的符號名_foo。
若是在模塊A中函數聲明瞭foo爲extern "C"類型,而模塊B中包含的是extern int foo( int x, int y ) ,則模塊B找不到模塊A中的函數;反之亦然。
因此,能夠用一句話歸納extern 「C」這個聲明的真實目的(任何語言中的任何語法特性的誕生都不是隨意而爲的,來源於真實世界的需求驅動。咱們在思考問題時,不能只停留在這個語言是怎麼作的,還要問一問它爲何要這麼作,動機是什麼,這樣咱們能夠更深刻地理解許多問題):實現C++與C及其它語言的混合編程。
以上即爲在分析VLC源碼過程當中用到的知識總結。
參考文章:
http://blog.csdn.net/dotphoenix/article/details/4345174
http://blog.csdn.net/qll125596718/article/details/6891881
http://www.cppblog.com/xmoss/archive/2009/07/20/90680.html
http://www.52rd.com/Blog/Archive_Thread.asp?SID=11774