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

鍍金池/ 教程/ C/ 0x05-C語言指針(Volume-2)
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-單線程備份(上)
總結(jié)
0x01-C語言序言
0x15-套接字編程-HTTP服務器(3)
0x14-套接字編程-HTTP服務器(2)
0x17-套接字編程-HTTP服務器(5)
第三部分
我的C語言
0x06-C語言預處理器
0x09-未曾領(lǐng)略的新風景
0x0A-C線程和Glib的視角
第二部分
0x10-網(wǎng)絡的世界
0x12-套接字編程-2
0x03-C代碼
0x0B-C語言錯誤處理

0x05-C語言指針(Volume-2)

0x05-C語言指針(Volume-2)

內(nèi)存的使用的那些事兒

你一直以為你操作的是真實物理內(nèi)存,實際上并不是,你操作的只是操作系統(tǒng)為你分配的資格虛擬地址,但這并不意味著我們可以無限使用內(nèi)存,那內(nèi)存賣那么貴干嘛,實際上存儲數(shù)據(jù)的還是物理內(nèi)存,只不過在操作系統(tǒng)這個中介的介入情況下,不同程序窗口(可以是相同程序)可以共享使用同一塊內(nèi)存區(qū)域,一旦某個傻大個程序的使用讓物理內(nèi)存不足了,我們就會把某些沒用到的數(shù)據(jù)寫到你的硬盤上去,之后再使用時,從硬盤讀回。這個特性會導致什么呢?假設(shè)你在Windows上使用了多窗口,打開了兩個相同的程序:

...
int  stay_here;
char tran_to_int[100];
printf("Address: %p\n", &stay_here);

fgets(tran_to_int, sizeof(tran_to_int), stdin);
sscanf(tran_to_int, "%d", &stay_here);

for(;;)
{
    printf("%d\n", stay_here);
    getchar();
    ++stay_here;
}
...

對此程序(引用前橋和彌的例子),每敲擊一次回車,值加1。當你同時打開兩個該程序時,你會發(fā)現(xiàn),兩個程序的stay_here都是在同一個地址,但對它進行分別操作時,產(chǎn)生的結(jié)果是獨立的!這在某一方面驗證了虛擬地址的合理性。虛擬地址的意義就在于,即使一個程序出現(xiàn)了錯誤,導致所在內(nèi)存完蛋了,也不會影響到其他進程。對于程序中部的兩個讀取語句,是一種理解C語言輸入流本質(zhì)的好例子,建議查詢用法,這里稍微解釋一下:

  • 通俗地說,fgets將輸入流中由調(diào)用起,stdin輸入的東西存入起始地址為tran_to_int的地方,并且最多讀取sizeof(tran_to_int)個,并在后方sscanf函數(shù)中將剛才讀入的數(shù)據(jù)按照%d的格式存入stay_here,這就是C語言一直在強調(diào)的流概念的意義所在,這兩個語句組合看起來也就是讀取一個數(shù)據(jù)這么簡單,但是我們要知道一個問題,一個關(guān)于scanf的問題

      scanf("%d", &stay_here);

    這個語句將會讀取鍵盤輸入,直到回車之前的所有數(shù)據(jù),什么意思?就是回車會留在輸入流中,被下一個輸入讀取或者丟棄。這就有可能會影響我們的程序,產(chǎn)生意料之外的結(jié)果。而使用上當兩句組合則不會。

函數(shù)與函數(shù)指針的那些事

事實上,函數(shù)名出現(xiàn)在賦值符號右邊就代表著函數(shù)的地址

int function(int argc){ /*...*/
}
...
int (*p_fun)(int) = function;
int (*p_fuc)(int) = &function;//和上一句意義一致

上述代碼即聲明并初始化了函數(shù)指針,p_fun的類型是指向一個返回值是int類型,參數(shù)是int類型的函數(shù)的指針

p_fun(11);
(*p_fun)(11);
function(11);

上述三個代碼的意義也相同,同樣我們也能使用函數(shù)指針數(shù)組這個概念

int (*p_func_arr[])(int) = {func1, func2,};

其中func1,func2都是返回值為int參數(shù)為int的函數(shù),接著我們能像數(shù)組索引一樣使用這個函數(shù)了。

Tips: 我們總是忽略函數(shù)聲明,這并不是什么好事。

  • 在C語言中,因為編譯器并不會對有沒有函數(shù)聲明過分深究,甚至還會放縱,當然這并不包含內(nèi)聯(lián)函數(shù)(inline),因為它本身就只在本文件可用。
  • 比如,當我們在某個地方調(diào)用了一個函數(shù),但是并沒有聲明它:

      CallWithoutDeclare(100); //參數(shù)100為 int 型

    那么,C編譯器就會推測,這個使用了int型參數(shù)的函數(shù),一定是有一個int型的參數(shù)列表,一旦函數(shù)定義中的參數(shù)列表與之不符合,將會導致參數(shù)信息傳遞錯誤(編譯器永遠堅信自己是對的!),我們知道C語言是強類型語言,一旦類型不正確,會導致許多意想不到的結(jié)果(往往是Bug)發(fā)生。

  • 對函數(shù)指針的調(diào)用同樣如此
