在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ C/ 0x09-未曾領(lǐng)略的新風(fēng)景
0x0E-單線程備份(下)
0x11-套接字編程-1
0x05-C語(yǔ)言指針:(Volume-1)
0x13-套接字編程-HTTP服務(wù)器(1)
0x0C-開(kāi)始行動(dòng)
C 語(yǔ)言進(jìn)階
第一部分
0x05-C語(yǔ)言指針(Volume-2)
0x08-C語(yǔ)言效率(下)
0x07-C語(yǔ)言效率(上)
0x04 C代碼規(guī)范
0x0F-多線程備份
0x05-C語(yǔ)言變量
第四部分
0x16-套接字編程-HTTP服務(wù)器(4)
0x0D-單線程備份(上)
總結(jié)
0x01-C語(yǔ)言序言
0x15-套接字編程-HTTP服務(wù)器(3)
0x14-套接字編程-HTTP服務(wù)器(2)
0x17-套接字編程-HTTP服務(wù)器(5)
第三部分
我的C語(yǔ)言
0x06-C語(yǔ)言預(yù)處理器
0x09-未曾領(lǐng)略的新風(fēng)景
0x0A-C線程和Glib的視角
第二部分
0x10-網(wǎng)絡(luò)的世界
0x12-套接字編程-2
0x03-C代碼
0x0B-C語(yǔ)言錯(cuò)誤處理

0x09-未曾領(lǐng)略的新風(fēng)景

0x09-未曾領(lǐng)略的新風(fēng)景

  • 前方曾提到兩個(gè)關(guān)鍵字 restrictinline 在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)化措施:

    • 有一個(gè)很重要的地方,稱為指針別名,是阻礙編譯器優(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)
    • Visual Studio(Visual C++) : __restrict
    • GCC, Clang : __restrict__
  • 剩下一個(gè)是前面也大概說(shuō)過(guò)的 volatile,當(dāng)時(shí)對(duì)其的解釋就是讓編譯器不對(duì)其進(jìn)行優(yōu)化的意思,這里再說(shuō)清楚一點(diǎn)
    • 假設(shè) volatile int i = 0;
    • 首先它的現(xiàn)象本質(zhì)就是,確保每次讀取 i 的時(shí)候,是從它的內(nèi)存位置讀取,每次對(duì)它操作完畢后,將結(jié)果寫(xiě)回它的內(nèi)存位置,而不是將其優(yōu)化保存在寄存器內(nèi)。
    • 這就讓一些編譯器的優(yōu)化無(wú)法進(jìn)行,就像上方所說(shuō)的。
    • 一般將其用在調(diào)試時(shí)期,防止編譯器的優(yōu)化對(duì)自己的代碼邏輯造成混淆。
    • 但是,正如上面所說(shuō),這個(gè)關(guān)鍵字的作用是每次都進(jìn)行存取,開(kāi)銷自然就變大了,意味著無(wú)法使用緩存來(lái)對(duì)其進(jìn)行加速,換句話來(lái)說(shuō)就是,只要是關(guān)于它的操作,開(kāi)銷都將變大。
    • 并且,其所能起到的作用大部分體現(xiàn)在 多線程編程中,而且也無(wú)法阻止指令重排之類的優(yōu)化。
      • 對(duì)此,有一個(gè)需要提及的內(nèi)容是,可以適當(dāng)?shù)氖褂?內(nèi)存屏障 來(lái)替代這種volatile的功能,內(nèi)存屏障是由操作系統(tǒng)提供的功能,目的是防止由于某些優(yōu)化,導(dǎo)致的指令重排的效果。
      • 某些編譯器也有提供類似的功能,例如 GCC就可以通過(guò)內(nèi)嵌匯編代碼的方式實(shí)現(xiàn)這個(gè)效果
    • 以上的略微提及,詳細(xì)可以自行查閱資料。
再議數(shù)組
  • 在常見(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__ 宏的較為官方的用法,前人之述備矣,就不在這里記錄了。

C11之 _Generic

只看名字就能明白這是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)查詢各編譯器。

