C/C++ 標準之 main 寫法有二種,一種是不接受任何參數列;另一種是接受二個參數列。程式碼大致如下
int main(int argc, char **argv) { return 0; } int main(void) { return 0; }
第二種於程式中較為常見,本文針對第一種 main 型態,說明主要有四點:
(1) int argc, char **argv 為何?
(2) 如何使用 argc, argv 測試程式?
(3) 在開發過程中,如何使用 VC 設定預設之 argv?
(4) 如何利用 VC 進行有效率之 argc, argv 測試?
0. 使用命令提示字元
在進入正題的時候,這裡說明如何使用命令提示字元 (應該說如何會用命令列)。先按下開始->執行-> cmd.exe 按下 Enter。
好了後按下 "dir.exe /?",你會看到會有一堆參數跑出來,雖平常都說 "dir 這個指令是用來查目錄、檔案",但實際上dir 它是一個小的應用程式,副檔名是 .exe,只是在命令提示字元裡面我可以給它「外部參數」,比如說我鍵入
dir.exe C:\
它便會幫我找出 C:\ 底下所有檔案、資料夾,我再鍵入
dir.exe C:\ /b
它也會幫我找出 C:\ 底下所有資料夾與檔案,只是這次是簡單模式的輸出。這裡就可以開始說一點點 argc, argv 了。
第一次我鍵入 dir.exe C:\ ,總共有二個單字,所以 argc = 2;第一個字是 "dir.exe",所以 argv[0] = "dir.exe",第二個字是 "C:\",所以 argv[1] = "C:\\"。
第二次我鍵入 dir.exe C:\ /b,一樣的道理,argc = 3,argv[0]="dir.exe",argv[1] = "C:\\",argv[2] = "/b"。
1. argc 與 argv
這裡就簡單的說那個 int main (int argc, char *argv[]) 裡面的的 int argc, char *argv[]。
假設,生成出來之執行檔為 Testing.exe,第一次輸入 Testing.exe C:\a.txt,這是2個參數,所以 argc = 2而 argv[0] = Testing.exe, argv[1] = "C:\\a.txt"。
第二次輸入 Testing.exe C:\1234567890.txt,這是2個參數,所以 argc = 2, argv[0] = Testing.exe, argv[1] = "C:\\1234567890.txt"
最後一次輸入 Testing.exe 1 2 3 4 5 6,共 7 個參數,argc = 7:argv[0] = Testing.exe, argv[1] = 1, argv[2] = 2, argv[3]=3, ... argv[6]=6。
注意事項有三點:
1、argc是argument count(參數個數)的縮寫,代表包括指令本身的參數個數。系統會自動計算所輸入的參數個數。
2、argv則是argument value的縮寫。代表參數值。也就是使用者在命令列中輸入的字串,每個字串以空白相隔 (但若用雙引號引住的路徑,即使有空白也只算一個字串參數)。同時,系統會自動將程式本身的名稱指定給argv[0],再將程式名稱後面所接續的參數依序指定給argv[1]、argv[2]….。
3. argv 事實上的確是以空白符號為分隔,但有個例外:若路徑名中間有空間,以雙引號引起來時則視為一組 argv。如: Testing.exe "C:\Program Files\Visual Studio 9.0" ,雖其中共有 4 個空白,但實際上 argv 只有二個,一個是 Testing.exe ;另一則為 "C:\Program Files\Visual Studio 9.0"。
2. 程式範例 - 顯示輸入之 argv
鑑於以上之說明,我們先寫個簡單之程式,這個程式將輸入之引數進行輸出,程式碼如下所示
#include <stdio.h> int main(int argc, char **argv) { int i=0; printf("argc = %d\n", argc); for(i=0; i!=argc; ++i) printf("argv[%d] = %s\n", i, argv[i]); return 0; }
3. 進行程式碼測試
好了之後,compiler 、execute,發現輸出竟然只有這個
argc = 1
argv[0] = d:\Testing.exe (視執行程式是位於哪裡)
為何?因直接用 compiler 去做 execute 動作,和在命令提示字元下只執行 D:\Testing.exe 沒二樣,並沒有再餵任何參數進去。於是測試方法大致如下
(0) 為方便說明,將 Testing.exe 放到 D:\ 底下
(1) 開始 -> 執行 -> 按下 cmd.exe (或按 WinKey + R,輸入 cmd.exe)
(2) 將工作目錄切到 D:\ 底下 ( 輸入 D:\ 後按 Enter )
(3) 輸入 Testing.exe a b c
以上便是測試步驟,執行結果如下所示
argc = 4
argv[0] = Testing.exe
argv[1] = a
argv[2] = b
argv[3] = c
由上敘述可知,任何程式的 argc 必大於等於1,而且 argv[0] 就是程式名字本身。
4. 使用 Visual Studio 設定預設之命令參數列
在第三大點所提到的方式,每次寫完後都還要跑到命令提示字元底下去測,這麼下來很麻煩。為提昇開發效率,於是會想一些辦法去提供開發速度,以下提出三個方法改善
(1) 於 main 一開始便把 argc, argv 覆蓋掉 <注意,這方法具危險性...>
於程式碼一開始就把 argc, argv 覆蓋過去,開發完要發佈時,再將覆蓋之程式碼註解掉,大致如下
#include <stdio.h> int main(int argc, char **argv) { int i=0; // recover argc and argv argc = 3; argv[0] = "a", argv[1]="b", argv[2]="c"; printf("argc = %d\n", argc); for(i=0; i!=argc; ++i) printf("argv[%d] = %s\n", i, argv[i]); return 0; }
argv 本身是一個指標的指標,指向其他的常數指標沒問題,危險的地方在於,由於我們不確定一開始程式傳進來的 argc 是多少、argv 有幾個,直接就假設存在 argv[1], argv[2] 做修改,這才是危險的地方。
(2) 寫批次檔進行測試
程式碼完全都不變動,是由批次檔進行處理。假設生成之執行檔位於 D:\Testing\Release\Testing.exe,則在任何地方建立起一副檔名為 bat 之檔案,內容如下
@echo off
D:\Testing\Release\Testing.exe a b c
D:\Testing\Release\Testing.exe 1 2 3
pause
上述之 bat 可有修改空間 (如依個人喜好,視 @echo off 要不要拿掉)。在 compiler 完後,直接快點該 bat 二下執行,便會生成一 console 視窗出現執行結果,結果如下所示
argc = 4
argv[0] = D:\Testing\Release\Testing.exe
argv[1] = a
argv[2] = b
argv[3] = c
argc = 4
argv[0] = D:\Testing\Release\Testing.exe
argv[1] = 1
argv[2] = 2
argv[3] = 3
請按任意鍵繼續...
優點:
容易新增/移除命令參數列腳本
缺點:
每次 compiler 後都要再去點出該 bat 去執行
(3) 使用 Visual Studio IDE 協助
吾人手邊版本為 2008,操作步驟如下
step1: 專案 -> Testing 屬性 (或直接按 Alt + F7)
step2:進入屬性頁後,左側選偵錯。
step3:右側於命令行輸入 testing edison x shih
step4:大功告成,此後直接執行便會視輸入參數為 step 3 所輸入,執行結果如下所示
優點:
1. 設定快速
2. 只需設定一次便不用再另開命令提示字元、也不用再點 .bat
缺點:
一次只能設一組測試參數 (若有方法可直接測試多個測試參數,請不吝告知)。
5. 死結問題
這是個有趣的邏輯問題。
標準函式庫裡面有個指令是 atexit (在 stdlib.h 內),使用這個函式時,會在程式退出後才執行指定之函式。給個例子看會更明確
#include <stdio.h> #include <stdlib.h> void test() {printf("test function\n");} int main() { printf("one\n"); atexit(test); printf("two\n"); return 0; }
輸出結果
one
two
test function
也基於這個特性,所以有部份人會想:那寫一個 bat,做多次測試。該 bat 檔就長這樣 (假設為 Testing.bat)
@echo off
D:\Testing\Release\Testing.exe a b c
D:\Testing\Release\Testing.exe 1 2 3
pause
接下來再用 atexit ,使用 system 方式呼叫 .bat ,這樣就可以達成多次測試的目標了!於是寫了下面這段碼
#include <stdio.h> #include <stdlib.h> void test() {system("Testing.bat");} int main(int argc, char **argv) { int i=0; printf("argc = %d\n", argc); for(i=0; i!=argc; ++i) printf("argv[%d] = %s\n", i, argv[i]); atexit(test); return 0; }
悲劇就此發生了.. 使用 Ctrl + C 也沒辦法完全跳出來,最後直接按下 Console Window 右上方的關閉列,暫時關掉了,但,再次 Compiler 該專案,卻怎麼都沒辦法寫入 Testing.exe ,Why ?
這個問題很容易想到,留給各位思考。
解決方案為:
開始 -> 執行 -> 輸入 cmd.exe (Enter) ->
命令提示字元裡輸入 taskkill /f /im testing.exe /im cmd.exe
若不這麼做的話,電腦的確還能使用,不過效能上會降許多,另解的方式應就為重開機處理。
6. correct and better
上述之描述,在 VC IDE 「命令引數」設定觀念上為誤。事實上,在屬性頁中,「組態屬性」-> 「偵錯」裡面,有二個參數是與命令行有關的,便為「命令」與「命令引數」。吾人上述乃簡化說明之過程,故沒提及到「命令」之設定。但事實上所有「命令引數」之第一個引數,根本就不用輸入執行檔名,因那在「命令」 (預設為 $(TargetPath) ) 便有,只要繼續輸入後面之引數即可。如
命令: ($TargetPath)
命令引數:a b c
這樣在 execute 展開便為
d:\Testing\Release\Testing.exe a b c
d:\Testing\Release\Testing.exe 即是在IDE 設定中的 ($TargetPath)
應用此特性,事實上可以做更有效率之設定,如下所示
命令:$(SolutionDir)\testing.bat
命令引數: $(TargetPath)
而在 testing.bat 可撰為
@echo off
%1 a b c
%1 1 2 3
pause
最後把 testing.bat 放在專案目錄底下便可進行大量之測試,同時每次只要再修改 .bat 內容便可,這段 IDE 設定與 bat 寫法吾人於此處便不解釋,避免過於離題。
7. 其餘不盡事項
(1) Dev-C / GCC 底下是否有類似 VC 功能,設定輸入命令引數?
(2) 若程式為「求得命令引數之所有總合」,該如何撰之? (提示:argv 為字串,記得用 atoi/atof 將字串轉數字)
(3) 請弄清楚 C:\Program Files\Visual Studio 9.0 與 "C:\Program Files\Visual Studio 9.0" 之影響。
(4) 關於批次檔之撰寫並不在本文之探討範圍內,所提供之批次檔已盡量簡潔,若對於批次檔之撰寫有所疑慮,請再自行進修。
(5) 試著在 vc 底下,發現更多類似 ($TargetPath) 、($SolutionDir) 之 compiler 巨集,使用上將更得心應手!
(6) 實際上 Visual Studio 10.0 與 Visual Studio 9.0 (恕筆者只用過6.0 / 9.0 / 10.0) 對於命令參數列之特性有所不同,最大差異便為 &pause 之解讀,讀者有興趣可試之。
- by EdisonX ( Edison . Shih )