初識 C 語言


歡迎來到 C 語言的世界。C 是一門功能強大的專業化編程語言,深受業余編程愛好者和專業程序員的喜愛。本文為讀者學習這一強大而流行的語言打好基礎,並介紹幾種開發 C 程序最可能使用的環境。

我們先來了解 C 語言的起源和一些特性,包括它的優缺點。然后,介紹編程的起源並探討一些編程的基本原則。最后,討論如何在一些常見系統中運行 C 程序。

一、C 語言的起源

1972 年,貝爾實驗室的丹尼斯·里奇(Dennis Ritch)和肯·湯普遜(Ken Thompson)在開發 UNIX 操作系統時設計了 C 語言。然而,C 語言不完全是里奇突發奇想而來,他是在 B 語言(湯普遜發明)的基礎上進行設計。至於 B 語言的起源,那是另一個故事。C 語言設計的初衷是將其作為程序員使用的一種編程工具,因此,其主要目標是成為有用的語言。

雖然絕大多數語言都以實用為目標,但是通常也會考慮其他方面。例如,Pascal 的主要目標是為更好地學習編程原理提供扎實的基礎;而 BASIC 的主要目標是開發出類似英文的語言,讓不熟悉計算機的學生輕松學習編程。這些目標固然很重要,但是隨着計算機的迅猛發展,它們已經不是主流語言。然而,最初為程序員設計開發的C語言,現在已成為首選的編程語言之一。

二、選擇 C 語言的理由

在過去 40 多年里,C 語言已成為最重要、最流行的編程語言之一。它的成長歸功於使用過的人都對它很滿意。過去 20 多年里,雖然許多人都從 C 語言轉而使用其他編程語言(如,C++、Objective C、Java 等),但是 C 語言仍憑借自身實力在眾多語言中脫穎而出。在學習 C 語言的過程中,會發現它的許多優點(見圖 1)。下面,我們來看看其中較為突出的幾點。

C 語言的優點

圖 1 C 語言的優點

2.1 設計特性

C 是一門流行的語言,融合了計算機科學理論和實踐的控制特性。C 語言的設計理念讓用戶能輕松地完成自頂向下的規划、結構化編程和模塊化設計。因此,用 C 語言編寫的程序更易懂、更可靠。

2.2 高效性

C 是高效的語言。在設計上,它充分利用了當前計算機的優勢,因此 C 程序相對更緊湊,而且運行速度很快。實際上,C 語言具有通常是匯編語言才具有的微調控制能力(匯編語言是為特殊的中央處理單元設計的一系列內部指令,使用助記符來表示;不同的 CPU 系列使用不同的匯編語言),可以根據具體情況微調程序以獲得最大運行速度或最有效地使用內存。

2.3 可移植性

C 是可移植的語言。這意味着,在一種系統中編寫的 C 程序稍作修改或不修改就能在其他系統運行。如需修改,也只需簡單更改主程序頭文件中的少許項即可。大部分語言都希望成為可移植語言,但是,如果經歷過把 IBM PC BASIC 程序轉換成蘋果 BASIC(兩者是近親),或者在 UNIX 系統中運行 IBM 大型機的 FORTRAN 程序的人都知道,移植是最麻煩的事。C 語言是可移植方面的佼佼者。從 8 位微處理器到克雷超級計算機,許多計算機體系結構都可以使用 C 編譯器(C 編譯器是把 C 代碼轉換成計算機內部指令的程序)。但是要注意,程序中針對特殊硬件設備(如,顯示監視器)或操作系統特殊功能(如,Windows 8 或 OS X)編寫的部分,通常是不可移植的。

由於 C 語言與 UNIX 關系密切,UNIX 系統通常會將 C 編譯器作為軟件包的一部分。安裝 Linux 時,通常也會安裝 C 編譯器。供個人計算機使用的 C 編譯器很多,運行各種版本的 Windows 和 Macintosh(即,Mac)的 PC 都能找到合適的 C 編譯器。因此,無論是使用家庭計算機、專業工作站,還是大型機,都能找到針對特定系統的 C 編譯器。

2.4 強大而靈活

C 語言功能強大且靈活(計算機領域經常使用這兩個詞)。例如,功能強大且靈活的 UNIX 操作系統,大部分是用 C 語言寫的;其他語言(如,FORTRAN、Perl、Python、Pascal、LISP、Logo、BASIC)的許多編譯器和解釋器都是用C語言編寫的。因此,在 UNIX 機上使用 FORTRAN 時,最終是由 C 程序生成最后的可執行程序。C 程序可以用於解決物理學和工程學的問題,甚至可用於制作電影的動畫特效。

2.5 面向程序員

C 語言是為了滿足程序員的需求而設計的,程序員利用 C 可以訪問硬件、操控內存中的位。C 語言有豐富的運算符,能讓程序員簡潔地表達自己的意圖。C 語言不像 Pascal 甚至是 C++ 那么嚴格。這樣的靈活性既是優點也是缺點。優點是,許多任務用 C 來處理都非常簡潔(如,轉換數據的格式);缺點是,你可能會犯一些莫名其妙的錯誤,這些錯誤不可能在其他語言中出現。C 語言在提供更多自由的同時,也讓使用者承擔了更大的責任。

另外,大多數 C 實現都有一個大型的庫,包含眾多有用的 C 函數。這些函數用於處理程序員經常需要解決的問題。

2.6 缺點

