2011年4月14日 星期四

[C]指標與除錯

最近工作上,遇到了C語言,這個東西是威力強大,
可是,若沒有現成的函式庫,只有標準的函式庫。
那真的很多東西都要先打造完畢,才能開始工作,
不然就是整天在字元裡面打轉,只能見樹不見林。
要解決問題,真是遙遙無期。

尤其是,C語言裡面,變數其實只是位址的代表,
更厲害的是指標的概念讓大家更搞不清楚狀況,
再加上函數的傳值呼叫,指標的指標,取址,
這幾個加起來的效應跟蝴蝶效應一樣。

其實萬法歸宗,在除錯的時候,尤其是處理指標的時候,
要記得變數只是位址,變數值的意思是隨人解釋。
其實 VC++ 的整合開發環境都會有每個變數的名字、位址及值可以查看。
一步一步來是可以體會的。
如果沒有這一類的整合開發環境,只好使用
printf(“%p”,變數)
來看變數的位置還有值。

例如有個需求是「動態字串陣列」,在主程式是這樣寫

int main(int argc, char ** argv){
char **buffer = NULL;
//開始加值
char * t1 = “t1”; //對,字串就是字元陣列的痛苦開始了,所以才要指標的指標。 //第一行
buffer = (char **)realloc(buffer, 1 * sizeof(char *)); //第二行
buffer[0] = t1; //第三行
//加第二個值
char * t2 = “t2”; //第四行
buffer = (char **)realloc(buffer, 2 * sizeof(char *)); //第五行
buffer[1] = t2; //第六行
}

這樣是可以的。可是每次都要copy paste這麼多,還要去改 index,忘了改到就完蛋了。
的確是需要一個函式來幫忙。就來開始改造。

void bufferadd(char ** buffer, char *str, int size)
{
  buffer = (char **)realloc(buffer, size * sizeof(char *)); //第一行
  *(buffer + (size - 1) = str; //第二行
}
int main(int argc, char ** argv){
char **buffer = NULL;
//開始加值
char * t1 = “t1”; //對,字串就是字元陣列的痛苦開始了,所以才要指標的指標。 //第一行
bufferadd(buffer,t1,1); //第二行
//加第二個值
char * t2 = “t2”;  //第四行
bufferadd(buffer,t1,1); //第五行
}

看起來簡單多了對吧,可是這是錯的。因為函式是傳值呼叫,所以問題很大。

新主程式的第二行,buffer的值這時候是 0 。
接下來,進到 bufferadd 之後,buffer 的值是11000d9d0, str 的值是1000015a8
函式第二行,把位址11000d9d0的值填入1000015a8。
第二次進來的時候,buffer 的值是11000d9f0,str 的值是1000015ac
函式第二行,把位址11000d9f0位移一格到11000d9f8的值填入1000015ac。
然後出函式在主程式看,所有的資料是空的。

為什麼?函式裡面自己做開心嗎?函式結束後,主程式的buffer的值還是0。
因為傳值呼叫,函式內無法對主程式的變數處理。

因此,把主程式的buffer的位址傳進去,再改一次。
bufferadd(&buffer,t1,1);
因為如此,bufferadd 的宣告也要變成 void bufferadd(char *** buffer, char *str, int size)
所以整個函數也跟著變

void bufferadd(char *** buffer, char *str, int size)
{
  *buffer = (char **)realloc(buffer, size * sizeof(char *)); //第一行
  *(*buffer + (size - 1)) = str; //第二行
}

新主程式也改成

int main(int argc, char ** argv){
char **buffer = NULL; //第一行
//開始加值
char * t1 = “t1”; //第二行
bufferadd(&buffer,t1,1); //第三行
//加第二個值
char * t2 = “t2”;  //第四行
bufferadd(buffer,t1,1); //第五行
}

依我實際執行的結果,在
主程式執行第一行之前 buffer 的位址是 ffffffffffff980,指到的值是0
所以在第三行執行的時候,要把 ffffffffffff980 傳進函式 bufferadd
在函式 bufferadd 內部檢查一下,
buffer 的位址不是ffffffffffff980,是ffffffffffff940,buffer的值是ffffffffffff980
但是 *buffer 是 0。
問題就在這裡。在函式裡的buffer是一個新的變數,有自己的位址,它的值是主程式的buffer的位址。(都是從這裡搞混的。)
在函式內部的第一行,就是要求幫 *buffer ,重配一組記憶體來用。
配到的是11000d9d0
在函式內部的第二行,把位址 11000d9d0  的值,設成字元指標的位址(1000015a8)。
第二次函式呼叫的時候,*buffer 重配記憶體,還是同一個位址11000d9d0。
(用realloc的話沒事不會移動。)
因要塞入第二組資料,所以把位址移動一格(位址+8)。所以是11000d9d8。
把位址 11000d9d8 的值,設字元指標的位址(1000015ac)

回到主程式,buffer 的位址ffffffffffff980,指到的值現在是11000d9d0。

也就是說在函式做完回到主程式之後,11000d9d0 開始的空間,
就有我們要的東西了。

執行結果整理成下表,有括號的是註解

變數名 位址
主程式的buffer ffffffffffff980 (一開始是0,由函式內*buffer=(char*)realloc(*buffer,1)指定為11000d9d0)
主程式的t1 1000015a8 “T1\0”
主程式的t2 1000015ac “T21234567890\0”
  11000d9d0 1000015a8
(在函式內*(*buffer)=str;指定)
  11000d9d8 1000015ac
(在函式內*(*buffer+1)=str;指定)
函式的buffer ffffffffffff940 ffffffffffff980
函式的str   1000015a8(第一次)
1000015ac(第二次)

 

好累…,記得以前就搞懂一次了,現在又再搞一次,希望不要再碰這個了。
不要怪我用這麼多指標…

沒有留言:

張貼留言