C語言中malloc的那些事兒

我們常常見到這種寫法:

int* pointer = (int*)malloc(sizeof(int));

這有什么奇怪的嗎?看下面這個例子:

int* pointer_2 = malloc(sizeof(int));

哪個寫法是正確的?兩個都正確,這是為什么呢,這又要追求到遠古C語言時期,在那個時候, void* 這個類型還沒有出現(xiàn)的時候,malloc 返回的是 char* 的類型,于是那時的程序員在調(diào)用這個函數(shù)時總要加上強制類型轉(zhuǎn)換,才能正確使用這個函數(shù),但是在標準C出現(xiàn)之后,這個問題不再擁有,由于任何類型的指針都能與 void* 互相轉(zhuǎn)換,并且C標準中并不贊同在不必要的地方使用強制類型轉(zhuǎn)換,故而C語言中比較正統(tǒng)的寫法是第二種。

題外話: C++中的指針轉(zhuǎn)換需要使用強制類型轉(zhuǎn)換,而不能像第二種例子,但是C++中有一種更好的內(nèi)存分配方法,所以這個問題也不再是問題。

Tips:

  • C語言的三個函數(shù)malloc, calloc, realloc都是擁有很大風險的函數(shù),在使用的時候務必記得對他們的結(jié)果進行校驗,最好的辦法還是對他們進行再包裝,可以選擇宏包裝,也可以選擇函數(shù)包裝。
  • realloc函數(shù)是最為人詬病的一個函數(shù),因為它的職能過于寬廣,既能分配空間,也能釋放空間,雖然看起來是一個好函數(shù),但是有可能在不經(jīng)意間會幫我們做一些意料之外的事情,例如多次釋放空間。正確的做法就是,應該使用再包裝閹割它的功能,使他只能進行擴展或者縮小堆內(nèi)存塊大小。
指針與結(jié)構(gòu)體
typedef struct tag{
        int  value;
        long vari_store[1];
}vari_struct;

乍一看,似乎是一個很中規(guī)中矩的結(jié)構(gòu)體

...
vari_struct  vari_1;
vari_struct* vari_p_1 = &vari_1;
vari_struct* vari_p_2 = malloc(sizeof(vari_struct))(

似乎都是這么用的,但總有那么一些人想出了一些奇怪的用法

int          what_spa_want = 10;
vari_struct* vari_p_3 = malloc(sizeof(vari_struct) + sizeof(long)*what_spa_want);

這么做是什么意思呢?這叫做可變長結(jié)構(gòu)體,即便我們超出了結(jié)構(gòu)體范圍,只要在分配空間內(nèi),就不算越界。what_spa_want解釋為你需要多大的空間,即在一個結(jié)構(gòu)體大小之外還需要多少的空間,空間用來存儲long類型,由于分配的內(nèi)存是連續(xù)的,故可以直接使用數(shù)組vari_store直接索引。 而且由于C語言中,編譯器并不對數(shù)組做越界檢查,故對于一個有N個數(shù)的數(shù)組arr,表達式&arr[N]是被標準允許的行為,但是要記住arr[N]卻是非法的。 這種用法并非是娛樂,而是成為了標準(C99)的一部分,運用到了實際中

對于內(nèi)存的理解

在內(nèi)存分配的過程中,我們使用 malloc 進行分配,用 free 進行釋放,但這是我們理解中的分配與釋放嗎? 在調(diào)用 malloc 時,該函數(shù)或使用 brk() 或使用 mmap() 向操作系統(tǒng)申請一片內(nèi)存,在使用時分配給需要的地方,與之對應的是 free,與我們硬盤刪除東西一樣,實際上:

int* value = malloc(sizeof(int)*5);
...
free(value);
printf("%d\n", value[0]);

代碼中,為什么在 free 之后,我又繼續(xù)使用這個內(nèi)存呢?因為 free 只是將該內(nèi)存標記上釋放的標記,示意分配內(nèi)存的函數(shù),我可以使用,但并沒有破壞當前內(nèi)存中的內(nèi)容,直到有操作對它進行寫入。 這便引申出幾個問題:

  • Bug更加難以發(fā)現(xiàn),讓我們假設(shè),如果我們有兩個指針p1,p2指向同一個內(nèi)存,如果我們對其中某一個指針使用了 free(p1); 操作,卻忘記了還有另一個指針指向它,那這就會導致很嚴重的安全隱患,而且這個隱患十分難以發(fā)現(xiàn),原因在于這個Bug并不會在當時顯露出來,而是有可能在未來的某個時刻,不經(jīng)意的讓你的程序崩潰。
  • 有可能會讓某些問題更加簡化,例如釋放一個條條相連的鏈表域。

某些大哥提到說,free并不是什么都不做,而是將該段地址空間的前面一小部分置零 但是如果地址空間很長的話,依舊有誤用的風險,希望大家還是警惕

實際上之所以庫作者不讓free操作將地址空間清空,有一部分原因是為了性能考慮,因為置零操作是一個消耗性能的行為,具體可以自行嘗試,所謂雙刃劍就在于此。

總的來說,還是那句話C語言是一把雙刃劍。