人無完人,金無足赤。C 語言也有一些缺點。例如,前面提到的,要享受用 C 語言自由編程的樂趣,就必須承擔更多的責任。特別是,C 語言使用指針,而涉及指針的編程錯誤往往難以察覺。有句話說的好:想擁有自由就必須時刻保持警惕。

C 語言緊湊簡潔,結合了大量的運算符。正因如此,我們也可以編寫出讓人極其費解的代碼。雖然沒必要強迫自己編寫晦澀的代碼,但是有興趣寫寫也無妨。試問,除 C 語言外還為哪種語言舉辦過年度混亂代碼大賽[1]

瑕不掩瑜,C 語言的優點比缺點多很多。我們不想在這里多費筆墨,還是來聊聊 C 語言的其他話題。

三、C 語言的應用范圍

早在 20 世紀 80 年代,C 語言就已經成為小型計算機(UNIX 系統)使用的主流語言。從那以后,C 語言的應用范圍擴展到微型機(個人計算機)和大型機(龐然大物)。如圖 2 所示,許多軟件公司都用 C 語言來開發文字處理程序、電子表格、編譯器和其他產品,因為用 C 語言編寫的程序緊湊而高效。更重要的是,C 程序很方便修改,而且移植到新型號的計算機中也沒什么問題。

C 語言的應用范圍

圖 2 C 語言的應用范圍

無論是軟件公司、經驗豐富的 C 程序員,還是其他用戶,都能從 C 語言中受益。越來越多的計算機用戶已轉而求助 C 語言解決一些安全問題。不一定非得是計算機專家才能使用 C 語言。

20 世紀 90 年代,許多軟件公司開始改用 C++ 來開發大型的編程項目。C++ 在 C 語言的基礎上嫁接了面向對象編程工具(面向對象編程是一門哲學,它通過對語言建模來適應問題,而不是對問題建模以適應語言)。C++ 幾乎是 C 的超集,這意味着任何 C 程序差不多就是一個 C++ 程序。學習 C 語言,也相當於學習了許多 C++ 的知識。

雖然這些年來 C++ 和 JAVA 非常流行,但是 C 語言仍是軟件業中的核心技能。在最想具備的技能中,C 語言通常位居前十。特別是,C 語言已成為嵌入式系統編程的流行語言。也就是說,越來越多的汽車、照相機、DVD 播放機和其他現代化設備的微處理器都用 C 語言進行編程。除此之外,C 語言還從長期被 FORTRAN 獨占的科學編程領域分得一杯羹。最終,作為開發操作系統的卓越語言,C 在 Linux 開發中扮演着極其重要的角色。因此,在進入 21 世紀的第 2 個 10 年中,C 語言仍然保持着強勁的勢頭。

簡而言之,C 語言是最重要的編程語言之一,將來也是如此。如果你想拿下一份編程的工作,被問到是否會 C 語言時,最好回答“是”。

四、計算機能做什么

在學習如何用 C 語言編程之前,最好先了解一下計算機的工作原理。這些知識有助於你理解用C語言編寫程序和運行 C 程序時所發生的事情之間有什么聯系。

現代的計算機由多種部件構成。中央處理單元(CPU)承擔絕大部分的運算工作。隨機存取內存(RAM)是存儲程序和文件的工作區;而永久內存存儲設備(過去一般指機械硬盤,現在還包括固態硬盤)即使在關閉計算機后,也不會丟失之前存儲的程序和文件。另外,還有各種外圍設備(如,鍵盤、鼠標、觸摸屏、監視器)提供人與計算機之間的交互。CPU 負責處理程序,接下來我們重點討論它的工作原理。

CPU 的工作非常簡單,至少從以下簡短的描述中看是這樣。它從內存中獲取並執行一條指令,然后再從內存中獲取並執行下一條指令,諸如此類(一個吉赫茲的 CPU 一秒鍾能重復這樣的操作大約十億次,因此,CPU 能以驚人的速度從事枯燥的工作)。CPU 有自己的小工作區——由若干個寄存器組成,每個寄存器都可以存儲一個數字。一個寄存器存儲下一條指令的內存地址,CPU 使用該地址來獲取和更新下一條指令。在獲取指令后,CPU 在另一個寄存器中存儲該指令,並更新第 1 個寄存器存儲下一條指令的地址。CPU 能理解的指令有限(這些指令的集合叫作指令集)。而且,這些指令相當具體,其中的許多指令都是用於請求計算機把一個數字從一個位置移動到另一個位置。例如,從內存移動到寄存器。

下面介紹兩個有趣的知識。其一,存儲在計算機中的所有內容都是數字。計算機以數字形式存儲數字和字符(如,在文本文檔中使用的字母)。每個字符都有一個數字碼。計算機載入寄存器的指令也以數字形式存儲,指令集中的每條指令都有一個數字碼。其二,計算機程序最終必須以數字指令碼(即,機器語言)來表示。

簡而言之,計算機的工作原理是:如果希望計算機做某些事,就必須為其提供特殊的指令列表(程序),確切地告訴計算機要做的事以及如何做。你必須用計算機能直接明白的語言(機器語言)創建程序。這是一項繁瑣、乏味、費力的任務。計算機要完成諸如兩數相加這樣簡單的事,就得分成類似以下幾個步驟。

  1. 從內存位置 2000 上把一個數字拷貝到寄存器1。

  2. 從內存位置 2004 上把另一個數字拷貝到寄存器2。

  3. 把寄存器 2 中的內容與寄存器 1 中的內容相加,把結果存儲在寄存器 1 中。

  4. 把寄存器 1 中的內容拷貝到內存位置 2008。

而你要做的是,必須用數字碼來表示以上的每個步驟!

