溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

嵌入式C語(yǔ)言自我修養(yǎng) 03:宏構(gòu)造利器 - 語(yǔ)句表達(dá)式

發(fā)布時(shí)間:2020-03-01 16:07:26 來(lái)源:網(wǎng)絡(luò) 閱讀:252 作者:宅學(xué)部落 欄目:系統(tǒng)運(yùn)維

3.1 基礎(chǔ)復(fù)習(xí):表達(dá)式、語(yǔ)句和代碼塊

表達(dá)式

表達(dá)式和語(yǔ)句是 C 語(yǔ)言中的基礎(chǔ)概念。什么是表達(dá)式呢?表達(dá)式就是由一系列操作符和操作數(shù)構(gòu)成的式子。操作符可以是 C 語(yǔ)言標(biāo)準(zhǔn)規(guī)定的各種算術(shù)運(yùn)算符、邏輯運(yùn)算符、賦值運(yùn)算符、比較運(yùn)算符等。操作數(shù)可以是一個(gè)常量,也可以是一個(gè)變量。表達(dá)式也可以沒有操作符,單獨(dú)的一個(gè)常量甚至是一個(gè)字符串,也是一個(gè)表達(dá)式。下面的字符序列都是表達(dá)式:

  • 2 + 3
  • 2
  • i = 2 + 3
  • i = i++ + 3
  • "wit"

表達(dá)式一般用來(lái)數(shù)據(jù)計(jì)算或?qū)崿F(xiàn)某種功能的算法。表達(dá)式有2個(gè)基本屬性:值和類型。如上面的表達(dá)式2+3,它的值為5。根據(jù)操作符的不同,表達(dá)式可以分為多種類型,如:

  • 關(guān)系表達(dá)式
  • 邏輯表達(dá)式
  • 條件表達(dá)式
  • 賦值表達(dá)式
  • 算術(shù)表達(dá)式
  • ……

語(yǔ)句

語(yǔ)句是構(gòu)成程序的基本單元,一般形式如下:

表達(dá)式 ;
i = 2 + 3 ;

表達(dá)式的后面加一個(gè); 就構(gòu)成了一條基本的語(yǔ)句。編譯器在編譯程序、解析程序時(shí),不是根據(jù)物理行,而是根據(jù)分號(hào) ; 來(lái)判斷一條語(yǔ)句的結(jié)束標(biāo)記的。如 i = 2 + 3; 這條語(yǔ)句,你寫成下面的形式也是可以編譯通過(guò)的:

i =
2 + 
3
;

代碼塊

不同的語(yǔ)句,使用大括號(hào){}括起來(lái),就構(gòu)成了一個(gè)代碼塊。C 語(yǔ)言允許在代碼塊里定義一個(gè)變量,這個(gè)變量的作用域也僅限于這個(gè)代碼塊內(nèi),因?yàn)榫幾g器就是根據(jù){}來(lái)做入棧出棧操作來(lái)管理變量的作用域的。如下面的程序:

int main(void)
{
    int i = 3; 
    printf("i=%d\n",i);
    {
        int i = 4;
        printf("i=%d\n",i);
    }
    printf("i=%d\n",i);
    return 0;
}
運(yùn)行結(jié)果為:
i=3
i=4
i=3

3.2 語(yǔ)句表達(dá)式

語(yǔ)句表達(dá)式的定義

GNU C 對(duì) C 標(biāo)準(zhǔn)作了擴(kuò)展,允許在一個(gè)表達(dá)式里內(nèi)嵌語(yǔ)句,允許在表達(dá)式內(nèi)部使用局部變量、for 循環(huán)和 goto 跳轉(zhuǎn)語(yǔ)句。這樣的表達(dá)式,我們稱之為語(yǔ)句表達(dá)式。語(yǔ)句表達(dá)式的格式如下:

({ 表達(dá)式1; 表達(dá)式2; 表達(dá)式3; })

語(yǔ)句表達(dá)式最外面使用小括號(hào)()括起來(lái),里面一對(duì)大括號(hào){}包起來(lái)的是代碼塊,代碼塊里允許內(nèi)嵌各種語(yǔ)句。語(yǔ)句的格式可以是 “表達(dá)式;”這種一般格式的語(yǔ)句,也可以是循環(huán)、跳轉(zhuǎn)等語(yǔ)句。

跟一般表達(dá)式一樣,語(yǔ)句表達(dá)式也有自己的值。語(yǔ)句表達(dá)式的值為內(nèi)嵌語(yǔ)句中最后一個(gè)表達(dá)式的值。我們舉個(gè)例子,使用語(yǔ)句表達(dá)式求值。

