官术网_书友最值得收藏!

1.5.3 參數(shù)的合法性檢查

很多細(xì)致的程序員會在每個函數(shù)的入口處檢查所有傳入?yún)?shù)的合法性,尤其是指針。比如,下面的函數(shù)會銷毀一個映射表:

int pcutils_map_destroy(pcutils_map* map)
{
    if (map == NULL)
        return -1;
 
    pcutils_map_clear(map);
    free(map);
    return 0;
}

該函數(shù)首先判斷傳入的參數(shù)map是否為空指針。可以預(yù)期,傳入該函數(shù)的參數(shù)map是由名為pcutils_map_create()的函數(shù)返回的。作為創(chuàng)建對象的函數(shù)接口,一般返回空值(NULL指針)表示失敗,返回非空值則表示成功;如果pcutils_map_create()函數(shù)返回空值,則不用再調(diào)用pcutils_map_destroy()函數(shù)。換句話說,在調(diào)用pcutils_map_destroy()函數(shù)時,除非誤用,否則不會給這個函數(shù)傳遞一個空值。

因此,這種判斷貌似有必要,但仔細(xì)考慮后就會發(fā)現(xiàn)意義不大。在上面的代碼中,程序?qū)?code>NULL作為非法值做了特別處理,但如果傳入的指針值為1或者?1,它們顯然也是非法值,那為何不對這兩種情況做判斷并返回對應(yīng)的錯誤值呢?更進(jìn)一步地,如何判斷一個尚未分配的地址值呢?

實質(zhì)上,C語言并沒有提供任何能夠判斷一個指針的值是否合法的語言級能力或者機(jī)制。我們所知道的不合法的指針值通常就是0、?1,以及特定情況下和當(dāng)前處理器的位寬不對齊的整數(shù)值。比如在32位系統(tǒng)中,對于指向32位整數(shù)的指針來講,任何不能被4整除的指針值大概率是非法的。除此之外,我們沒有其他有效的手段來判斷一個指針值的合法性。因此,這類參數(shù)的有效性檢查其實是多余的。

再者,在頻繁調(diào)用的函數(shù)中執(zhí)行此類不必要的參數(shù)有效性檢查,會大大降低程序的執(zhí)行效率。

因此,上述代碼的最佳實現(xiàn)應(yīng)該如下:

void pcutils_map_destroy(pcutils_map* map)
{
    pcutils_map_clear(map);
    free(map);
}

我們沒有必要僅針對空值做參數(shù)的有效性檢查。一方面,這種檢查并不能覆蓋所有的情形;另一方面,如果我們僅僅需要檢查空值這種情形,那么程序會很快因為訪問空指針而出錯。后一種情況說明調(diào)用者誤傳了參數(shù),在程序的開發(fā)階段,借助調(diào)試器,我們可以迅速定位缺陷所在。

但在某些情況下,我們?nèi)匀幌M谡{(diào)用這類函數(shù)時,對傳入的常見非法值NULL做一些特殊處理,以便可以及時發(fā)現(xiàn)調(diào)用者的問題。為此,我們可以使用assert()assert()本質(zhì)上是一個宏,而非函數(shù),而且這個宏的行為依賴于NDEBUG宏。assert()通常的定義如下:

#ifdef NDEBUG
#   define assert(exp)      \
        do {                \
        } while (0)
#else   /* defined NDEBUG */
#   define assert(exp)      \
        do {                \
            if (!(exp))     \
                abort();    \
        } while (0)
#endif  /* not defined NDEBUG */

在上面的代碼中,NDEBUG是一個約定俗成的全局宏,通常由構(gòu)建系統(tǒng)定義。當(dāng)NDEBUG宏被定義時,意味著程序?qū)⒈粯?gòu)建為發(fā)布版本,assert()不做任何事情;反之,當(dāng)程序被構(gòu)建為調(diào)試版本時,assert()將判斷表達(dá)式exp的真假,若為假,則調(diào)用abort()函數(shù)終止程序的運行。

如此一來,我們可以將上述代碼進(jìn)一步修改為如下形式:

#include <assert.h>
 
void pcutils_map_destroy(pcutils_map* map)
{
    assert(map != NULL);
    pcutils_map_clear(map);
    free(map);
}