函數(shù)返回實(shí)體

  • 許多年前,在C編程的普遍常識(shí)是,返回指針,而不是一個(gè)實(shí)體。
  • 但是現(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)始我們的程序.

    • 也就是說(shuō),系統(tǒng)在上位這個(gè)函數(shù)分配了空間
    • 緊接著我們調(diào)用了函數(shù) ret_struct
    • 調(diào)用之后,為了保存現(xiàn)有狀態(tài),棧里會(huì)被壓入許多信息,包括當(dāng)下main的位置以及ret_struct的各種參數(shù)等等,其中有一個(gè)東西就是返回地址
      • 這個(gè)被壓入的元素保證了在執(zhí)行完ret_struct之后我們能夠順利的返回main調(diào)用它的位置繼續(xù)執(zhí)行
      • 這個(gè)和我們要講的有什么關(guān)系呢?
    • 沒(méi)關(guān)系我會(huì)亂說(shuō) = =
    • 一般來(lái)說(shuō),在函數(shù)返回一個(gè)值(把所有對(duì)象,值都稱為值)時(shí),由于這個(gè)值是在函數(shù)中創(chuàng)建的(無(wú)論是傳入的參數(shù),還是在函數(shù)里創(chuàng)建的非static對(duì)象,即便是static或者全局變量情況也是一樣只是不符合這個(gè)假設(shè)結(jié)論罷了),所以在函數(shù)結(jié)束后,??臻g被回收,它就被默認(rèn)的銷毀了(可以參考前橋和彌的書(shū)里有這個(gè)的解釋,實(shí)際上值并沒(méi)有真正被銷毀了,但是不允許再用,否則視為非法),但是我們是怎么接收到函數(shù)的返回值的?
    • 當(dāng)然是因?yàn)槌绦驇湍憧截惲艘环葸@個(gè)值的副本的原因啊。
    • 而這個(gè)副本再使用過(guò)以后就會(huì)立即被銷毀,那么我們?nèi)绻裆戏侥敲捶祷匾粋€(gè)結(jié)構(gòu)體的話會(huì)發(fā)生什么應(yīng)該就很清晰了:復(fù)制副本->銷毀本地的原身->將這個(gè)副本的值賦給外部接收的變量(沒(méi)有則銷毀)->銷毀副本
    • 這有什么問(wèn)題,難道還有更好的方法?

      那自然有啊

    • 現(xiàn)代科技飛速發(fā)展,編譯器也不甘示弱,只要你外部有接收的地址,在(不開(kāi)優(yōu)化的情況下,開(kāi)了優(yōu)化也可能因?yàn)榘姹締?wèn)題或者某些不可抗力而不優(yōu)化)直接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ì)象。
    • 這就實(shí)現(xiàn)了讓系統(tǒng)幫你管理內(nèi)存的目的,而不是擔(dān)心是否沒(méi)有釋放內(nèi)存帶來(lái)的風(fēng)險(xiǎn),而且還優(yōu)化了性能,何樂(lè)而不為。
    • 注:關(guān)于上方提到的 開(kāi)了優(yōu)化也可能因?yàn)榘姹締?wèn)題或者某些不可抗力而不優(yōu)化 這個(gè)說(shuō)法是有道理的,因?yàn)榇蠹业木幾g器版本都不一樣,有的人用老版本那自然沒(méi)有這個(gè)優(yōu)化了,有的則是因?yàn)槟憔帉?xiě)的程序邏輯上的構(gòu)造導(dǎo)致編譯器無(wú)法為此處產(chǎn)生如此的優(yōu)化,這個(gè)請(qǐng)參考前方提到的書(shū)本深入理解計(jì)算機(jī)系統(tǒng)的優(yōu)化章節(jié)。讓然編譯原理要是能看自然更清楚嘍(ps:我還沒(méi)看)
    • 題外話:這個(gè)方法對(duì)于C++同樣適用