如果以這種方式編寫程序很合你的意,那不得不說抱歉,因為用機器語言編程的黃金時代已一去不復返。但是,如果你對有趣的事情比較感興趣,不妨試試高級編程語言。

五、高級計算機語言和編譯器

高級編程語言(如,C)以多種方式簡化了編程工作。首先,不必用數字碼表示指令;其次,使用的指令更貼近你如何想這個問題,而不是類似計算機那樣繁瑣的步驟。使用高級編程語言,可以在更抽象的層面表達你的想法,不用考慮 CPU 在完成任務時具體需要哪些步驟。例如,對於兩數相加,可以這樣寫:

total = mine + yours;

對我們而言,光看這行代碼就知道要計算機做什么;而看用機器語言寫成的等價指令(多條以數字碼形式表現的指令)則費勁得多。但是,對計算機而言卻恰恰相反。在計算機看來,高級指令就是一堆無法理解的無用數據。編譯器在這里派上了用場。編譯器是把高級語言程序翻譯成計算機能理解的機器語言指令集的程序。程序員進行高級思維活動,而編譯器則負責處理冗長乏味的細節工作。

編譯器還有一個優勢。一般而言,不同 CPU 制造商使用的指令系統和編碼格式不同。例如,用 Intel Core i7(英特爾酷睿 i7)CPU 編寫的機器語言程序對於 ARM Cortex-A57 CPU 而言什么都不是。但是,可以找到與特定類型 CPU 匹配的編譯器。因此,使用合適的編譯器或編譯器集,便可把一種高級語言程序轉換成供各種不同類型 CPU 使用的機器語言程序。一旦解決了一個編程問題,便可讓編譯器集翻譯成不同 CPU 使用的機器語言。

簡而言之,高級語言(如 C、Java、Pascal)以更抽象的方式描述行為,不受限於特定 CPU 或指令集。而且,高級語言簡單易學,用高級語言編程比用機器語言編程容易得多。

六、語言標准

目前,有許多 C 實現可用。在理想情況下,編寫 C 程序時,假設該程序中未使用機器特定的編程技術,那么它的運行情況在任何實現中都應該相同。要在實踐中做到這一點,不同的實現要遵循同一個標准。

C 語言發展之初,並沒有所謂的 C 標准。1978 年,布萊恩·柯林漢(Brian Kernighan)和丹尼斯·里奇(Dennis Ritchie)合著的 The C Programming Language(《C語言程序設計》)第 1 版是公認的 C 標准,通常稱之為 K&R C 或經典 C。特別是,該書中的附錄中的“C語言參考手冊”已成為實現 C 的指導標准。例如,編譯器都聲稱提供完整的 K&R 實現。雖然這本書中的附錄定義了 C 語言,但卻沒有定義 C 庫。與大多數語言不同的是,C 語言比其他語言更依賴庫,因此需要一個標准庫。實際上,由於缺乏官方標准,UNIX 實現提供的庫已成為了標准庫。

6.1 第 1 個 ANSI/ISO C 標准

隨着 C 的不斷發展,越來越廣泛地應用於更多系統中,C 社區意識到需要一個更全面、更新穎、更嚴格的標准。鑒於此,美國國家標准協會(ANSI)於 1983 年組建了一個委員會(X3J11),開發了一套新標准,並於 1989 年正式公布。該標准(ANSI C)定義了 C 語言和 C 標准庫。國際標准化組織於 1990 年采用了這套 C 標准(ISO C)。ISO C 和 ANSI C 是完全相同的標准。ANSI/ISO 標准的最終版本通常叫作 C89(因為 ANSI 於 1989 年批准該標准)或 C90(因為 ISO 於 1990 年批准該標准)。另外,由於 ANSI 先公布 C 標准,因此業界人士通常使用 ANSI C。

在該委員會制定的指導原則中,最有趣的可能是:保持 C 的精神。委員會在表述這一精神時列出了以下幾點:

  • 信任程序員;
  • 不要妨礙程序員做需要做的事;
  • 保持語言精練簡單;
  • 只提供一種方法執行一項操作;
  • 讓程序運行更快,即使不能保證其可移植性。

在最后一點上,標准委員會的用意是:作為實現,應該針對目標計算機來定義最合適的某特定操作,而不是強加一個抽象、統一的定義。在學習 C 語言過程中,許多方面都反映了這一哲學思想。

6.2 C99 標准

1994 年,ANSI/ISO 聯合委員會(C9X 委員會)開始修訂 C 標准,最終發布了 C99 標准。該委員會遵循了最初 C90 標准的原則,包括保持語言的精練簡單。委員會的用意不是在 C 語言中添加新特性,而是為了達到新的目標。第 1 個目標是,支持國際化編程。例如,提供多種方法處理國際字符集。第 2 個目標是,“調整現有實踐致力於解決明顯的缺陷”。因此,在遇到需要將 C 移至 64 位處理器時,委員會根據現實生活中處理問題的經驗來添加標准。第 3 個目標是,為適應科學和工程項目中的關鍵數值計算,提高 C 的適應性,讓 C 比 FORTRAN 更有競爭力。

這 3 點(國際化、彌補缺陷和提高計算的實用性)是主要的修訂目標。在其他方面的改變則更為保守,例如,盡量與 C90、C++ 兼容,讓語言在概念上保持簡單。用委員會的話說:“……委員會很滿意讓 C++ 成為大型、功能強大的語言”。