int main(void)
{
    int sum = 0;
    sum = 
    ({
        int s = 0;
        for( int i = 0; i < 10; i++)
            s = s + i;
            s;
    });
    printf("sum = %d\n",sum);
    return 0;
}

在上面的程序中,通過(guò)語(yǔ)句表達(dá)式實(shí)現(xiàn)了從1到10的累加求和,因?yàn)檎Z(yǔ)句表達(dá)式的值等于最后一個(gè)表達(dá)式的值,所以在 for 循環(huán)的后面,我們要添加一個(gè) s; 語(yǔ)句表示整個(gè)語(yǔ)句表達(dá)式的值。如果不加這一句,你會(huì)發(fā)現(xiàn) sum=0?;蛘吣銓⑦@一行語(yǔ)句改為100; 你會(huì)發(fā)現(xiàn)最后 sum 的值就變成了100,這是因?yàn)檎Z(yǔ)句表達(dá)式的值總等于最后一個(gè)表達(dá)式的值。

語(yǔ)句表達(dá)式內(nèi)使用 goto 跳轉(zhuǎn)

在上面的程序中,我們?cè)谡Z(yǔ)句表達(dá)式內(nèi)定義了局部變量,使用了 for 循環(huán)語(yǔ)句。在語(yǔ)句表達(dá)式內(nèi),我們同樣也可以使用 goto 進(jìn)行跳轉(zhuǎn)。

int main(void)
{
    int sum = 0;
    sum = 
    ({
        int s = 0;
        for( int i = 0; i < 10; i++)
            s = s + i;
            goto here;
            s;  
    });
    printf("sum = %d\n",sum);
here:
    printf("here:\n");
    printf("sum = %d\n",sum);
    return 0;
}

3.3 在宏定義中使用語(yǔ)句表達(dá)式

語(yǔ)句表達(dá)式的亮點(diǎn)在于定義復(fù)雜功能的宏。使用語(yǔ)句表達(dá)式來(lái)定義宏,不僅可以實(shí)現(xiàn)復(fù)雜的功能,而且還能避免宏定義帶來(lái)的歧義和漏洞。下面就以一個(gè)宏定義例子,讓我們來(lái)見識(shí)見識(shí)語(yǔ)句表達(dá)式在宏定義中的強(qiáng)悍殺傷力!

假如你此刻正在面試,面試職位是:Linux C 語(yǔ)言開發(fā)工程師。面試官給你出了一道題:

請(qǐng)定義一個(gè)宏,求兩個(gè)數(shù)的最大值。

別看這么簡(jiǎn)單的一個(gè)考題,面試官就能根據(jù)你寫出的宏,來(lái)判斷你的 C 語(yǔ)言功底,來(lái)決定給不給你 Offer。

合格

對(duì)于學(xué)過(guò) C 語(yǔ)言的同學(xué),寫出這個(gè)宏基本上不是什么難事,使用條件運(yùn)算符就能完成:

#define  MAX(x,y)  x > y ? x : y

這是最基本的 C 語(yǔ)言語(yǔ)法,如果連這個(gè)也寫不出來(lái),估計(jì)場(chǎng)面會(huì)比較尷尬。面試官為了緩解尷尬,一般會(huì)對(duì)你說(shuō):小伙子,你很棒,回去等消息吧,有消息,我們會(huì)通知你!這時(shí)候,你應(yīng)該明白:不用再等了,趕緊把這篇文章看完,接著面下家。這個(gè)宏能寫出來(lái),也不要覺得你很牛 X,因?yàn)檫@只能說(shuō)明你有了 C 語(yǔ)言的基礎(chǔ),但還有很大的進(jìn)步空間。比如,我們寫一個(gè)程序,驗(yàn)證一下我們定義的宏是否正確:

#define MAX(x,y) x > y ? x : y
int main(void)
{
    printf("max=%d",MAX(1,2));
    printf("max=%d",MAX(2,1));
    printf("max=%d",MAX(2,2));
    printf("max=%d",MAX(1!=1,1!=2));
    return 0;
}

測(cè)試程序嗎,我們肯定要把各種可能出現(xiàn)的情況都測(cè)一遍。這不,測(cè)試第4行語(yǔ)句,當(dāng)宏的參數(shù)是一個(gè)表達(dá)式,發(fā)現(xiàn)實(shí)際運(yùn)行結(jié)果為 max=0,跟我們預(yù)期結(jié)果 max=1 不一樣。這是因?yàn)?,宏展開后,就變成了這個(gè)樣子:

printf("max=%d",1!=1>1!=2?1!=1:1!=2);