此外,還有一種針對參數(shù)的合法性檢查,或者說針對常規(guī)條件分支的優(yōu)化方法,常見于一些優(yōu)秀的C語言開源項目中。程序清單1.4列出了glib(Linux系統(tǒng)常用的C工具函數(shù)庫,在一些場景中也可寫作GLib)中用于快速驗證UTF-8編碼有效性的函數(shù)。

程序清單1.4 使用UNLIKELY宏優(yōu)化條件分支

#define VALIDATE_BYTE(mask, expect)                         \
do {                                                        \
    if (UNLIKELY((*(uint8_t *)p & (mask)) != (expect)))     \
        goto error;                                         \
} while (0)
 
/* see IETF RFC 3629 Section 4 */
 
static const char *
fast_validate(const char *str)
{
    size_t n = 0;
    const char *p;
 
    for (p = str; *p; p++) {
        if (*(uint8_t *)p < 128) {
            n++;
        }
        else {
            const char *last;
 
            last = p;
            if (*(uint8_t *)p < 0xe0) {         /* 110xxxxx */
                if (UNLIKELY (*(uint8_t *)p < 0xc2))
                    goto error;
            }
            else {
                if (*(uint8_t *)p < 0xf0) {     /* 1110xxxx */
                    switch (*(uint8_t *)p++ & 0x0f) {
                        ...
                    }
                }
                else if (*(uint8_t *)p < 0xf5) {
                    /* 11110xxx excluding out-of-range */
                    switch (*(uint8_t *)p++ & 0x07) {
                        ...
                    }
                    p++;
                    VALIDATE_BYTE(0xc0, 0x80); /* 10xxxxxx */
                }
                else
                    goto error;
            }
 
            p++;
            VALIDATE_BYTE(0xc0, 0x80);          /* 10xxxxxx */
            n++;
            continue;
error:
            return last;
        }
    }
 
    return p;
}

上述代碼多次使用了UNLIKELY 宏,用于判斷一些不太可能出現(xiàn)在正常UTF-8編碼中的字符。這個宏以及成對定義的LIKELY 宏利用了現(xiàn)代編譯器的一些特性,它們可以告訴編譯器一個分支判斷的結(jié)果為真或者為假的可能性是大還是小。利用這兩個宏,我們可以協(xié)助編譯器充分利用處理器的分支預(yù)測能力,提高編譯后代碼的執(zhí)行效率。

因此,如果非要檢查傳入?yún)?shù)的有效性,我們可以利用UNLIKELY宏,對旨在銷毀映射表的代碼作如下優(yōu)化:

int pcutils_map_destroy(pcutils_map* map)
{
    if (UNLIKELY(map == NULL))
        return -1;
 
    pcutils_map_clear(map);
    free(map);
    return 0;
}

這樣編譯器就會認(rèn)為出現(xiàn)map == NULL這一條件的可能性較低,從而在生成最終的機(jī)器指令時,通過適當(dāng)?shù)膬?yōu)化,將可能性較低的條件判斷對性能的影響降到最小。

注意,LIKELYUNLIKELY宏是非標(biāo)準(zhǔn)宏,目前僅GCC或兼容GCC的編譯器支持。這兩個宏通常定義如下:

/* LIKELY */
#if !defined(LIKELY) && defined(__GNUC__)
#define LIKELY(x) __builtin_expect(!!(x), 1)
#endif
 
#if !defined(LIKELY)
#define LIKELY(x) (x)
#endif
 
/* UNLIKELY */
#if !defined(UNLIKELY) && defined(__GNUC__)
#define UNLIKELY(x) __builtin_expect(!!(x), 0)
#endif
 
#if !defined(UNLIKELY)
#define UNLIKELY(x) (x)
#endif

其中使用了__builtin_expect這一GCC特有的優(yōu)化指令。

主站蜘蛛池模板: 右玉县| 渝中区| 连南| 清水河县| 沛县| 舒兰市| 渭源县| 阆中市| 宁陵县| 出国| 清原| 都匀市| 海南省| 金堂县| 固阳县| 淮南市| 阜新| 珠海市| 会昌县| 元氏县| 汨罗市| 泸西县| 洛南县| 常宁市| 运城市| 福泉市| 和平区| 长沙市| 城步| 呈贡县| 交城县| 罗山县| 马尔康县| 灵丘县| 开平市| 子洲县| 河间市| 瑞昌市| 宁远县| 乌恰县| 洞头县|