C99 的修訂保留了 C 語言的精髓,C 仍是一門簡潔高效的語言。雖然該標准已發布了很長時間,但並非所有的編譯器都完全實現 C99 的所有改動。因此,你可能發現 C99 的一些改動在自己的系統中不可用,或者只有改變編譯器的設置才可用。

6.3 C11 標准

維護標准任重道遠。標准委員會在 2007 年承諾 C 標准的下一個版本是 C1X,2011 年終於發布了 C11 標准。此次,委員會提出了一些新的指導原則。出於對當前編程安全的擔憂,不那么強調“信任程序員”目標了。而且,供應商並未像對 C90 那樣很好地接受和支持 C99。這使得 C99 的一些特性成為 C11 的可選項。因為委員會認為,不應要求服務小型機市場的供應商支持其目標環境中用不到的特性。另外需要強調的是,修訂標准的原因不是因為原標准不能用,而是需要跟進新的技術。例如,新標准添加了可選項支持當前使用多處理器的計算機。對於 C11 標准,我們淺嘗輒止,深入分析這部分內容已超出本文討論的范圍。

七、使用 C 語言的 7 個步驟

C 是編譯型語言。如果之前使用過編譯型語言(如,Pascal 或 FORTRAN),就會很熟悉組建 C 程序的幾個基本步驟。但是,如果以前使用的是解釋型語言(如,BASIC)或面向圖形界面語言(如,Visual Basic),或者甚至沒接觸過任何編程語言,就有必要學習如何編譯。別擔心,這並不復雜。首先,為了讓讀者對編程有大概的了解,我們把編寫 C 程序的過程分解成 7 個步驟(見圖 3)。注意,這是理想狀態。在實際的使用過程中,尤其是在較大型的項目中,可能要做一些重復的工作,根據下一個步驟的情況來調整或改進上一個步驟。

編程的 7 個步驟

圖 3 編程的 7 個步驟

7.1 第 1 步:定義程序的目標

在動手寫程序之前,要在腦中有清晰的思路。想要程序去做什么首先自己要明確自己想做什么,思考你的程序需要哪些信息,要進行哪些計算和控制,以及程序應該要報告什么信息。在這一步驟中,不涉及具體的計算機語言,應該用一般術語來描述問題。

7.2 第 2 步:設計程序

對程序應該完成什么任務有概念性的認識后,就應該考慮如何用程序來完成它。例如,用戶界面應該是怎樣的?如何組織程序?目標用戶是誰?准備花多長時間來完成這個程序?

除此之外,還要決定在程序(還可能是輔助文件)中如何表示數據,以及用什么方法處理數據。學習 C 語言之初,遇到的問題都很簡單,沒什么可選的。但是,隨着要處理的情況越來越復雜,需要決策和考慮的方面也越來越多。通常,選擇一個合適的方式表示信息可以更容易地設計程序和處理數據。

再次強調,應該用一般術語來描述問題,而不是用具體的代碼。但是,你的某些決策可能取決於語言的特性。例如,在數據表示方面,C 的程序員就比 Pascal 的程序員有更多選擇。

7.3 第 3 步:編寫代碼

設計好程序后,就可以編寫代碼來實現它。也就是說,把你設計的程序翻譯成 C 語言。這里是真正需要使用 C 語言的地方。可以把思路寫在紙上,但是最終還是要把代碼輸入計算機。這個過程的機制取決於編程環境,我們稍后會詳細介紹一些常見的環境。一般而言,使用文本編輯器創建源代碼文件。該文件中內容就是你翻譯的 C 語言代碼。程序清單 1 是一個 C 源代碼的示例。

程序清單 1 C 源代碼示例

#include <stdio.h>
int main(void)
{
     int dogs;

     printf("How many dogs do you have?\n");
     scanf("%d", &dogs);
     printf("So you have %d dog(s)!\n", dogs);

     return 0;
}

在這一步驟中,應該給自己編寫的程序添加文字注釋。最簡單的方式是使用 C 的注釋工具在源代碼中加入對代碼的解釋。

7.4 第 4 步:編譯

接下來的這一步是編譯源代碼。再次提醒讀者注意,編譯的細節取決於編程的環境,我們稍后馬上介紹一些常見的編程環境。現在,先從概念的角度講解編譯發生了什么事情。

前面介紹過,編譯器是把源代碼轉換成可執行代碼的程序。可執行代碼是用計算機的機器語言表示的代碼。這種語言由數字碼表示的指令組成。如前所述,不同的計算機使用不同的機器語言方案。C 編譯器負責把 C 代碼翻譯成特定的機器語言。此外,C 編譯器還將源代碼與C庫(庫中包含大量的標准函數供用戶使用,如 printf() 和 scanf())的代碼合並成最終的程序(更精確地說,應該是由一個被稱為鏈接器的程序來鏈接庫函數,但是在大多數系統中,編譯器運行鏈接器)。其結果是,生成一個用戶可以運行的可執行文件,其中包含着計算機能理解的代碼。

編譯器還會檢查 C 語言程序是否有效。如果 C 編譯器發現錯誤,就不生成可執行文件並報錯。理解特定編譯器報告的錯誤或警告信息是程序員要掌握的另一項技能。

7.5 第 5 步:運行程序

傳統上,可執行文件是可運行的程序。在常見環境(包括 Windows 命令提示符模式、UNIX 終端模式和 Linux 終端模式)中運行程序要輸入可執行文件的文件名,而其他環境可能要運行命令(如,在 VAX 中的 VMS[2])或一些其他機制。例如,在 Windows 和 Macintosh 提供的集成開發環境(IDE)中,用戶可以在 IDE 中通過選擇菜單中的選項或按下特殊鍵來編輯和執行 C 程序。最終生成的程序可通過單擊或雙擊文件名或圖標直接在操作系統中運行。