因?yàn)楸容^運(yùn)算符 > 的優(yōu)先級(jí)為6,大于 !=(優(yōu)先級(jí)為7),所以展開的表達(dá)式,運(yùn)算順序發(fā)生了改變,結(jié)果就跟我們的預(yù)期不一樣了。為了避免這種展開錯(cuò)誤,我們可以給宏的參數(shù)加一個(gè)小括號(hào)()來(lái)防止展開后,表達(dá)式的運(yùn)算順序發(fā)生變化。這樣的宏才能算一個(gè)合格的宏:

#define MAX(x,y) (x) > (y) ? (x) : (y)

中等

上面的宏,只能算合格,但還是存在漏洞。比如,我們使用下面的代碼測(cè)試:

#define MAX(x,y) (x) > (y) ? (x) : (y)
int main(void)
{
    printf("max=%d",3 + MAX(1,2));
    return 0;
}

在程序中,我們打印表達(dá)式 3 + MAX(1, 2) 的值,預(yù)期結(jié)果應(yīng)該是5,但實(shí)際運(yùn)行結(jié)果卻是1。我們展開后,發(fā)現(xiàn)同樣有問(wèn)題:

3 + (1) > (2) ? (1) : (2);

因?yàn)檫\(yùn)算符 + 的優(yōu)先級(jí)大于比較運(yùn)算符 >,所以這個(gè)表達(dá)式就變?yōu)?>2?1:2,最后結(jié)果為1也就見怪不怪了。此時(shí)我們應(yīng)該繼續(xù)修改這個(gè)宏:

#define MAX(x,y) ((x) > (y) ? (x) : (y))

使用小括號(hào)將宏定義包起來(lái),這樣就避免了當(dāng)一個(gè)表達(dá)式同時(shí)含有宏定義和其它高優(yōu)先級(jí)運(yùn)算符時(shí),破壞整個(gè)表達(dá)式的運(yùn)算順序。如果你能寫到這一步,說(shuō)明你比前面那個(gè)面試合格的同學(xué)強(qiáng),前面那個(gè)同學(xué)已經(jīng)回去等消息了,我們接著面試下一輪。

良好

上面的宏,雖然解決了運(yùn)算符優(yōu)先級(jí)帶來(lái)的問(wèn)題,但是仍存在一定的漏洞。比如,我們使用下面的測(cè)試程序來(lái)測(cè)試我們定義的宏:

#define MAX(x,y) ((x) > (y) ? (x) : (y))
int main(void)
{
    int i = 2;
    int j = 6;
    printf("max=%d",MAX(i++,j++));
    return 0;
}

在程序中,我們定義兩個(gè)變量 i 和 j,然后比較兩個(gè)變量的大小,并作自增運(yùn)算。實(shí)際運(yùn)行結(jié)果發(fā)現(xiàn) max = 7,而不是預(yù)期結(jié)果 max = 6。這是因?yàn)樽兞?i 和 j 在宏展開后,做了兩次自增運(yùn)算,導(dǎo)致打印出 i 的值為7。

遇到這種情況,那該怎么辦呢? 這時(shí)候,語(yǔ)句表達(dá)式就該上場(chǎng)了。我們可以使用語(yǔ)句表達(dá)式來(lái)定義這個(gè)宏,在語(yǔ)句表達(dá)式中定義兩個(gè)臨時(shí)變量,分別來(lái)暫儲(chǔ) i 和 j 的值,然后進(jìn)行比較,這樣就避免了兩次自增、自減問(wèn)題。

#define MAX(x,y)({     \
    int _x = x;        \
    int _y = y;        \
    _x > _y ? _x : _y; \
})
int main(void)
{
    int i = 2;
    int j = 6;
    printf("max=%d",MAX(i++,j++));
    return 0;
}

在語(yǔ)句表達(dá)式中,我們定義了2個(gè)局部變量 _x、_y 來(lái)存儲(chǔ)宏參數(shù) x 和 y 的值,然后使用 _x 和 _y 來(lái)比較大小,這樣就避免了 i 和 j 帶來(lái)的2次自增運(yùn)算問(wèn)題。

你能堅(jiān)持到了這一關(guān),并寫出這樣自帶 BGM 的宏,面試官心里可能已經(jīng)有了給你 Offer 的意愿了。但此時(shí)此刻,千萬(wàn)不要驕傲!為了徹底打消面試官的心理顧慮,我們需要對(duì)這個(gè)宏繼續(xù)優(yōu)化。

優(yōu)秀

在上面這個(gè)宏中,我們定義的兩個(gè)臨時(shí)變量數(shù)據(jù)類型是 int 型,只能比較兩個(gè)整型的數(shù)據(jù)。那對(duì)于其它類型的數(shù)據(jù),就需要重新再定義一個(gè)宏了,這樣太麻煩了!我們可以基于上面的宏繼續(xù)修改,讓它可以支持任意類型的數(shù)據(jù)比較大?。?/p>

