TC 與其他編譯系統的比較

      TC 可以堪稱標準的 C 語言編譯器,近說:“教材上也是用這個啊”,“二級考試不也是規定用的TC?”;
遠說:“TC 可以運行在任何系統下,其程序有系統無關性”。TC 確實是 C 語言初學者的一個好的編譯器,然而對真正的開發者,TC 有很多的侷限性,近說:“TC 不是很好的編輯器,不能複製粘貼,不支持鼠標,等等”。細緻地分析,主要存在以下幾方面的問題:

一、頭文件的問題

好了,來看個經典的TC2下在老潭的書的《C語言程序設計(第二版)》,第4頁的一段代碼:

main( )
{
  printf("This is a C program./n");
}

      好了,當時的TC2的確允許使用printf和scanf可以不加頭文件,也的確允許main()這樣的聲明。允許是允許了,可是你寫成這樣對你有好處嗎?

     打個比方,Basic語言知道嗎?這種語言可以不聲明變量就使用。的確同樣也是允許了,可是給我的話,我會要求編譯器強制檢查變量是不是聲明瞭再使用。爲什麼?如果你需要用名字是nlen這個變量,當中有一個不小心寫成n1en,不強制檢查的話,編譯器不告訴你錯,但運行結果是錯誤的。但這種錯誤要是在很多行代碼裏你怎麼找?特別是'l'和'1'如此相似。

     編譯器雖然允許,但你別以爲那樣寫就是好事。說回剛剛的C代碼,TC2允許不加頭文件,於是不少初學的人習慣了從不寫頭文件,以爲C語言不需要頭文件似的。好了,等到看到那本書的138頁,那個字符輸入輸出,用了gets,就突然多了一個#include <stdio.h>,仔細一看,那一頁中間有100來字強調了要用庫函數,可是沒有講怎麼用,下面多了的那一行初學者也可能會覺得奇怪,要是不理解的人就跳過了,要是沒留意的更不當一回事了,誰讓之前書裏一直都沒有那句呢。然後,當有的初學者用TC3.0打代碼的時候,編譯提示說scanf沒有聲明(或者是用TC2的打了gets函數說gets沒有聲明),他們就鬱悶了,都是按照書的代碼啊,都一模一樣還會錯?於是跑來論壇問。有跑來問的還好,最怕的就是用TC2的,看了數組覺得難還跳了過去的,於是一直都不會寫頭文件,成了習慣。要是隨便換個環境,結果沒寫頭文件的,錯了,結果還說自己寫C代碼寫那麼多,這裏怎麼就錯了,然後就懷疑編譯器是不是壞的。然後就藉口說這個用不習慣要換TC2。結果就是惡性循環,跳不出TC2,學不到新領域的東西。論壇上這種代碼絕不少見,隨便翻一些提問帖子肯定找得到。其實早在TC3.0已經不再允許scanf和printf不加頭文件了,隨後的所有C編譯器也不再支持TC2的這個特性了。

二、main的聲明與返回值

    其實如果只是算潭書的第二版,這個無類型的main聲明沒有問題,只是直到C99標準
的出現,這個聲明才徹底廢除。其實也是同樣一個問題,允許你不寫,但這不是說你
這樣用就是好事。不過,這個的後果有點不一樣的就是,你忽略不寫,很可能以爲是
不需要返回的,或者是以爲返回一個無類型的(void),接着就出現了main函數裏不
寫return 0;的問題。那本書就是從頭到尾所有main函數一律沒有return 0; 。好了,
等到學了函數那章,書上說,沒有返回值的函數應該要用void來聲明,好了,
void main就這樣誕生了。不信就看看譚書的第三版,變成從頭到尾的void main()。
不過話說回頭,這樣的問題對於初學者學習基本語法和結構方面和寫寫小程序的都不會
出現任何影響。在編譯器上來說,這只是一個標準的問題,但對於你來說別小看了這麼
一點點的變化。因爲你習慣了main這種寫法的話,必然會把這個習慣帶到編寫其它函數裏
(這裏暫且不講main本身返回值的問題)。
舉個簡單例子,寫以下代碼:


#include <stdio.h>
factorial(int n)
{
    if(n==1)return 1;
    else if(n>1)return n * factorial(n-1);
}
int main(void)
{
    int n;
    while(scanf("%d", &n),n>=0)
    {
        printf("%d/n", factorial(n));
    }
    return 0;
}


    看得出來是計算階乘的(主要看那個子函數,main函數用回標準寫法),只不過少了int聲明和最後一個必然的return而已,

    看看輸入一個0進去會發生什麼事吧:
TC2: 1           (結果居然是碰對了)
VC6: -858993460  (不確定的隨機結果)
GCC: 0           (C99標準)
如果你用TC,那你還可能以爲這份代碼是正確的,
於是你就以爲遞歸算階乘就是這麼寫的。
於是還反過來問我:“喂,雨中飛燕,這個結果不就是正確的嗎,還錯什麼啊?”
於是我無語了。。。然後你也就留下了一個你可能以後都不打算去檢查的Bug。。。

    現在我們再來看一個:


#include <stdio.h>
long Factorial(int n)
{
    if(n==1)return 1;
    else if(n>1)return n * Factorial(n-1);
    return 1;
}
FactorialSum(int n)
{
    float f = 0;
    for(;n>0;--n)
    {
        f += 1.0/Factorial(n);
    }
    return f;
}
int main(void)
{
    int n;
    while(scanf("%d", &n),n>=0)
    {
        printf("%f/n", FactorialSum(n));
    }
    return 0;
}


    這個是計算1/1! + 1/2! + 1/3! +...+ 1/n!的代碼,Factorial的改好了,
然後加一個FactorialSum函數計算和。裏面的變量f就是用來累加(喜歡用float來
保存浮點也是書上的一大不良特色),1寫成1.0保證結果不是整數。似乎沒錯吧?
運行一下不就知道了嘛,輸入1,輸出0;輸入2,輸出0;輸入5,輸出0,
結果是輸出全部是0。原因就是你省略類型埋的禍根,只需要在那個函數前面補上
一個float那結果就正確了。


    這個危害有多嚴重嗎?在這裏不算嚴重,因爲在這裏代碼很短,相當容易看得出來。
要是換成大程序呢?上千代碼甚至上萬的代碼,要是最後都懶得寫一下return,
或者一個返回類型,那麼錯誤還怎麼找?你要是這個也省略那個也省略,
寫了N多行代碼的時候,一運行,這個結果也錯那個輸入也不行的時候,
你再看看你是不是被你自己的“習慣”給難住你自己吧。

    其實說實在的,WIN-TC自帶的一個tcsearch.exe文件,那個可以查函數用法和示例,
上面的示例代碼全部都是一樣的風格: int main(void)
非常規範標準的寫法,可是有多少人看了這個了呢?

三、函數聲明

經典的老代碼:
int max(x,y)
int x,y;
{
    return x>y ? x : y;
}
現在早已不是pascal時代了,這樣的函數聲明不但難讀,並且現在C99標準已經不再
支持這種寫法了。在老潭的書也僅僅用了半版不到的篇幅提到了一下,
其它地方並沒有使用這種聲明,這點做得還不錯。不過很奇怪的是論壇上還時不時
能看見這種聲明,這個到底是拜誰所賜呢?這我就不知道了,這裏也不展開來講了。

四、代碼風格

    這個問題就非常嚴重了。現在的情況是,從代碼風格,就可以知道你的大概水平了,
至少能知道你是不是菜鳥。那些代碼縮進弄得亂七八糟的,不用看詳細代碼都知道
水平肯定高不到哪裏去(當然不排除你可以故意弄亂)。因爲要是作爲一個新手,
要是寫的代碼亂,調試的時候或者自己看自己的代碼的時候,要是你自己都覺得亂,
當代碼有Bug,你要找出來的話,看你還頭痛不頭痛了。本來代碼就有問題,再加上
格式亂,要是你自己看着都不舒服,那你還怎麼去調試代碼呢?自己不會看着頭暈?
代碼少的時候你可能不覺得,等你寫了上百行代碼的那再嚐嚐這種滋味吧。

    更嚴重的問題是,DOS原版TC對縮進支持不好,
格式的控制相對其它的編輯器來說都要弱。
對於初學者,很容易弄出參差不齊的代碼,
對於學習方面來說這是一個很不利的因素。

    再者,大括號的位置及變量聲明位置的問題。看以下幾種風格:


1.
int main(void) //潭氏風格
{int n,s;
    while(scanf("%d", &n),n>=0)
    {
        s = factorial(n);
        printf("%d/n", s);
    }
    return 0;
}
2.
int main(void) //視頻教學風格?
{   int n,s;
    while(scanf("%d", &n),n>=0)
    {   s = factorial(n);
        printf("%d/n", s);
    }
    return 0;
}
3.
int main(void) //不知道這風格的來源
    {
    int n,s;
    while(scanf("%d", &n),n>=0)
        {
        s = factorial(n);
        printf("%d/n", s);
        }
    return 0;
    }
4.
int main(void){ //這種風格也有不少高手使用的
    int n,s;
    while(scanf("%d", &n),n>=0){
        s = factorial(n);
        printf("%d/n", s);
    }
    return 0;
}
5.
int main(void) //C Primer Plus 上的風格
{
    int n;
    while(scanf("%d", &n),n>=0)
    {
        int s = factorial(n);
        printf("%d/n", s);
    }
    return 0;
}

    我推薦的寫法是第5種。第三種寫法貌似較少見,雨中飛燕本人不好作出評論。
但對於1,2,4三種寫法,都是有原因的。原因也很簡單,減少佔用的行數。
特別是紙版書,節省這點行數累積起來可以節約不少紙張,降低書的成本。
對於powerpoint演示,如果不節約行數,代碼根本沒辦法顯示完。
但你要注意,它這樣寫可能不是爲了告訴你要這樣寫出這樣緊密的代碼。
但同樣的問題出現在TC上。DOS窗口標準大小80*25,高度只能顯示25行,
再加上TC菜單和最下面的輸出窗口,你能同時看到最多20行,要是按第5種風格
來寫代碼,會看得很辛苦(因爲TC2用不了鼠標,TC3能用不過也麻煩),
於是造成N多緊縮型代碼。如果你換成現在新的編輯軟件,根本用不着這樣。
Windows上的集成編輯軟件一頁下來就是三四十行,用鼠標滾輪滾一下就能上下拉,
一個代碼塊要是不想看還可以摺疊起來,這些特性都是TC所無法比擬的,
都比TC方便得多。你還有什麼理由抱着TC不放呢?還何必寫緊縮型代碼來爲難
自己的那雙眼睛呢?

五、TC圖形庫

    TC圖形庫這個可以說是TC一大特色,用TC2寫出來的代碼一般可以一眼看出,因爲TC2
編譯運行的時候,屏幕上原有的東西並不會清除,所以用TC的人一般會習慣在程序開頭
寫上clrscr()。當然,要是用Win-TC就不會發生這種情況。如果使用者本人知道這個
庫是TC專有(好比是VC的MFC)的話,這倒問題不大。問題是如果使用者不知道的話,
屏幕輸出用習慣了gotoxy,變得理所當然地認爲C就應該支持這類函數的話,
那就糟糕了。論壇上跑來問VC6可不可以輸出圖形,或者問有沒有gotoxy函數的人
不是一個兩個人的問題了。

六、越界檢查


    由於在DOS下,DOS系統根本不會去檢查程序的訪問越界問題,無論你要對內存的哪裏
進行讀寫,都是允許的(只要不把自己程序的代碼區改寫了就沒事)。但在Windows下,
尤其是WinXP,內存是分塊的,對只讀塊寫數據或者對不可讀寫塊進行讀數據都會引發
異常,如果程序不能夠處理這個異常,那麼這個程序就會被強制關閉。有了這個異常
機制,當然會使你更容易查出程序的錯誤。特別地,在VC6的Debug模式下,
堆裏未被初始化的內存被0xCD字節模式填充,堆裏釋放的內存被0xDD字節模式填充。
於是一但發生越界的時候,很容易通過程序運行結果得知當中有錯,爲調試帶來了方便。
然而TC則不然,不管你越界了多少,你都收不到任何的警告或者錯誤。
而且在多數情況下,TC下運行正常的越界代碼換VC6上就會結果出錯。
有初學者以爲這是VC6編譯器有問題,其實不然,而是TC給你掩蓋了這個錯誤。


#include <stdio.h>
int main(void)
{
    int n;
    int num[8];
    int sum[8] = {0};
    for(n=1;n<=8;++n)num[n] = 1;
    for(n=1;n<8;++n)sum[n] = num[n]+num[n+1];
    for(n=1;n<8;++n)
    printf("%d/n", sum[n]);
    getchar();
    return 0;
}

    這個程序的目的是給num數組全部給1,然後sum數組計算num相鄰兩個數的和。
有的初學者可能會說,這個程序沒問題,如果他以爲int num[8];的下標是從1至8的話。
然後,照樣運行一下看看。TC的結果是7個2,結果正常。
VC6的結果是,什麼都沒有顯示,然後你打開資源管理器查看一下進程,
你就會發現這個程序佔用CPU高達90%以上。你不要以爲這是編譯器發生了問題,
原因正是因爲這裏的越界導致了一個死循環!如果是GCC或更高版本的VC編譯器,
把for裏的8改成11也會發生死循環。但即使改成11,在TC上運行的結果也非常正常,
TC裏完全沒有任何錯誤的徵兆。這裏不分析這個死循環產生的原因,這裏只是想告訴你,
你在TC上運行結果無誤的程序不一定就邏輯上正確了(包括VC和GCC)。特別是TC幾乎
沒有任何的越界檢查,所以更要求程序員在TC上編寫代碼時候要十分謹慎和細心。
但現在問題是現在使用TC的大多是初學者,在沒有什麼經驗的情況下,很大可能
會寫出類似這樣的越界訪問代碼。這種代碼也許在TC上沒有問題,可是隻要換一個
編譯環境,問題就馬上暴露出來了。不信就看看下面的經典TC錯誤代碼吧:


#include <stdio.h>
int main(void)
{
    char *pstr;
    gets(pstr);
    puts(pstr);
    getchar();
    return 0;
}
(別告訴我說你不知道這代碼錯在哪裏,你不知道錯哪裏的話那你平時一定是使用TC的)

七、結束語

    現在再來回答文章一開頭的內容:“爲什麼說TC不適合真正的開發者”?
主要原因其實不是過時不過時的問題,主要是不適合現在真正學習C語言的學生們使用。
很容易因爲TC過於寬鬆的一些語法,或者一些與C99標準不一樣的語法,
讓初學者養成不良習慣或者產生特定環境下的依賴性。這些都是對學習上不利的因素。
所以我一般給別人推薦DevC++或者VC2005就是這個原因,藉助編譯器的強制能力,
迫使你使用較爲規範的方式去寫代碼。新加坡的環境爲什麼好?不是因爲公民素質高,
是因爲有法律規定,隨便扔垃圾或者破壞環境的都會有嚴重的法律後果。

    歸根究底,不讓你用TC,就是不希望你有不良習慣,更何況有很多比TC優秀的編譯器呢?
何必因爲教材或者考試是TC,你就一定用TC呢?

    知識是屬於自己的,用來真正充實你自己的
考試是給別人看的,用來得到一時之虛榮的

發佈了26 篇原創文章 · 獲贊 4 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章