7.6 第 6 步:測試和調試程序

程序能運行是個好跡象,但有時也可能會出現運行錯誤。接下來,應該檢查程序是否按照你所設計的思路運行。你會發現你的程序中有一些錯誤,計算機行話叫作 bug。查找並修復程序錯誤的過程叫調試。學習的過程中不可避免會犯錯,學習編程也是如此。因此,當你把所學的知識應用於編程時,最好為自己會犯錯做好心理准備。隨着你越來越老練,你所寫的程序中的錯誤也會越來越不易察覺。

將來犯錯的機會很多。你可能會犯基本的設計錯誤,可能錯誤地實現了一個好想法,可能忽視了輸入檢查導致程序癱瘓,可能會把圓括號放錯地方,可能誤用 C 語言或打錯字,等等。把你將來犯錯的地方列出來,這份錯誤列表應該會很長。

看到這里你可能會有些絕望,但是情況沒那么糟。現在的編譯器會捕獲許多錯誤,而且自己也可以找到編譯器未發現的錯誤。在學習 C 語言的過程中,我們會給讀者提供一些調試的建議。

7.7 第 7 步:維護和修改代碼

創建完程序后,你發現程序有錯,或者想擴展程序的用途,這時就要修改程序。例如,用戶輸入以 Zz 開頭的姓名時程序出現錯誤、你想到了一個更好的解決方案、想添加一個更好的新特性,或者要修改程序使其能在不同的計算機系統中運行,等等。如果在編寫程序時清楚地做了注釋並采用了合理的設計方案,這些事情都很簡單。

7.8 說明

編程並非像描述那樣是一個線性的過程。有時,要在不同的步驟之間往復。例如,在寫代碼時發現之前的設計不切實際,或者想到了一個更好的解決方案,或者等程序運行后,想改變原來的設計思路。對程序做文字注釋為今后的修改提供了方便。

許多初學者經常忽略第 1 步和第 2 步(定義程序目標和設計程序),直接跳到第 3 步(編寫代碼)。剛開始學習時,編寫的程序非常簡單,完全可以在腦中構思好整個過程。即使寫錯了,也很容易發現。但是,隨着編寫的程序越來越龐大、越來越復雜,動腦不動手可不行,而且程序中隱藏的錯誤也越來越難找。最終,那些跳過前兩個步驟的人往往浪費了更多的時間,因為他們寫出的程序難看、缺乏條理、讓人難以理解。要編寫的程序越大越復雜,事先定義和設計程序環節的工作量就越大。

磨刀不誤砍柴工,應該養成先規划再動手編寫代碼的好習慣,用紙和筆記錄下程序的目標和設計框架。這樣在編寫代碼的過程中會更加得心應手、條理清晰。

八、編程機制

生成程序的具體過程因計算機環境而異。C 是可移植性語言,因此可以在許多環境中使用,包括 UNIX、Linux、MS-DOS(一些人仍在使用)、Windows 和 Macintosh OS。有些產品會隨着時間的推移發生演變或被取代。

首先,來看看許多 C 環境(包括上面提到的 5 種環境)共有的一些方面。雖然不必詳細了解計算機內部如何運行 C 程序,但是,了解一下編程機制不僅能豐富編程相關的背景知識,還有助於理解為何要經過一些特殊的步驟才能得到 C 程序。

用 C 語言編寫程序時,編寫的內容被存儲在文本文件中,該文件被稱為源代碼文件(source code file)。大部分 C 系統,包括之前提到的,都要求文件名以 .c 結尾(如,wordcount.c 和 budget.c)。在文件名中,點號(.)前面的部分稱為基本名(basename),點號后面的部分稱為擴展名(extension)。因此,budget 是基本名,c 是擴展名。基本名與擴展名的組合(budget.c)就是文件名。文件名應該滿足特定計算機操作系統的特殊要求。例如,MS-DOS 是 IBM PC 及其兼容機的操作系統,比較老舊,它要求基本名不能超過 8 個字符。因此,剛才提到的文件名 wordcount.c 就是無效的 DOS 文件名。有些 UNIX 系統限制整個文件名(包括擴展名)不超過 14 個字符,而有些 UNIX 系統則允許使用更長的文件名,最多 255 個字符。Linux、Windows 和 Macintosh OS 都允許使用長文件名。

接下來,我們來看一下具體的應用,假設有一個名為 concrete.c 的源文件,其中的 C 源代碼如程序清單 2 所示。

程序清單 2 c 程序

#include <stdio.h>
int main(void)
{
     printf("Concrete contains gravel and cement.\n");

     return 0;
}

如果看不懂程序清單 2 中的代碼,不用擔心,我們將在以后的文章中學習相關知識。

8.1 目標代碼文件、可執行文件和庫

C 編程的基本策略是,用程序把源代碼文件轉換為可執行文件(其中包含可直接運行的機器語言代碼)。典型的 C 實現通過編譯和鏈接兩個步驟來完成這一過程。編譯器把源代碼轉換成中間代碼,鏈接器把中間代碼和其他代碼合並,生成可執行文件。C 使用這種分而治之的方法方便對程序進行模塊化,可以獨立編譯單獨的模塊,稍后再用鏈接器合並已編譯的模塊。通過這種方式,如果只更改某個模塊,不必因此重新編譯其他模塊。另外,鏈接器還將你編寫的程序和預編譯的庫代碼合並。