#define MAX(type,x,y)({     \
    type _x = x;        \
    type _y = y;        \
    _x > _y ? _x : _y; \
})
int main(void)
{
    int i = 2;
    int j = 6;
    printf("max=%d\n",MAX(int,i++,j++));
    printf("max=%f\n",MAX(float,3.14,3.15));
    return 0;
}

在這個(gè)宏中,我們添加一個(gè)參數(shù):type,用來(lái)指定臨時(shí)變量 _x 和 _y 的類型。這樣,我們?cè)诒容^兩個(gè)數(shù)的大小時(shí),只要將2個(gè)數(shù)據(jù)的類型作為參數(shù)傳給宏,就可以比較任意類型的數(shù)據(jù)了。如果你能在面試中,寫出這樣的宏,面試官肯定會(huì)非常高興,他一般會(huì)跟你說(shuō):稍等,待會(huì) HR 會(huì)跟你談待遇問(wèn)題。

還能不能更牛逼

如果你想薪水拿得高一點(diǎn),待遇好一點(diǎn),此時(shí)不應(yīng)該驕傲,你應(yīng)該大手一揮:且慢,我還可以更牛逼!

上面的宏定義中,我們?cè)黾恿艘粋€(gè)type類型參數(shù),來(lái)兼容不同的數(shù)據(jù)類型,此時(shí)此刻,為了薪水,我們應(yīng)該把這個(gè)也省去。如何做到?使用typeof就可以了,typeof是GNU C新增的一個(gè)關(guān)鍵字,用來(lái)獲取數(shù)據(jù)類型,我們不用傳參進(jìn)去,讓typeof直接獲取!

#define max(x, y) ({    \
    typeof(x) _x = (x); \
    typeof(y) _y = (y); \
    (void) (&_x == &_y);\
    _x > _y ? _x : _y; })

在這個(gè)宏定義中,使用了 typeof 關(guān)鍵字用來(lái)獲取宏的兩個(gè)參數(shù)類型。干貨在(void) (&x == &y);這句話,簡(jiǎn)直是天才般的設(shè)計(jì)!一是用來(lái)給用戶提示一個(gè)警告,對(duì)于不同類型的指針比較,編譯器會(huì)給一個(gè)警告,提示兩種數(shù)據(jù)類型不同;二是,當(dāng)兩個(gè)值比較,比較的結(jié)果沒有用到,有些編譯器可能會(huì)給出一個(gè)warning,加個(gè)(void)后,就可以消除這個(gè)警告!

此刻,面試官看到你的這個(gè)宏,估計(jì)會(huì)倒吸一口氣:乖乖,果然是后生可畏,這家伙比我還牛逼!你等著,HR待會(huì)過(guò)來(lái)跟你談薪水!

恭喜你,拿到offer了!

3.4 語(yǔ)句表達(dá)式在 Linux 內(nèi)核中的使用

語(yǔ)句表達(dá)式,作為 GNU C 對(duì) C 標(biāo)準(zhǔn)的一個(gè)擴(kuò)展,在內(nèi)核中,尤其是在內(nèi)核的宏定義中,被大量的使用。使用語(yǔ)句表達(dá)式定義宏,不僅可以實(shí)現(xiàn)復(fù)雜的功能,還可以避免宏定義帶來(lái)的一些歧義和漏洞。比如在 Linux 內(nèi)核中,max_t 和 min_t 的宏定義,就使用了語(yǔ)句表達(dá)式:

#define min_t(type, x, y) ({            \
    type __min1 = (x);          \
    type __min2 = (y);          \
    __min1 < __min2 ? __min1 : __min2; })

#define max_t(type, x, y) ({            \
    type __max1 = (x);          \
    type __max2 = (y);          \
    __max1 > __max2 ? __max1 : __max2; })

除此之外,在 Linux 內(nèi)核、GNU 開源軟件中,你會(huì)發(fā)現(xiàn),還有大量的宏定義使用了語(yǔ)句表達(dá)式。通過(guò)本節(jié)教程的學(xué)習(xí),相信大家以后再碰到這種使用語(yǔ)句表達(dá)式定義的宏,肯定就知道是怎么回事了,心中有丘壑,再也不用慌。

本教程根據(jù) C語(yǔ)言嵌入式Linux高級(jí)編程視頻教程 第05期 改編,電子版書籍可加入QQ群:475504428 下載,更多嵌入式視頻教程,可關(guān)注:
微信公眾號(hào):宅學(xué)部落(armlinuxfun)
51CTO學(xué)院-王利濤老師:http://edu.51cto.com/sd/d344f

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI