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

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

0x09-未曾領略的新風景

0x09-未曾領略的新風景

  • 前方曾提到兩個關鍵字 restrictinline 在C語言中的使用,但是后者可能還能帶來些許理解上的便利,開啟 -O3 優(yōu)化是一個很不錯的選擇。
  • inline 的作用還是在于和 static 一起使用,讓小函數(shù)盡可能的減小開銷甚至消除函數(shù)開銷。
  • restrict 最重要的還是在于編譯器的優(yōu)化上。編譯器能夠為我們的程序提供優(yōu)化,這是眾所周知的,但是編譯器是如何優(yōu)化的,知道的人少之又少,其中有一些優(yōu)化是建立在編譯器能夠理解你的代碼,或者說編譯器要認為你的代碼是可以被優(yōu)化的情況下,才會采取優(yōu)化措施:

    • 有一個很重要的地方,稱為指針別名,是阻礙編譯器優(yōu)化代碼的最重要的地方
    • 什么是指針別名?

          void tmp_plus(int * a, int * b)
          {
              for(int i = 0; i < b_len;++i)
                  *a += b[i];
          }

      這段代碼中,a, b 是兩個被傳入的指針,編譯器對他們毫無所知,也不知道a是否在b的范圍之內,故無法對其做出最大程度上的優(yōu)化,這會導致什么結果呢?也就是,每依次循環(huán)過后,*a的結果都會寫回到主存當中去,而不是在寄存器里迅速進行下一次增加!

      或者有的聰明的編譯器可以將其擴展成if ... else的加長版形式來避免寫回操作。

      但是如果我們增加了restrict

          void tmp_plus(int * restrict a, int * restrict b) ...

      這就是告訴編譯器,這兩個指針是完全不相干的,你可以放心的優(yōu)化,不會出錯。

  • 但是在這里有一些小的問題,那就是C++并不支持這個關鍵字,這會導致什么后果?

    • 你在Visual Studio下編程的時候會發(fā)現(xiàn)使用restrict關鍵字是會產生編譯錯誤的,無論你使用 .c 還是 .cpp,難道說不支持嗎?實際上不是,主流的編譯器都對這個關鍵字有自己的實現(xiàn)
    • Visual Studio(Visual C++) : __restrict
    • GCC, Clang : __restrict__
  • 剩下一個是前面也大概說過的 volatile,當時對其的解釋就是讓編譯器不對其進行優(yōu)化的意思,這里再說清楚一點
    • 假設 volatile int i = 0;
    • 首先它的現(xiàn)象本質就是,確保每次讀取 i 的時候,是從它的內存位置讀取,每次對它操作完畢后,將結果寫回它的內存位置,而不是將其優(yōu)化保存在寄存器內。
    • 這就讓一些編譯器的優(yōu)化無法進行,就像上方所說的。
    • 一般將其用在調試時期,防止編譯器的優(yōu)化對自己的代碼邏輯造成混淆
    • 但是,正如上面所說,這個關鍵字的作用是每次都進行存取,開銷自然就變大了,意味著無法使用緩存來對其進行加速,換句話來說就是,只要是關于它的操作,開銷都將變大。
    • 并且,其所能起到的作用大部分體現(xiàn)在 多線程編程中,而且也無法阻止指令重排之類的優(yōu)化。
      • 對此,有一個需要提及的內容是,可以適當?shù)氖褂?內存屏障 來替代這種volatile的功能,內存屏障是由操作系統(tǒng)提供的功能,目的是防止由于某些優(yōu)化,導致的指令重排的效果。
      • 某些編譯器也有提供類似的功能,例如 GCC就可以通過內嵌匯編代碼的方式實現(xiàn)這個效果
    • 以上的略微提及,詳細可以自行查閱資料。
再議數(shù)組
  • 在常見C中,數(shù)組是這樣的。

      int arr_1[3];
      int arr_2[] = {1, 2, 3}; /* 創(chuàng)建三個元素的數(shù)組 */
  • C99之后,可以使用一種叫做 復合文字(Compound Literal)的機制來做到更多的事情,最簡單的就是創(chuàng)建匿名數(shù)組(看著有點像C++11引進的 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

    當然,這種機制并不是只能如此使用,稍微高級一點的應用是,可以傳遞數(shù)組了,無論是按參數(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標準以后,可以講某個整體進行返回的例子,也包括結構體:

      typedef struct compond{
              int value;
              int number;
              int arrays[10];
      }compond;
      //假設有test_fun函數(shù)返回該結構體
      ...
      return (combond){
                      1, // 給value
                      2, // 給number
                      {most[0], most[1], most[2], most[3]...}}; //給arrats

    當然也可以構造完成之后再返回實體,不過這么做不如上面寫的效果好,原因前方已經提過。

    稍微修改一下結構體,又是另一番情況:

      typedef struct compond{
              int value;
              int number;
              int arrays[]; /* 這里不再顯式聲明大小,也就無法構造實體 */
      }compond;

    這個方式很像前方提到的 前橋和彌越界結構體 的例子,只不過這個是一個在C標準允許的情況下,而前橋和彌則是利用一些C語言標準的漏洞達到目的。

    在使用這種結構體的時候,首先要為其動態(tài)分配好空間,之后通過指針進行操作,也增建了內存泄漏的風險,所以仁者見仁智者見智了:

      compond* ptocom = malloc(sizeof(compond) + num_you_want * sizeof(int));
      /* 這樣就成功分配了足夠的空間 */
      ptocom->arrays[0] = some_number;
      ...
      free(ptocom);
      ptocom = NULL;

    這其實并不是這種機制的目的,我覺得這種復合文字機制的最大用處還是在于消除艱澀難懂的函數(shù)調用

    例如有一個函數(shù)的參數(shù)列表及其之長,我們就應該考慮使用新機制結合結構體,來對這個函數(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,
                      ); /* 實現(xiàn)省略 */

    這種函數(shù),在陌生的他人拿到之后,一定頭疼不已,可以對它進行一些處理,來減輕使用時候的苦惱:

      /* 首先使用宏進行包裹 */
      #define good_function(...) {\
      /* 使用這個宏作為接口,可傳入不限個數(shù)的參數(shù) */

    接下來定義一個結構體,用于參數(shù)的接收。

      /* 接收參數(shù)的結構體 */ 
      typedef struct param{
      double price;           /* 銷售價格 */
      double count;           /* 折扣 */
      int    number;          /*總數(shù)量*/
      int    sales;           /*銷售數(shù)量*/
      Date   sale_day;        /* 銷售日期 */
      Date   in_day;          /* 進貨日期 */
      String name;          /* 貨物名稱 */
      String ISBN;          /* ISBN號 */
      String market_name;   /* 銷售市場 */
      }param;
      /* 并配上文檔說明每個參數(shù)的作用 */

    其次繼續(xù)完成宏

      /* 此時將函數(shù)的聲明改為: */
      int bad_function(param input);
      /* 宏 */
      #define good_function(...) {\
          bad_function((param){__VA_ARGS__});\
      }

    這就完成了包裹

    使用的時候:

      good_function(.price = 199.9, .count = 0.9, 
                    .number = 999, .sale = 20 /*and so on*/)

    也可以在宏利使用默認參數(shù),以此來減少一些不必要的工作量,達到像其他高級語言一樣的函數(shù)默認參數(shù)的功能。當然如果不添加默認的值,則會按照標準將其值初始化為 0 或者 NULL.

      #define good_function(...) {\
          bad_function((param{.price = 100.0, .count = 1.0, __VA_ARGS__})); \
          /* 假設想要設置默認價格為100, 默認折扣為 1.0 */\
      }

    較之C89(C90)的提取可變宏參數(shù)要來的更加靈活及"高效"。

    至于 __VA_ARGS__ 宏的較為官方的用法,前人之述備矣,就不在這里記錄了。

C11之 _Generic

只看名字就能明白這是C語言支持泛型的兆頭。

好像很有意思

不過某些地方依舊有些限制,比如對于選擇函數(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)

調用它

int main(void)
{
    CHOOSE(11.0);  /* 11.000000 */
    CHOOSE(11.0f); /* Or else, Will get here */
    return 0;
}

缺點就在于,: 后面無法真正的調用函數(shù),而是只能寫上函數(shù)名或者函數(shù)指針, 當然為了突破這一點可以使用宏嵌套來間接實現(xiàn)這一點,但是歸根結底,無法在 : 后面調用函數(shù)。

#define CHOOSE(X) _Generic((x), \
                            int : prinf("It is Int")\
                            double : printf("It is double"))(x)
/* Compile Error! */

這樣做會導致編譯錯誤,編譯器會告訴你 CHOOSE并不是一個函數(shù)或者函數(shù)指針,看起來錯誤很無厘頭,實際上一想,你要是在 : 之后調用了函數(shù),那么左后一個括號該如何自處,唯一的辦法就是返回函數(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)

調用:

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 ! */

對于宏而言,最新的編譯器支持, #program once, 將這個放在頭文件中,就代表該頭文件只編譯一次,也就是說,可以替代原有的老式 #ifdef的三段式保護,具體編譯器支持請查詢各編譯器。

函數(shù)返回實體

  • 許多年前,在C編程的普遍常識是,返回指針,而不是一個實體。
  • 但是現(xiàn)在,在這個C99(C11)世紀,早已經打破這個局限,無論是從程序員編寫的語法角度看,亦或者是從編譯器的優(yōu)化角度看,都不在需要特地的將一個實體表示為指針進行返回。

      combine* ret_struct(combine* other){
          /* 這里的參數(shù)也是指針,因為當時并不允許直接給結構體進行賦值 */
          int value = other->filed_value;
          /* SomeThing to do */
          combine* p_local_ret_com = malloc(sizeof(combine));
          /* 一系列安全檢查 */
          return p_local_ret_com;

    這在當下自然也是可以的,而且會有不錯的性能,但是。但是這也是C語言最令人詬病的地方,你卻深深的踏了進去。

    盡量少用 malloc(calloc, realloc) 之類的內存操作函數(shù),是現(xiàn)代C編程的一個指標,在這個函數(shù)中,我們沒有辦法保證分配出去的內存能夠回收(因為就這個函數(shù)而言并沒有回收這個內存),雖然現(xiàn)代計算機(非特殊機器)的內存已經不在乎那幾十個甚至幾百個中等結構體的內存泄漏,但是內存泄露依然是C語言最嚴重的問題,沒有之一。

    我們該做的就是盡量減少風險的發(fā)生率:

      combine ret_struct(combine other){
          /* C99之后,我們就開始允許直接給結構體賦值,
                  意味著可以直接返回結構體了 */
          combine loc_ret_com; /* 如果沒有復合的結構體成員的話,各成員會自動初始化為0,不必擔心初始化問題 */·
          /* 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;
      }

    這么做的目的自然是為了讓我們的風險降到最低,讓系統(tǒng)棧幫我們管理內存,包括創(chuàng)建->使用->回收,這個過程(就像被其他語言所津津樂道的GC機制,實際上C語言程序員可以選擇自己實現(xiàn)一個垃圾回收機制,在本系列的最后面可能會做一個簡易的回收機制供大家參考,但是首先讓我們看完風景,再用一個實際程序串聯(lián)起來后,再去考慮GC)不需要你來操心。

    但是這真的是最好的形式了嗎?

    讓我們回想一下C語言調用函數(shù)的時候發(fā)生的某些事情,因為最開始的我們是從 main 函數(shù)的調用開始我們的程序.

    • 也就是說,系統(tǒng)在上位這個函數(shù)分配了空間
    • 緊接著我們調用了函數(shù) ret_struct
    • 調用之后,為了保存現(xiàn)有狀態(tài),棧里會被壓入許多信息,包括當下main的位置以及ret_struct的各種參數(shù)等等,其中有一個東西就是返回地址
      • 這個被壓入的元素保證了在執(zhí)行完ret_struct之后我們能夠順利的返回main調用它的位置繼續(xù)執(zhí)行
      • 這個和我們要講的有什么關系呢?
    • 沒關系我會亂說 = =
    • 一般來說,在函數(shù)返回一個值(把所有對象,值都稱為值)時,由于這個值是在函數(shù)中創(chuàng)建的(無論是傳入的參數(shù),還是在函數(shù)里創(chuàng)建的非static對象,即便是static或者全局變量情況也是一樣只是不符合這個假設結論罷了),所以在函數(shù)結束后,??臻g被回收,它就被默認的銷毀了(可以參考前橋和彌的書里有這個的解釋,實際上值并沒有真正被銷毀了,但是不允許再用,否則視為非法),但是我們是怎么接收到函數(shù)的返回值的?
    • 當然是因為程序幫你拷貝了一份這個值的副本的原因啊。
    • 而這個副本再使用過以后就會立即被銷毀,那么我們如果像上方那么返回一個結構體的話會發(fā)生什么應該就很清晰了:復制副本->銷毀本地的原身->將這個副本的值賦給外部接收的變量(沒有則銷毀)->銷毀副本
    • 這有什么問題,難道還有更好的方法?

      那自然有啊

    • 現(xiàn)代科技飛速發(fā)展,編譯器也不甘示弱,只要你外部有接收的地址,在(不開優(yōu)化的情況下,開了優(yōu)化也可能因為版本問題或者某些不可抗力而不優(yōu)化)直接return對象的情況下,是可以省去副本的操作的
    • 也就是說:

          /*改寫上方代碼*/
          combine ret_struct(combine other){
              other->filed_value = ...;
              /* SomeThing to other */
              return (combine){ .filed_value = other->filed_value
                                  ...};
          }
      如果這么寫,編譯器就知道,喲!你是想要把這個對象放到外邊使用是吧,那我懂了,就直接找到外邊接收這個值得變量地址,不再創(chuàng)建副本(其實還是創(chuàng)建,只不過不再銷毀而已),而是在那個變量地址中寫入這個對象。
    • 這就實現(xiàn)了讓系統(tǒng)幫你管理內存的目的,而不是擔心是否沒有釋放內存帶來的風險,而且還優(yōu)化了性能,何樂而不為。
    • 注:關于上方提到的 開了優(yōu)化也可能因為版本問題或者某些不可抗力而不優(yōu)化 這個說法是有道理的,因為大家的編譯器版本都不一樣,有的人用老版本那自然沒有這個優(yōu)化了,有的則是因為你編寫的程序邏輯上的構造導致編譯器無法為此處產生如此的優(yōu)化,這個請參考前方提到的書本深入理解計算機系統(tǒng)的優(yōu)化章節(jié)。讓然編譯原理要是能看自然更清楚嘍(ps:我還沒看)
    • 題外話:這個方法對于C++同樣適用