中間文件有多種形式。我們在這里描述的是最普遍的一種形式,即把源代碼轉換為機器語言代碼,並把結果放在目標代碼文件(或簡稱目標文件)中(這里假設源代碼只有一個文件)。雖然目標文件中包含機器語言代碼,但是並不能直接運行該文件。因為目標文件中存儲的是編譯器翻譯的源代碼,這還不是一個完整的程序。

目標代碼文件缺失啟動代碼(startup code)。啟動代碼充當着程序和操作系統之間的接口。例如,可以在 MS Windows 或 Linux 系統下運行 IBM PC 兼容機。這兩種情況所使用的硬件相同,所以目標代碼相同,但是 Windows 和 Linux 所需的啟動代碼不同,因為這些系統處理程序的方式不同。

目標代碼還缺少庫函數。幾乎所有的 C 程序都要使用C標准庫中的函數。例如,concrete.c 中就使用了 printf() 函數。目標代碼文件並不包含該函數的代碼,它只包含了使用 printf() 函數的指令。printf() 函數真正的代碼存儲在另一個被稱為庫的文件中。庫文件中有許多函數的目標代碼。

鏈接器的作用是,把你編寫的目標代碼、系統的標准啟動代碼和庫代碼這 3 部分合並成一個文件,即可執行文件。對於庫代碼,鏈接器只會把程序中要用到的庫函數代碼提取出來(見圖 4)。

編譯器和鏈接器

圖 4 編譯器和鏈接器

簡而言之,目標文件和可執行文件都由機器語言指令組成的。然而,目標文件中只包含編譯器為你編寫的代碼翻譯的機器語言代碼,可執行文件中還包含你編寫的程序中使用的庫函數和啟動代碼的機器代碼。

在有些系統中,必須分別運行編譯程序和鏈接程序,而在另一些系統中,編譯器會自動啟動鏈接器,用戶只需給出編譯命令即可。

接下來,了解一些具體的系統。

8.2 UNIX 系統

由於 C 語言因 UNIX 系統而生,也因此而流行,所以我們從 UNIX 系統開始(注意:我們提到的 UNIX 還包含其他系統,如 FreeBSD,它是 UNIX 的一個分支,但是由於法律原因不使用該名稱)。

1.在 UNIX 系統上編輯

UNIX C 沒有自己的編輯器,但是可以使用通用的 UNIX 編輯器,如 emacs、jove、vi 或 X Window System 文本編輯器。

作為程序員,要負責輸入正確的程序和為存儲該程序的文件起一個合適的文件名。如前所述,文件名應該以 .c 結尾。注意,UNIX 區分大小寫。因此,budget.c、BUDGET.c 和 Budget.c 是 3 個不同但都有效的 C 源文件名。但是 BUDGET.C 是無效文件名,因為該名稱的擴展名使用了大寫 C 而不是小寫 c。

假設我們在 vi 編輯器中編寫了下面的程序,並將其存儲在 inform.c 文件中:

#include <stdio.h>
int main(void)
{
     printf("A .c is used to end a C program filename.\n");

     return 0;
}

以上文本就是源代碼,inform.c 是源文件。注意,源文件是整個編譯過程的開始,不是結束。

2.在 UNIX 系統上編譯

雖然在我們看來,程序完美無缺,但是對計算機而言,這是一堆亂碼。計算機不明白 #include 和 printf 是什么(也許你現在也不明白,但是學到后面就會明白,而計算機卻不會)。如前所述,我們需要編譯器將我們編寫的代碼(源代碼)翻譯成計算機能看懂的代碼(機器代碼)。最后生成的可執行文件中包含計算機要完成任務所需的所有機器代碼。

以前,UNIX C 編譯器要調用語言定義的 cc 命令。但是,它沒有跟上標准發展的腳步,已經退出了歷史舞台。但是,UNIX 系統提供的 C 編譯器通常來自一些其他源,然后以 cc 命令作為編譯器的別名。因此,雖然在不同的系統中會調用不同的編譯器,但用戶仍可以繼續使用相同的命令。

編譯 inform.c,要輸入以下命令:

cc inform.c

幾秒鍾后,會返回 UNIX 的提示,告訴用戶任務已完成。如果程序編寫錯誤,你可能會看到警告或錯誤消息,但我們先假設編寫的程序完全正確(如果編譯器報告 void 的錯誤,說明你的系統未更新成 ANSI C 編譯器,只需刪除 void 即可)。如果使用 ls 命令列出文件,會發現有一個 a.out 文件(見圖 5)。該文件是包含已翻譯(或已編譯)程序的可執行文件。要運行該文件,只需輸入:

a.out
用 UNIX 准備 C 程序

圖 5 用 UNIX 准備 C 程序

輸出內容如下:

A .c is used to end a C program filename.

如果要存儲可執行文件(a.out),應該把它重命名。否則,該文件會被下一次編譯程序時生成的新 a.out 文件替換。

如何處理目標代碼?C 編譯器會創建一個與源代碼基本名相同的目標代碼文件,但是其擴展名是 .o。在該例中,目標代碼文件是 inform.o。然而,卻找不到這個文件,因為一旦鏈接器生成了完整的可執行程序,就會將其刪除。如果原始程序有多個源代碼文件,則保留目標代碼文件。學到后面多文件程序時,你會明白到這樣做的好處。

8.3 GNU 編譯器集合和 LLVM 項目

