電子產(chǎn)業(yè)一站式賦能平臺

PCB聯(lián)盟網(wǎng)

搜索
查看: 104|回復(fù): 0
收起左側(cè)

兩種 C 語言之間的差異

[復(fù)制鏈接]

475

主題

475

帖子

4237

積分

四級會員

Rank: 4

積分
4237
跳轉(zhuǎn)到指定樓層
樓主
發(fā)表于 2024-11-19 09:02:00 | 只看該作者 |只看大圖 回帖獎勵 |倒序瀏覽 |閱讀模式
點擊上方“C語言與CPP編程”,選擇“關(guān)注/置頂/星標(biāo)公眾號
干貨福利,第一時間送達!
最近有小伙伴說沒有收到當(dāng)天的文章推送,這是因為微信更改了推送機制,導(dǎo)致沒有星標(biāo)公眾號的小伙伴刷不到當(dāng)天推送的文章,無法接收到一些比較實用的知識和資訊。所以建議大家加個星標(biāo)??,以后就能第一時間收到推送了。


對于軟件工程師來說,尤其是嵌入式開發(fā),C 語言可以說是最最最主要的編程語言,然而,Linux GNU C 和 ANSI C 這兩者之間,卻存在著一定的差異,一旦使用不當(dāng),很容易造成語法錯誤。
Linux 上可用的 C 編譯器是 GNU C 編譯器,它建立在自由軟件基金會的編程許可證的基礎(chǔ)上,因此可以自由發(fā)布。
GNU C對標(biāo)準(zhǔn)C進行一系列擴展,以增強標(biāo)準(zhǔn)C的功能。
1.零長度和變量長度數(shù)組GNU C允許使用零長度數(shù)組,在定義變長對象的頭結(jié)構(gòu)時,這個特性非常有用。例如:
struct var_data {
    int len;
    char data[0];
};
char data[0]僅僅意味著程序中通過var_data結(jié)構(gòu)體實例的data[index]成員可以訪問len之后的第index個地址,它并 沒有為data[]數(shù)組分配內(nèi)存,因此sizeof(struct var_data)=sizeof(int)。
假設(shè)struct var_data的數(shù)據(jù)域就保存在struct var_data緊接著的內(nèi)存區(qū)域中,則通過如下代碼可以遍歷這些數(shù)據(jù):
struct var_data s;
...
for (i = 0; i printf('%02x', s.data);
GNU C中也可以使用1個變量定義數(shù)組,例如如下代碼中定義的“double x[n]”:
int main (int argc, char *argv[])
{
    int i, n = argc;
    double x[n];
    for (i = 0; i return 0;
}
2.case范圍GNU C支持case x…y這樣的語法,區(qū)間[x,y]中的數(shù)都會滿足這個case的條件,請看下面的代碼:
switch (ch) {
case '0'... '9': c -= '0';
    break;
case 'a'... 'f': c -= 'a' - 10;
    break;
case 'A'... 'F': c -= 'A' - 10;
    break;
}
代碼中的case'0'...'9'等價于標(biāo)準(zhǔn)C中的:
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
3.語句表達式GNU C把包含在括號中的復(fù)合語句看成是一個表達式,稱為語句表達式,它可以出現(xiàn)在任何允許表達式的地 方。我們可以在語句表達式中使用原本只能在復(fù)合語句中使用的循環(huán)、局部變量等,例如:
#define min_t(type,x,y) \
( { type _ _x =(x);type _ _y = (y); _ _xint ia, ib, mini;
float fa, fb, minf;
mini = min_t(int, ia, ib);
minf = min_t(float, fa, fb);
因為重新定義了__xx和__y這兩個局部變量,所以用上述方式定義的宏將不會有副作用。在標(biāo)準(zhǔn)C中,對應(yīng)的如 下宏則會產(chǎn)生副作用:
#define min(x,y) ((x)
代碼min(++ia,++ib)會展開為((++ia)
4.typeof關(guān)鍵字typeof(x)語句可以獲得x的類型,因此,可以借助typeof重新定義min這個宏:
#define min(x,y) ({ \
const typeof(x) _x = (x); \
const typeof(y) _y = (y); \
(void) (&_x == &_y); \
_x 我們不需要像min_t(type,x,y)那個宏那樣把type傳入,因為通過typeof(x)、typeof(y)可以獲得type。代 碼行(void)(&_x==&_y)的作用是檢查_x和_y的類型是否一致。
5.可變參數(shù)宏標(biāo)準(zhǔn)C就支持可變參數(shù)函數(shù),意味著函數(shù)的參數(shù)是不固定的,例如printf()函數(shù)的原型為:
int printf( const char *format [, argument]... );
而在GNU C中,宏也可以接受可變數(shù)目的參數(shù),例如:
#define pr_debug(fmt,arg...) \
printk(fmt,##arg)
這里arg表示其余的參數(shù),可以有零個或多個參數(shù),這些參數(shù)以及參數(shù)之間的逗號構(gòu)成arg的值,在宏擴展時替換 arg,如下列代碼:
pr_debug('%s:%d',filename,line)
會被擴展為:
printk('%s:%d', filename, line)
使用“##”是為了處理arg不代表任何參數(shù)的情況,這時候,前面的逗號就變得多余了。使用“##”之后,GNU C預(yù) 處理器會丟棄前面的逗號,這樣,下列代碼:
pr_debug('success!
')
會被正確地擴展為:
printk('success!
')
而不是:
printk('success!
',)
6.標(biāo)號元素標(biāo)準(zhǔn)C要求數(shù)組或結(jié)構(gòu)體的初始化值必須以固定的順序出現(xiàn),在GNU C中,通過指定索引或結(jié)構(gòu)體成員名,允許 初始化值以任意順序出現(xiàn)。
指定數(shù)組索引的方法是在初始化值前添加“[INDEX]=”,當(dāng)然也可以用“[FIRST...LAST]=”的形式指定一個范圍。例如,下面的代碼定義了一個數(shù)組,并把其中的所有元素賦值為0:
unsigned char data[MAX] = { [0 ... MAX-1] = 0 };
下面的代碼借助結(jié)構(gòu)體成員名初始化結(jié)構(gòu)體:
struct file_operations ext2_file_operations = {
    llseek: generic_file_llseek,
    read: generic_file_read,
    write: generic_file_write,
    ioctl: ext2_ioctl,
    mmap: generic_file_mmap,
    open: generic_file_open,
    release: ext2_release_file,
    fsync: ext2_sync_file,
};
但是,Linux 2.6推薦類似的代碼應(yīng)該盡量采用標(biāo)準(zhǔn)C的方式:
struct file_operations ext2_file_operations = {
    .llseek     = generic_file_llseek,
    .read       = generic_file_read,
    .write      = generic_file_write,
    .aio_read   = generic_file_aio_read,
    .aio_write  = generic_file_aio_write,
    .ioct       = ext2_ioctl,
    .mmap       = generic_file_mmap,
    .open       = generic_file_open,
    .release    = ext2_release_file,
    .fsync      = ext2_sync_file,
    .readv      = generic_file_readv,
    .writev     = generic_file_writev,
    .sendfile   = generic_file_sendfile,
};
7.當(dāng)前函數(shù)名GNU C預(yù)定義了兩個標(biāo)識符保存當(dāng)前函數(shù)的名字,__FUNCTION__保存函數(shù)在源碼中的名字,__PRETTY_FUNCTION__保存帶語言特色的名字。在C函數(shù)中,這兩個名字是相同的。
void example()
{
    printf('This is function:%s', __FUNCTION__);
}
代碼中的__FUNCTION__意味著字符串“example”。C99已經(jīng)支持__func__宏,因此建議在Linux編程中不再使用__FUNCTION__,而轉(zhuǎn)而使用__func__:
void example(void)
{
    printf('This is function:%s', __func__);
}
8.特殊屬性聲明GNU C允許聲明函數(shù)、變量和類型的特殊屬性,以便手動優(yōu)化代碼和定制代碼檢查的方法。要指定一個聲明的 屬性,只需要在聲明后添加__attribute__((ATTRIBUTE))。其中ATTRIBUTE為屬性說明,如果存在多個屬 性,則以逗號分隔。GNU C支持noreturn、format、section、aligned、packed等十多個屬性。
noreturn屬性作用于函數(shù),表示該函數(shù)從不返回。這會讓編譯器優(yōu)化代碼,并消除不必要的警告信息。例如:
# define ATTRIB_NORET __attribute__((noreturn)) ....
asmlinkage NORET_TYPE void do_exit(long error_code) ATTRIB_NORET;
format屬性也用于函數(shù),表示該函數(shù)使用printf、scanf或strftime風(fēng)格的參數(shù),指定format屬性可以讓編譯器根據(jù)格 式串檢查參數(shù)類型。例如:
asmlinkage int printk(const char * fmt, ...) __attribute__ ((format (printf, 1, 2)));
上述代碼中的第1個參數(shù)是格式串,從第2個參數(shù)開始都會根據(jù)printf()函數(shù)的格式串規(guī)則檢查參數(shù)。
unused屬性作用于函數(shù)和變量,表示該函數(shù)或變量可能不會用到,這個屬性可以避免編譯器產(chǎn)生警告信息。
aligned屬性用于變量、結(jié)構(gòu)體或聯(lián)合體,指定變量、結(jié)構(gòu)體或聯(lián)合體的對齊方式,以字節(jié)為單位,例如:
struct example_struct {
    char a;
    int b;
    long c;
} __attribute__((aligned(4)));
表示該結(jié)構(gòu)類型的變量以4字節(jié)對齊。
packed屬性作用于變量和類型,用于變量或結(jié)構(gòu)體成員時表示使用最小可能的對齊,用于枚舉、結(jié)構(gòu)體或聯(lián)合體類型時表示該類型使用最小的內(nèi)存。例如:
struct example_struct {
    char a;
    int b;
    long c __attribute__((packed));
};
編譯器對結(jié)構(gòu)體成員及變量對齊的目的是為了更快地訪問結(jié)構(gòu)體成員及變量占據(jù)的內(nèi)存。例如,對 于一個32位的整型變量,若以4字節(jié)方式存放(即低兩位地址為00),則CPU在一個總線周期內(nèi)就可以讀取32 位;否則,CPU需要兩個總線周期才能讀取32位。
9.內(nèi)建函數(shù)GNU C提供了大量內(nèi)建函數(shù),其中大部分是標(biāo)準(zhǔn)C庫函數(shù)的GNU C編譯器內(nèi)建版本,例如memcpy()等,它們與對應(yīng)的標(biāo)準(zhǔn)C庫函數(shù)功能相同。
不屬于庫函數(shù)的其他內(nèi)建函數(shù)的命名通常以__builtin開始,如下所示。
內(nèi)建函數(shù)__builtin_return_address(LEVEL)返回當(dāng)前函數(shù)或其調(diào)用者的返回地址,參數(shù)LEVEL指定調(diào)用棧的級數(shù),如0表示當(dāng)前函數(shù)的返回地址,1表示當(dāng)前函數(shù)的調(diào)用者的返回地址。
內(nèi)建函數(shù)__builtin_constant_p(EXP)用于判斷一個值是否為編譯時常數(shù),如果參數(shù)EXP的值是常數(shù),函數(shù)返回1,否則返回0。例如,下面的代碼可檢測第1個參數(shù)是否為編譯時常數(shù)以確定采用參數(shù)版本還是非參數(shù)版本:
#define test_bit(nr,addr) \
(__builtin_constant_p(nr) \
constant_test_bit((nr),(addr)) : \
variable_test_bit((nr),(addr)))
內(nèi)建函數(shù)__builtin_expect(EXP,C)用于為編譯器提供分支預(yù)測信息,其返回值是整數(shù)表達式EXP的值,C的 值必須是編譯時常數(shù)。
Linux內(nèi)核編程時常用的likely()和unlikely()底層調(diào)用的likely_notrace()、unlikely_notrace()就是基于 __builtin_expect(EXP,C)實現(xiàn)的。
#define likely_notrace(x) __builtin_expect(!!(x), 1)
#define unlikely_notrace(x) __builtin_expect(!!(x), 0)
若代碼中出現(xiàn)分支,則即可能中斷流水線,我們可以通過likely()和unlikely()暗示分支容易成立還是不容易 成立,例如:
if (likely(!IN_DEV_ROUTE_LOCALNET(in_dev)))
    if (ipv4_is_loopback(saddr))
    goto e_inval;
在使用gcc編譯C程序的時候,如果使用“-ansi–pedantic”編譯選項,則會告訴編譯器不使用GNU擴展語法。例如對 于如下C程序test.c:
struct var_data {
    int len;
    char data[0];
};
struct var_data a;
直接編譯可以通過:
gcc -c test.c
如果使用“-ansi–pedantic”編譯選項,編譯會報警:
gcc -ansi -pedantic -c test.c
test.c:3: warning: ISO C forbids zero-size array 'data'——EOF——你好,我是飛宇。日常分享C/C++、計算機學(xué)習(xí)經(jīng)驗、工作體會,歡迎點擊此處查看我以前的學(xué)習(xí)筆記&經(jīng)驗&分享的資源。
我組建了一些社群一起交流,群里有大牛也有小白,如果你有意可以一起進群交流。

歡迎你添加我的微信,我拉你進技術(shù)交流群。此外,我也會經(jīng)常在微信上分享一些計算機學(xué)習(xí)經(jīng)驗以及工作體驗,還有一些內(nèi)推機會。


加個微信,打開另一扇窗
經(jīng)常遇到有讀者后臺私信想要一些編程學(xué)習(xí)資源,這里分享 1T 的編程電子書、C/C++開發(fā)手冊、Github上182K+的架構(gòu)路線圖、LeetCode算法刷題筆記等精品學(xué)習(xí)資料,點擊下方公眾號會回復(fù)"編程"即可免費領(lǐng)取~
感謝你的分享,點贊,在看三  

回復(fù)

使用道具 舉報

發(fā)表回復(fù)

您需要登錄后才可以回帖 登錄 | 立即注冊

本版積分規(guī)則


聯(lián)系客服 關(guān)注微信 下載APP 返回頂部 返回列表