用表驅動代替switch-case

來源:互聯網
上載者:User

轉自:http://blog.csdn.net/orbit/article/details/2120086

 

 

不知道從什麼時候開始,switch-case語句成了代碼壞味道的代名詞,寫代碼的時候小心翼翼地避開它,看到別人代碼中的switch-case就皺眉頭,想想其實大可不必這樣,switch-case語句並不是代碼壞味道的根源,壞味道來自糟糕的代碼(結構)設計,比如過多的switch-case分支,或者多重switch-case嵌套等等,這些都將導致代碼可讀性下降,如果再加上代碼風格較差,代碼不對齊,那麼壞味道就相當地大了。

    簡短的switch-case還是繼續用吧,但是對於分支太多的長switch-case最好能想辦法化解開,那麼什麼算長什麼算短呢?我也不知道,就以在最低解析度的顯示器上能夠在一個視窗中完整顯示整個switch-case塊為判斷依據吧。化解長switch-case的方法有很多種,用函數封裝或者宏取代case塊是治標不治本的方法,使用表驅動通常是治療這種頑症的有效方法,本文將介紹如何用表驅動方法化解長switch-case。

    還是用例子說明問題吧,假設我們要為一個系統編寫驅動,系統已經定義好了如下所示的複用介面(MUX):

STATUS DriverIoControl(UINT function_no, PVOID para_in, PVOID para_out)

使用者層程式通過複用介面調用驅動,功能號就是function_no,驅動程式負責實現具體的DriverIoControl()函數完成相應的功能。這是使用switch-case的典型情境,先看一個使用switch-case的方案:

STATUS DriverIoControl(UINT function_no, PVOID para_in, PVOID para_out){    STATUS rc;    switch(function_no)    {        case PROCESSA:            rc = ProcessA(para_in,para_out);            break;        case PROCESSB:            rc = ProcessB(para_in,para_out);            break;        case PROCESSC:            rc = ProcessC(para_in,para_out);            break;                    ..........                default:            rc = UN_SUPPORT;            break    }    return rc;}                                                                        STATUS ProcessA(PVOID para_in, PVOID para_out){    //一些代碼....}STATUS ProcessB(PVOID para_in, PVOID para_out){    //一些代碼....}STATUS ProcessC(PVOID para_in, PVOID para_out){    //一些代碼....}

................

這個方案中規中矩,但是如果驅動很複雜,功能很多,那麼DriverIoControl函數代碼的長度是相當可觀的,好像已經聞到壞味道了,呵呵。現在換成使用宏的解決方案:

#define DISPATCH_BEGIN(func)      switch(func) /                                  { #define DISPATCH_FUNCTION(func_c, function) case func_c: /                                                rc = function(para_in,para_out); /                                              break;#define DISPATCH_END(code)        default: /                                      rc = code; /                                   }STATUS DriverIoControl(UINT function_no, PVOID para_in, PVOID para_out){    STATUS rc;    DISPATCH_BEGIN(function_no)    DISPATCH_FUNCTION(PROCESSA,ProcessA)    DISPATCH_FUNCTION(PROCESSB,ProcessB)    DISPATCH_FUNCTION(PROCESSC,ProcessC)    ........................        DISPATCH_END(UN_SUPPORT)    return rc;}

嗯,好一點,但好不到哪裡去,只是用一行代替多行而已,並不能改變代碼隨著功能增多線性增長的趨勢。羅嗦一下,我不喜歡宏的原因很簡單,目前很少有(說實話,是我確實沒有見過)調試器支援對宏的展開調試。這很麻煩,當一段摻雜著宏的代碼沒有達到預期的目的時,你不得不一遍一遍地在心裡展開你的宏,以確定它是沒有問題的(或者,你根本不能確定,只能假設它沒有問題)。

    現在看看錶驅動方案,如果系統約定的function_no是個連續的線性數字,或者是某個區間上的連續線性數字,那麼驅動表可以簡單就是一個線性數組,看看例子:

typedef STATUS (*ProcessFuncPtr)(PVOID para_in, PVOID para_out);typedef struct tagDispatchItem{    ProcessFuncPtr func_ptr;}DISPATCH_ITEM;DISPATCH_ITEM  dispatch_table[MAX_DISPATCH_ITEM];STATUS DriverIoControl(UINT function_no, PVOID para_in, PVOID para_out){    /*或者需要對function_no調整,使其區間落到[0 - MAX_DISPATCH_ITEM)*/    if((function_no >= 0) && (function_no < MAX_DISPATCH_ITEM))        return dispatch_table[function_no].func_ptr(para_in,para_out);    else        return UN_SUPPORT;}如果function_no不是線性,那就需要查表:typedef struct tagDispatchItem{    UNIT func_no;    ProcessFuncPtr func_ptr;}DISPATCH_ITEM;DISPATCH_ITEM  dispatch_table[MAX_DISPATCH_ITEM];STATUS DriverIoControl(UINT function_no, PVOID para_in, PVOID para_out){    int i,find = -1;        for(i = 0; i < MAX_DISPATCH_ITEM; i++)    {        if(function_no == dispatch_table[i].func_no)        {            return dispatch_table[i].func_ptr(para_in,para_out);        }    }    return UN_SUPPORT;}

使用表驅動的好處就是DriverIoControl的代碼就這幾行,添加新功能,只需要維護驅動表dispatch_table就行了,就這樣擺脫了冗長乏味的switch-case。

    前面例子中的switch-case語句中各個case分支參數比較簡單整齊,也就是各個case分支都是用相同的參數para_in和para_out,如果各個分支使用的參數不整齊怎麼辦?那就需要封裝,通常是用struct和union結合定義一個統一的資料結構做為介面參數,不同的分支dispatch函數內部根據需要從這個統一的資料結構中提取相應的資料。具體方法就是首先定義統一的dispatch函數介面,例如:

typedef STATUS (*ProcessFuncPtr)(DISPATCH_DATA *para);

然後用struct和union相結合定義DISPATCH_DATA,DISPATCH_DATA通常有這樣的結構:

typedef struct tagDISPATCH_DATA
{
    //此處聲明所有的分支公用的資料
    union
    {
        //此處聲明processA分支使用的資料
    }processA;
    union
    {
        //此處聲明processA分支使用的資料
    }processB;
    union
    {
        //此處聲明processA分支使用的資料
    }processC;
    ......
}DISPATCH_DATA;

做過Windows驅動程式的朋友肯定對IRP不陌生,IRP就是一個典型的例子。

    就這樣吧,打完,收工。

 

=============================================================================

 

以下是原文評論:說的是表驅動某些情況下會帶來效率的損耗和可能在驅動裡導致潛在的問題。

因為使用函數指標數組在迴圈查詢來代替直接調用,一方面可能降低運行效率,每次IOCTL調用都要走一個迴圈,而且function越多越慢.(而如果你仔細查看switch產生得彙編代碼,其實編譯器在這種情況下也會最佳化成查表法的,只不過更有效率).另一方面就是將一些Bug由編譯時間報錯延遲到了運行時才發生,導致籃屏.而效率和安全是驅動程式最需要的.

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.