GNU 項目始於 1987 年,是一個開發大量自由 UNIX 軟件的集合(GNU 的意思是“GNU’s Not UNIX”,即 GNU 不是 UNIX)。GNU 編譯器集合(也被稱為 GCC,其中包含 GCC C 編譯器)是該項目的產品之一。GCC 在一個指導委員會的帶領下,持續不斷地開發,它的 C 編譯器緊跟 C 標准的改動。GCC 有各種版本以適應不同的硬件平台和操作系統,包括 UNIX、Linux 和 Windows。用 gcc 命令便可調用 GCC C 編譯器。許多使用 gcc 的系統都用 cc 作為 gcc 的別名。

LLVM 項目成為 cc 的另一個替代品。該項目是與編譯器相關的開源軟件集合,始於伊利諾伊大學 2000 年的研究項目。它的 Clang 編譯器處理 C 代碼,可以通過 clang 調用。有多種版本供不同的平台使用,包括 Linux。2012 年,Clang 成為 FreeBSD 的默認 C 編譯器。Clang 也對最新的 C 標准支持得很好。

GNU 和 LLVM 都可以使用 -v 選項來顯示版本信息,因此各系統都使用 cc 別名來代替 gcc 或 clang 命令。以下組合:

cc -v

顯示你所使用的編譯器及其版本。

gcc 和 clang 命令都可以根據不同的版本選擇運行時選項來調用不同 C 標准。

gcc -std=c99 inform.c
gcc -std=c1x inform.c
gcc -std=c11 inform.c

GCC 最基本的用法是:gcc [options] [filenames],其中 options 是所需的參數,filenames 是文件名。

第 1 行調用 C99 標准,第 2 行調用 GCC 接受 C11 之前的草案標准,第 3 行調用 GCC 接受的 C11 標准版本。Clang 編譯器在這一點上用法與 GCC 相同。

8.4 Linux 系統

Linux 是一個開源、流行、類似於 UNIX 的操作系統,可在不同平台(包括 PC 和 Mac)上運行。在 Linux 中准備 C 程序與在 UNIX 系統中幾乎一樣,不同的是要使用 GNU 提供的 GCC 公共域 C 編譯器。編譯命令類似於:

gcc inform.c

注意,在安裝 Linux 時,可選擇是否安裝 GCC。如果之前沒有安裝 GCC,則必須安裝。通常,安裝過程會將 cc 作為 gcc 的別名,因此可以在命令行中使用 cc 來代替 gcc。

欲詳細了解 GCC 和最新發布的版本,請訪問 https://www.gnu.org/software/gcc/index.html

8.5 PC 的命令行編譯器

C 編譯器不是標准 Windows 軟件包的一部分,因此需要從別處獲取並安裝 C 編譯器。可以從互聯網免費下載 Cygwin 和 MinGW,這樣便可在 PC 上通過命令行使用 GCC 編譯器。Cygwin 在自己的視窗運行,模仿 Linux 命令行環境,有一行命令提示。MinGW 在 Windows 的命令提示模式中運行。這和 GCC 的最新版本一樣,支持 C99 和 C11 最新的一些功能。Borland 的 C++ 編譯器 5.5 也可以免費下載,支持 C90。

源代碼文件應該是文本文件,不是字處理器文件(字處理器文件包含許多額外的信息,如字體和格式等)。因此,要使用文本編輯器(如,Windows Notepad)來編輯源代碼。如果使用字處理器,要以文本模式另存文件。源代碼文件的擴展名應該是 .c。一些字處理器會為文本文件自動添加 .txt 擴展名。如果出現這種情況,要更改文件名,把 txt 替換成 c。

通常,C 編譯器生成的中間目標代碼文件的擴展名是 .obj(也可能是其他擴展名)。與 UNIX 編譯器不同,這些編譯器在完成編譯后通常不會刪除這些中間文件。有些編譯器生成帶 .asm 擴展名的匯編語言文件,而有些編譯器則使用自己特有的格式。

一些編譯器在編譯后會自動運行鏈接器,另一些要求用戶手動運行鏈接器。在可執行文件中鏈接的結果是,在原始的源代碼基本名后面加上 .exe 擴展名。例如,編譯和鏈接 concrete.c 源代碼文件,生成的是 concrete.exe 文件。可以在命令行輸入基本名來運行該程序:

C>concrete

8.6 集成開發環境(Windows)

許多供應商(包括微軟、Embarcadero、Digital Mars)都提供 Windows 下的集成開發環境,或稱為 IDE(目前,大多數 IDE 都是 C 和 C++ 結合的編譯器)。可以免費下載的 IDE 有 Microsoft Visual Studio Express 和 Pelles C。利用集成開發環境可以快速開發 C 程序。關鍵是,這些 IDE 都內置了用於編寫 C 程序的編輯器。這類集成開發環境都提供了各種菜單(如,命名、保存源代碼文件、編譯程序、運行程序等),用戶不用離開 IDE 就能順利編寫、編譯和運行程序。如果編譯器發現錯誤,會返回編輯器中,標出有錯誤的行號,並簡單描述情況。

