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(第二次)

 

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

2011年4月5日 星期二

[windows]win7安裝用的usb開機碟

公司同事在網路上找了很久想要做一隻usb開機碟,開完機要讓win7可以安裝。
他把成功的做法分享給我:

第一步:格式化隨身碟
這個步驟將使用命令列 diskpart 命令,使用此命令有可能遺失重要資料,請小心使用

插入隨身碟。
開啟管理員身分的命令列 CMD  。
找到隨身碟的磁碟機代號,打入以下命令:
diskpart
list disk
磁碟列表將會展開,為了下一步驟,請記住隨身碟的代號,在此假設為 disk 1 。
用下面的命令格式化隨身碟,記住將下面命令的 disk 1 替換為上述找到的代號。
select disk 1
clean
create partition primary
select partition 1
active
format fs=NTFS
assign
exit
第二步:將隨身碟設為可開機 
接下來我們將使用 bootsect 工具,此命令內建在 Vista/Windows 7 的光碟,此命令會將隨身碟製作成可開機。:

將 Windows Vista / 7 DVD 放入光碟。
在步驟一開啟的命令列下,切換目錄到 DVD 的 bootsect 命令所在的路徑:
d: (光碟機磁碟代號)
cd d:\boot
使用 bootsect 設置隨身碟為可開機的 NTFS ,以準備給 Vista/7 映像使用。假設USB 隨身碟磁碟代號為 G:\ :
bootsect /nt60 g:
現在,可以關閉命令列視窗,現在都做好了。
第三步:將 Vista/7 安裝 DVD 拷貝到 USB 隨身碟
您可以使用檔案總館將 DVD 內所有檔案與資料夾拷貝到製作完成的隨身碟,這步驟完成之後,您將有份可開機可安裝的 Vista/7 USB 隨身碟了。

第四步:設定 BIOS 開機選項為 USB 開機
由於每家電腦公司主機板的 BIOS 設定都不同,相信有點基礎的話,應該都知道怎麼調整 USB 開機,有些主機板還會在開機時,提示您按下熱鍵顯示開機選單。