restrict 和 inline 在C語(yǔ)言中的使用,但是后者可能還能帶來(lái)些許理解上的便利,開(kāi)啟 -O3 優(yōu)化是一個(gè)很不錯(cuò)的選擇。inline 的作用還是在于和 static 一起使用,讓小函數(shù)盡可能的減小開(kāi)銷甚至消除函數(shù)開(kāi)銷。restrict 最重要的還是在于編譯器的優(yōu)化上。編譯器能夠?yàn)槲覀兊某绦蛱峁﹥?yōu)化,這是眾所周知的,但是編譯器是如何優(yōu)化的,知道的人少之又少,其中有一些優(yōu)化是建立在編譯器能夠理解你的代碼,或者說(shuō)編譯器要認(rèn)為你的代碼是可以被優(yōu)化的情況下,才會(huì)采取優(yōu)化措施:
什么是指針別名?
void tmp_plus(int * a, int * b)
{
for(int i = 0; i < b_len;++i)
*a += b[i];
}
這段代碼中,a, b 是兩個(gè)被傳入的指針,編譯器對(duì)他們毫無(wú)所知,也不知道a是否在b的范圍之內(nèi),故無(wú)法對(duì)其做出最大程度上的優(yōu)化,這會(huì)導(dǎo)致什么結(jié)果呢?也就是,每依次循環(huán)過(guò)后,*a的結(jié)果都會(huì)寫(xiě)回到主存當(dāng)中去,而不是在寄存器里迅速進(jìn)行下一次增加!
或者有的聰明的編譯器可以將其擴(kuò)展成if ... else的加長(zhǎng)版形式來(lái)避免寫(xiě)回操作。
但是如果我們?cè)黾恿?code>restrict
void tmp_plus(int * restrict a, int * restrict b) ...
這就是告訴編譯器,這兩個(gè)指針是完全不相干的,你可以放心的優(yōu)化,不會(huì)出錯(cuò)。
但是在這里有一些小的問(wèn)題,那就是C++并不支持這個(gè)關(guān)鍵字,這會(huì)導(dǎo)致什么后果?
Visual Studio下編程的時(shí)候會(huì)發(fā)現(xiàn)使用restrict關(guān)鍵字是會(huì)產(chǎn)生編譯錯(cuò)誤的,無(wú)論你使用 .c 還是 .cpp,難道說(shuō)不支持嗎?實(shí)際上不是,主流的編譯器都對(duì)這個(gè)關(guān)鍵字有自己的實(shí)現(xiàn)__restrict__restrict__volatile,當(dāng)時(shí)對(duì)其的解釋就是讓編譯器不對(duì)其進(jìn)行優(yōu)化的意思,這里再說(shuō)清楚一點(diǎn)
volatile int i = 0;i 的時(shí)候,是從它的內(nèi)存位置讀取,每次對(duì)它操作完畢后,將結(jié)果寫(xiě)回它的內(nèi)存位置,而不是將其優(yōu)化保存在寄存器內(nèi)。volatile的功能,內(nèi)存屏障是由操作系統(tǒng)提供的功能,目的是防止由于某些優(yōu)化,導(dǎo)致的指令重排的效果。在常見(jiàn)C中,數(shù)組是這樣的。
int arr_1[3];
int arr_2[] = {1, 2, 3}; /* 創(chuàng)建三個(gè)元素的數(shù)組 */
C99之后,可以使用一種叫做 復(fù)合文字(Compound Literal)的機(jī)制來(lái)做到更多的事情,最簡(jiǎn)單的就是創(chuàng)建匿名數(shù)組(看著有點(diǎn)像C++11引進(jìn)的 Lambda匿名函數(shù)):
int *ptoarr = (int[]){1, 2, 4}; /* 之后可以使用 ptoarr 操作 */
ptoarr[2] = 0;
printf("The Third number is : %d", ptoarr[2]);
輸出: $ The Third number is : 0
當(dāng)然,這種機(jī)制并不是只能如此使用,稍微高級(jí)一點(diǎn)的應(yīng)用是,可以傳遞數(shù)組了,無(wú)論是按參數(shù)傳遞還是返回值。
int *test_fun(int most[], int length){
for(int i = 0;i < length;++i)
most[i] = i;
return (int []){most[0], most[1], most[2], most[3]...};/* so on */
}
// main
test_fun((int []){6,6,6,6,6}, 5);
這也是自從更新了C99標(biāo)準(zhǔn)以后,可以講某個(gè)整體進(jìn)行返回的例子,也包括結(jié)構(gòu)體:
typedef struct compond{
int value;
int number;
int arrays[10];
}compond;
//假設(shè)有test_fun函數(shù)返回該結(jié)構(gòu)體
...
return (combond){
1, // 給value
2, // 給number
{most[0], most[1], most[2], most[3]...}}; //給arrats
當(dāng)然也可以構(gòu)造完成之后再返回實(shí)體,不過(guò)這么做不如上面寫(xiě)的效果好,原因前方已經(jīng)提過(guò)。
稍微修改一下結(jié)構(gòu)體,又是另一番情況:
typedef struct compond{
int value;
int number;
int arrays[]; /* 這里不再顯式聲明大小,也就無(wú)法構(gòu)造實(shí)體 */
}compond;
這個(gè)方式很像前方提到的 前橋和彌的 越界結(jié)構(gòu)體 的例子,只不過(guò)這個(gè)是一個(gè)在C標(biāo)準(zhǔn)允許的情況下,而前橋和彌則是利用一些C語(yǔ)言標(biāo)準(zhǔn)的漏洞達(dá)到目的。
在使用這種結(jié)構(gòu)體的時(shí)候,首先要為其動(dòng)態(tài)分配好空間,之后通過(guò)指針進(jìn)行操作,也增建了內(nèi)存泄漏的風(fēng)險(xiǎn),所以仁者見(jiàn)仁智者見(jiàn)智了:
compond* ptocom = malloc(sizeof(compond) + num_you_want * sizeof(int));
/* 這樣就成功分配了足夠的空間 */
ptocom->arrays[0] = some_number;
...
free(ptocom);
ptocom = NULL;
這其實(shí)并不是這種機(jī)制的目的,我覺(jué)得這種復(fù)合文字機(jī)制的最大用處還是在于消除艱澀難懂的函數(shù)調(diào)用
例如有一個(gè)函數(shù)的參數(shù)列表及其之長(zhǎng),我們就應(yīng)該考慮使用新機(jī)制結(jié)合結(jié)構(gòu)體,來(lái)對(duì)這個(gè)函數(shù)重新修飾一番:
int bad_function(double price, double count, int number,
int sales, Date sale_day, Date in_day,
String name, String ISBN, String market_name,
); /* 實(shí)現(xiàn)省略 */
這種函數(shù),在陌生的他人拿到之后,一定頭疼不已,可以對(duì)它進(jìn)行一些處理,來(lái)減輕使用時(shí)候的苦惱:
/* 首先使用宏進(jìn)行包裹 */
#define good_function(...) {\
/* 使用這個(gè)宏作為接口,可傳入不限個(gè)數(shù)的參數(shù) */
接下來(lái)定義一個(gè)結(jié)構(gòu)體,用于參數(shù)的接收。
/* 接收參數(shù)的結(jié)構(gòu)體 */
typedef struct param{
double price; /* 銷售價(jià)格 */
double count; /* 折扣 */
int number; /*總數(shù)量*/
int sales; /*銷售數(shù)量*/
Date sale_day; /* 銷售日期 */
Date in_day; /* 進(jìn)貨日期 */
String name; /* 貨物名稱 */
String ISBN; /* ISBN號(hào) */
String market_name; /* 銷售市場(chǎng) */
}param;
/* 并配上文檔說(shuō)明每個(gè)參數(shù)的作用 */
其次繼續(xù)完成宏
/* 此時(shí)將函數(shù)的聲明改為: */
int bad_function(param input);
/* 宏 */
#define good_function(...) {\
bad_function((param){__VA_ARGS__});\
}
這就完成了包裹
使用的時(shí)候:
good_function(.price = 199.9, .count = 0.9,
.number = 999, .sale = 20 /*and so on*/)
也可以在宏利使用默認(rèn)參數(shù),以此來(lái)減少一些不必要的工作量,達(dá)到像其他高級(jí)語(yǔ)言一樣的函數(shù)默認(rèn)參數(shù)的功能。當(dāng)然如果不添加默認(rèn)的值,則會(huì)按照標(biāo)準(zhǔn)將其值初始化為 0 或者 NULL.
#define good_function(...) {\
bad_function((param{.price = 100.0, .count = 1.0, __VA_ARGS__})); \
/* 假設(shè)想要設(shè)置默認(rèn)價(jià)格為100, 默認(rèn)折扣為 1.0 */\
}
較之C89(C90)的提取可變宏參數(shù)要來(lái)的更加靈活及"高效"。
至于 __VA_ARGS__ 宏的較為官方的用法,前人之述備矣,就不在這里記錄了。
只看名字就能明白這是C語(yǔ)言支持泛型的兆頭。
好像很有意思
不過(guò)某些地方依舊有些限制,比如對(duì)于選擇函數(shù)方面。
/* -std=c11 */
void print_int(int x) {printf("%d\n", x);}
void print_double(double x) {printf("%f\n", x);}
void print(){printf("Or else, Will get here\n");}
#define CHOOSE(x) _Generic((x),\
int : print_int,\
double : print_double,\
default : print)(x)
調(diào)用它
int main(void)
{
CHOOSE(11.0); /* 11.000000 */
CHOOSE(11.0f); /* Or else, Will get here */
return 0;
}
缺點(diǎn)就在于,: 后面無(wú)法真正的調(diào)用函數(shù),而是只能寫(xiě)上函數(shù)名或者函數(shù)指針, 當(dāng)然為了突破這一點(diǎn)可以使用宏嵌套來(lái)間接實(shí)現(xiàn)這一點(diǎn),但是歸根結(jié)底,無(wú)法在 : 后面調(diào)用函數(shù)。
#define CHOOSE(X) _Generic((x), \
int : prinf("It is Int")\
double : printf("It is double"))(x)
/* Compile Error! */
這樣做會(huì)導(dǎo)致編譯錯(cuò)誤,編譯器會(huì)告訴你 CHOOSE并不是一個(gè)函數(shù)或者函數(shù)指針,看起來(lái)錯(cuò)誤很無(wú)厘頭,實(shí)際上一想,你要是在 : 之后調(diào)用了函數(shù),那么左后一個(gè)括號(hào)該如何自處,唯一的辦法就是返回函數(shù)指針:
typedef void (*void_p_double)(double);
typedef void (*void_p_int)(int);
void print_detail_double(double tmp){
printf("The Double is %f\n", tmp);
}
void print_detail_int(int tmp){
printf("The Int is %d\n", tmp);
}
void_p_int print_int(){
printf("It is a Int! ");
return print_detail_int;
}
void_p_double print_double() {
printf("It is a Double! ");
return print_detail_double;
}
void print_default(){printf("Nothing Matching !\n");}
#define CHOOSE(x) _Generic((x),\
int : print_int(x),\
double : print_double(x),\
default : print_default)(x)
調(diào)用:
CHOOSE(11); /* It is a Int The Int is 11 */
CHOOSE(11.0); /* It is a Double The Double is 11.000000 */
CHOOSE(11.Of); /* Nothing Matching ! */
choose(11l); /* Nothing Matching ! */
對(duì)于宏而言,最新的編譯器支持,
#program once, 將這個(gè)放在頭文件中,就代表該頭文件只編譯一次,也就是說(shuō),可以替代原有的老式#ifdef的三段式保護(hù),具體編譯器支持請(qǐng)查詢各編譯器。
但是現(xiàn)在,在這個(gè)C99(C11)世紀(jì),早已經(jīng)打破這個(gè)局限,無(wú)論是從程序員編寫(xiě)的語(yǔ)法角度看,亦或者是從編譯器的優(yōu)化角度看,都不在需要特地的將一個(gè)實(shí)體表示為指針進(jìn)行返回。
combine* ret_struct(combine* other){
/* 這里的參數(shù)也是指針,因?yàn)楫?dāng)時(shí)并不允許直接給結(jié)構(gòu)體進(jìn)行賦值 */
int value = other->filed_value;
/* SomeThing to do */
combine* p_local_ret_com = malloc(sizeof(combine));
/* 一系列安全檢查 */
return p_local_ret_com;
這在當(dāng)下自然也是可以的,而且會(huì)有不錯(cuò)的性能,但是。但是這也是C語(yǔ)言最令人詬病的地方,你卻深深的踏了進(jìn)去。
盡量少用 malloc(calloc, realloc) 之類的內(nèi)存操作函數(shù),是現(xiàn)代C編程的一個(gè)指標(biāo),在這個(gè)函數(shù)中,我們沒(méi)有辦法保證分配出去的內(nèi)存能夠回收(因?yàn)榫瓦@個(gè)函數(shù)而言并沒(méi)有回收這個(gè)內(nèi)存),雖然現(xiàn)代計(jì)算機(jī)(非特殊機(jī)器)的內(nèi)存已經(jīng)不在乎那幾十個(gè)甚至幾百個(gè)中等結(jié)構(gòu)體的內(nèi)存泄漏,但是內(nèi)存泄露依然是C語(yǔ)言最嚴(yán)重的問(wèn)題,沒(méi)有之一。
我們?cè)撟龅木褪潜M量減少風(fēng)險(xiǎn)的發(fā)生率:
combine ret_struct(combine other){
/* C99之后,我們就開(kāi)始允許直接給結(jié)構(gòu)體賦值,
意味著可以直接返回結(jié)構(gòu)體了 */
combine loc_ret_com; /* 如果沒(méi)有復(fù)合的結(jié)構(gòu)體成員的話,各成員會(huì)自動(dòng)初始化為0,不必?fù)?dān)心初始化問(wèn)題 */·
/* Do SomeThing to 'loc_ret_com' with 'other' */
...
return loc_ret_com;
}
/* main */
int main(void)
{
combine preview = {...};
combine action = ret_struct(preview);
return 0;
}
這么做的目的自然是為了讓我們的風(fēng)險(xiǎn)降到最低,讓系統(tǒng)棧幫我們管理內(nèi)存,包括創(chuàng)建->使用->回收,這個(gè)過(guò)程(就像被其他語(yǔ)言所津津樂(lè)道的GC機(jī)制,實(shí)際上C語(yǔ)言程序員可以選擇自己實(shí)現(xiàn)一個(gè)垃圾回收機(jī)制,在本系列的最后面可能會(huì)做一個(gè)簡(jiǎn)易的回收機(jī)制供大家參考,但是首先讓我們看完風(fēng)景,再用一個(gè)實(shí)際程序串聯(lián)起來(lái)后,再去考慮GC)不需要你來(lái)操心。
但是這真的是最好的形式了嗎?
讓我們回想一下C語(yǔ)言在調(diào)用函數(shù)的時(shí)候發(fā)生的某些事情,因?yàn)樽铋_(kāi)始的我們是從 main 函數(shù)的調(diào)用開(kāi)始我們的程序.
ret_structmain的位置以及ret_struct的各種參數(shù)等等,其中有一個(gè)東西就是返回地址
ret_struct之后我們能夠順利的返回main調(diào)用它的位置繼續(xù)執(zhí)行= =static對(duì)象,即便是static或者全局變量情況也是一樣只是不符合這個(gè)假設(shè)結(jié)論罷了),所以在函數(shù)結(jié)束后,??臻g被回收,它就被默認(rèn)的銷毀了(可以參考前橋和彌的書(shū)里有這個(gè)的解釋,實(shí)際上值并沒(méi)有真正被銷毀了,但是不允許再用,否則視為非法),但是我們是怎么接收到函數(shù)的返回值的?復(fù)制副本->銷毀本地的原身->將這個(gè)副本的值賦給外部接收的變量(沒(méi)有則銷毀)->銷毀副本這有什么問(wèn)題,難道還有更好的方法?
那自然有啊
return對(duì)象的情況下,是可以省去副本的操作的也就是說(shuō):
/*改寫(xiě)上方代碼*/
combine ret_struct(combine other){
other->filed_value = ...;
/* SomeThing to other */
return (combine){ .filed_value = other->filed_value
...};
}
如果這么寫(xiě),編譯器就知道,喲!你是想要把這個(gè)對(duì)象放到外邊使用是吧,那我懂了,就直接找到外邊接收這個(gè)值得變量地址,不再創(chuàng)建副本(其實(shí)還是創(chuàng)建,只不過(guò)不再銷毀而已),而是在那個(gè)變量地址中寫(xiě)入這個(gè)對(duì)象。
C++同樣適用