初次接觸 Windows IDE 可能會望而生畏,因為它提供了多種目標(target),即運行程序的多種環境。例如,IDE 提供了 32 位 Windows 程序、64 位 Windows 程序、動態鏈接庫文件(DLL)等。許多目標都涉及 Windows 圖形界面。要管理這些(及其他)選擇,通常要先創建一個項目(project),以便稍后在其中添加待使用的源代碼文件名。不同的產品具體步驟不同。一般而言,首先使用【文件】菜單或【項目】菜單創建一個項目。選擇正確的項目形式非常重要。本文中的例子都是一般示例,針對在簡單的命令行環境中運行而設計。Windows IDE 提供多種選擇以滿足用戶的不同需求。例如,Microsoft Visual Studio 提供【Win32 控制台應用程序】選項。對於其他系統,查找一個諸如【DOS EXE】、【Console】或【Character Mode】的可執行選項。選擇這些模式后,將在一個類控制台窗口中運行可執行程序。選擇好正確的項目類型后,使用 IDE 的菜單打開一個新的源代碼文件。對於大多數產品而言,使用【文件】菜單就能完成。你可能需要其他步驟將源文件添加到項目中。

通常,Windows IDE 既可處理 C 也可處理 C++,因此要指定待處理的程序是 C 還是 C++。有些產品用項目類型來區分兩者,有些產品(如,Microsoft Visual C++)用 .c 文件擴展名來指明使用 C 而不是 C++。當然,大多數 C 程序也可以作為 C++ 程序運行。

你可能會遇到一個問題:在程序執行完畢后,執行程序的窗口立即消失。如果不希望出現這種情況,可以讓程序暫停,直到按下 Enter 鍵,窗口才消失。要實現這種效果,可以在程序的最后(return 這行代碼之前)添加下面一行代碼:

getchar();

該行讀取一次鍵的按下,所以程序在用戶按下 Enter 鍵之前會暫停。有時根據程序的需要,可能還需要一個擊鍵等待。這種情況下,必須用兩次 getchar():

getchar();
getchar();

例如,程序在最后提示用戶輸入體重。用戶鍵入體重后,按下 Enter 鍵以輸入數據。程序將讀取體重,第 1 個 getchar() 讀取 Enter 鍵,第 2 個 getchar() 會導致程序暫停,直至用戶再次按下 Enter 鍵。如果你現在不知所雲,沒關系,在學完 C 輸出后就會明白。到時,我們會提醒讀者使用這種方法。

雖然許多 IDE 在使用上大體一致,但是細節上有所不同。就一個產品的系列而言,不同版本也是如此。要經過一段時間的實踐,才會熟悉編譯器的工作方式。必要時,還需閱讀使用手冊或網上教程。

Microsoft Visual Studio 和 C 標准

在 Windows 軟件開發中,Microsoft Visual Studio 及其免費版本 Microsoft Visual Studio Express 都久負盛名,它們與 C 標准的關系也很重要。然而,微軟鼓勵程序員從 C 轉向 C++ 和 C#。雖然 Visual Studio 支持 C89/90,但是到目前為止,它只選擇性地支持那些在 C++ 新特性中能找到的 C 標准(如,long long 類型)。而且,自 2012 版本起,Visual Studio 不再把 C 作為項目類型的選項。盡管如此,本文中的絕大多數程序仍可用 Visual Studio 來編譯。在新建項目時,選擇 C++ 選項,然后選擇【Win32 控制台應用程序】,在應用設置中選擇【空項目】。幾乎所有的 C 程序都能與 C++ 程序兼容。所以,本文中的絕大多數 C 程序都可作為 C++ 程序運行。或者,在選擇 C++ 選項后,將默認的源文件擴展名 .cpp 替換成 .c,編譯器便會使用 C 語言的規則代替 C++。

8.7 Windows/Linux

許多 Linux 發行版都可以安裝在 Windows 系統中,以創建雙系統。一些存儲器會為 Linux 系統預留空間,以便可以啟動 Windows 或 Linux。可以在 Windows 系統中運行 Linux 程序,或在 Linux 系統中運行 Windows 程序。不能通過 Windows 系統訪問 Linux 文件,但是可以通過 Linux 系統訪問 Windows 文檔。

8.8 Macintosh 中的 C

目前,蘋果免費提供 Xcode 開發系統下載(過去,它有時免費,有時付費)。它允許用戶選擇不同的編程語言,包括 C 語言。

Xcode 憑借可處理多種編程語言的能力,可用於多平台,開發超大型的項目。但是,首先要學會如何編寫簡單的 C 程序。在 Xcode 4.6 中,通過【File】菜單選擇【New Project】,然后選擇【OS X Application Command Line Tool】,接着輸入產品名並選擇 C 類型。Xcode 使用 Clang 或 GCC C 編譯器來編譯 C 代碼,它以前默認使用 GCC,但是現在默認使用 Clang。可以設置選擇使用哪一個編譯器和哪一套 C 標准(因為許可方面的事宜,Xcode 中 Clang 的版本比 GCC 的版本要新)。

Mac OS X 建立在 UNIX 系統之上,終端工具打開的窗口是讓用戶在 UNIX 命令行環境中運行程序。蘋果在標准軟件包中不提供命令行編譯器,但是,如果下載了 Xcode,還可以下載可選的命令行工具,這樣就可以使用 clang 和 gcc 命令在命令行模式中編譯。

原文:初識 C 語言

(完)


  1. 國際 C 語言混淆代碼大賽(IOCCC,The International Obfuscated C Code Contest)。這是一項國際編程賽事,從 1984 年開始,每年舉辦一次(1997、1999、2002、2003 和 2006 年除外),目的是寫出最有創意且最讓人難以理解的 C 語言代碼。 ↩︎

  2. VAX(Virtual Address eXtension)是一種可支持機器語言和虛擬地址的 32 位小型計算機。VMS(Virtual Memory System)是舊名,現在叫 OpenVMS,是一種用於服務器的操作系統,可在 VAX、Alpha 或 Itanium 處理器系列平台上運行。 ↩︎


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM