在學習Nginx中的過濾模塊開發時, 遇到了使用全局變量和靜態全局變量構成的單向函數鏈表, 對於我這種新手來說, 有些無法理解.
從分配內存空間看:
全局變量、靜態局部變量、靜態全局變量都在靜態存儲區分配空間,而局部變量在棧分配空間。
區別:
全局變量本身就是靜態存儲方式,靜態全局變量當然也是靜態存儲方式。這兩者在存儲方式上沒有什麼不同。區別在於非靜態全局變量的作用域是整個源程序,當一個源程序由多個源文件組成時,非靜態的全局變量在各個源文件中都是有效的。而靜態全局變量則限制了其作用域,即只在定義該變量的源文件內有效,在同一源程序的其他源文件中不能使用它。由於靜態全局變量的作用域侷限於一個源文件內,只能爲該源文件內的函數公用,因此可以避免在其他源文件中引起錯誤。
所以, 自己按照Nginx中構建鏈表的方式也寫了一個小小的實驗程序如下.
整個框架目的:
目的是爲了能實現一系列相同類型的函數(即可以以同用一個函數指針指向的衆多函數)
如果想要添加能執行的函數(這裏我們稱爲模塊), 只需要編寫新的文件而不需要改動原有的代碼
框架結構體:
首先, 這是頭文件. 用於制定每個模塊需要的一些信息.
只是這裏因爲程序太小太簡陋, 所以不需要什麼信息. 只需要每個模塊的初始化函數. 初始化函數的用途會在下面講
//from "conf.h"
#include <stdio.h>
#ifndef CONF_H
#define CONF_H
//形成這個函數鏈表的每個(函數)元素的類型
typedef int (*function_init)();
//每個模塊真正工作的函數的指針
typedef void (*function_job)();
typedef struct{//每個模塊結構體當前只包含必須的初始化函數
function_init init;
}module_t;
#endif
框架自帶的模塊.
//from "module_A.c"
#include "conf.h"
//這個function_top_job函數是整個框架用來遍歷模塊函數的入口, 換句話說, 就是函數鏈表的入口
extern function_job function_top_job;
//這個必須是靜態全局變量, 才能讓每個模塊具有相同的變量名字但指向不同的函數
static function_job function_next_job;
static void module_A_job();
static int module_A_init();
//對於模塊A的定義.
module_t module_A = {
module_A_init
};
//模塊A的真正工作函數, 其中要包含其調用下一個函數元素的代碼
static void
module_A_job(){
printf("I am from module A\n");
//看是否執行到鏈表尾部. 如果還有則繼續執行
if(function_next_job)
function_next_job();
}
//模塊A的初始化函數. 從這裏明顯可以看出和Nginx中相同的用法
//將每個模塊插入在鏈表頭部, 所以後初始化的函數會被優先執行
static int
module_A_init(){
//靜態變量指向原來的整個框架的鏈表首部
function_next_job = function_top_job;
//整個框架的鏈表首部被重新賦值
function_top_job = module_A_job;
}
模塊整合
我們知道, 每個模塊都需要執行它的初始化函數之後纔會被放到鏈表函數中去, 那麼我們不可能在每次添加新模塊的時候都
到main函數中去添加新模塊的初始化函數調用代碼, 所以就需要使用下面這個數組
在Nginx中, 類似的文件叫做 /nginx/obj/ngx_modules.c, 這個文件是由configure文件生成的. 因爲我還不會編寫這個東西...所以每次添加模塊的話, 還
需要到這裏來做一些小小的修改. 不過這個是單獨的文件, 所以改起來比較清晰方便.
//from "modules.c"
#include "conf.h"
//其他文件的全局變量
extern module_t module_A;
//將其他文件的模塊結構體放到這個模塊數組中來
module_t *modules[] = {
&module_A,
NULL //放一個NULL在最後也就方便遍歷, 不需要知道到底有多少元素在這個modules數組中
};
最後, 整個框架需要一個main函數:
//from "main.c"
#include "conf.h"
//定義框架的函數鏈表入口
function_job function_top_job = NULL;
//所有模塊都被放在這個模塊指針數組中
extern module_t *modules[];
int main()
{
int i;
//執行每個模塊的初始化函數. 形成函數鏈表
for(i=0; modules[i]; i++)
modules[i]->init();
//從入口開始執行, 遍歷每個函數元素
function_top_job();
return 0;
}
編譯執行:
好了, 基本的一個框架已經完成了. 如果在編譯後執行, 那麼輸出的內容就是:
$ gcc -o exe conf.h module_A.c modules.c main.c
$ ./exe
I am from module A
添加新模塊
現在來添加新的模塊. 添加新的模塊在原本的設計下也就顯得十分簡單了.
比如, 現在想要添加模塊B和模塊C
//from "module_B.c"
#include "conf.h"
extern function_job function_top_job;
static function_job function_next_job;
static void module_B_job();
static int module_B_init();
module_t module_B = {
module_B_init
};
static void
module_B_job(){
printf("I am from module B\n");
if(function_next_job)
function_next_job();
}
static int
module_B_init(){
function_next_job = function_top_job;
function_top_job = module_B_job;
}
//from "module_C.c"
#include "conf.h"
extern function_job function_top_job;
static function_job function_next_job;
static void module_C_job();
static int module_C_init();
module_t module_C = {
module_C_init
};
static void
module_C_job(){
printf("I am from module C\n");
if(function_next_job)
function_next_job();
}
static int
module_C_init(){
function_next_job = function_top_job;
function_top_job = module_C_job;
}
除了添加這兩個源文件外, 還需要去modules.c文件中進行簡單的修改, 改成下面這個樣子:
#include "conf.h"
extern module_t module_A;
extern module_t module_B;
extern module_t module_C;
module_t *modules[] = {
&module_A,
&module_B,
&module_C,
NULL
};
最後編譯執行:
$ gcc -o exe conf.h module_A.c modules.c main.c module_B.c module_C.c
$ ./exe
I am from module C
I am from module B
I am from module A
可以發現, 函數的執行順序與插入操作是相符合的.
好了, 可以發現, 添加的東西與原來模塊的格式相同, 這裏內容雖然也差不多, 但完全可以改成完全不同的操作. 只是格式必須要遵守.
另外需要修改的modules.c 也不是很困難...