經過這麼多年,世界仍然由 C 編程驅動

已發表: 2022-03-11

今天存在的許多 C 項目都是在幾十年前開始的。

UNIX 操作系統的開發始於 1969 年,其代碼於 1972 年用 C 重寫。創建 C 語言實際上是為了將 UNIX 內核代碼從彙編語言轉移到更高級別的語言,後者可以用更少的代碼行完成相同的任務.

Oracle 數據庫開發始於 1977 年,1983 年其代碼由彙編改寫為 C,成為世界上最流行的數據庫之一。

1985 年發布了 Windows 1.0。 儘管 Windows 源代碼不公開,但據說它的內核大部分是用 C 編寫的,有些部分是彙編的。 Linux 內核開發始於 1991 年,也是用 C 語言編寫的。第二年,它在 GNU 許可下發布,並被用作 GNU 操作系統的一部分。 GNU 操作系統本身是使用 C 和 Lisp 編程語言開始的,因此它的許多組件都是用 C 編寫的。

但是 C 編程並不局限於幾十年前開始的項目,當時還沒有今天那麼多的編程語言。 許多 C 項目今天仍在啟動; 有一些很好的理由。

世界是如何由 C 驅動的?

儘管高級語言盛行,但 C 繼續賦予世界權力。 以下是一些被數百萬人使用並用 C 語言編程的系統。

微軟Windows

微軟的 Windows 內核主要是用 C 語言開發的,有些部分是用彙編語言開發的。 幾十年來,世界上使用最多的操作系統,擁有大約 90% 的市場份額,由 C 語言編寫的內核提供支持。

Linux

Linux 也主要是用 C 語言編寫的,其中一些部分是彙編語言。 全球 500 台最強大的超級計算機中約有 97% 運行 Linux 內核。 它也用於許多個人計算機。

蘋果

Mac 計算機也由 C 驅動,因為 OS X 內核主要是用 C 編寫的。Mac 中的每個程序和驅動程序,就像在 Windows 和 Linux 計算機中一樣,都在 C 驅動的內核上運行。

移動的

iOS、Android 和 Windows Phone 內核也是用 C 編寫的。它們只是對現有 Mac OS、Linux 和 Windows 內核的移動改編。 因此,您每天使用的智能手機都在 C 內核上運行。

用 C 編寫的操作系統內核

數據庫

世界上最流行的數據庫,包括 Oracle 數據庫、MySQL、MS SQL Server 和 PostgreSQL,都是用 C 編碼的(前三個實際上都是用 C 和 C++ 編寫的)。

數據庫用於各種系統:金融、政府、媒體、娛樂、電信、健康、教育、零售、社交網絡、網絡等。

由 C 驅動的數據庫

3D電影

3D 電影是使用通常用 C 和 C++ 編寫的應用程序創建的。 這些應用程序需要非常高效和快速,因為它們處理大量數據並每秒執行許多計算。 它們的效率越高,藝術家和動畫師製作電影鏡頭所需的時間就越少,公司節省的錢就越多。

嵌入式系統

想像一下,有一天你醒來去購物。 叫醒你的鬧鐘很可能是用 C 語言編寫的。然後你用微波爐或咖啡機做早餐。 它們也是嵌入式系統,因此可能是用 C 語言編程的。你一邊吃早餐一邊打開電視或收音機。 這些也是嵌入式系統,由 C 提供支持。當您使用遙控器打開車庫門時,您也在使用最有可能用 C 編程的嵌入式系統。

然後你上你的車。 如果它具有以下特性,也可以用 C 語言編程:

  • 自動變速器
  • 胎壓檢測系統
  • 傳感器(氧氣、溫度、油位等)
  • 記憶座椅和後視鏡設置。
  • 儀表板顯示
  • 防抱死剎車
  • 自動穩定控制
  • 巡航控制
  • 氣候控制
  • 兒童鎖
  • 無鑰匙進入
  • 加熱的座椅
  • 安全氣囊控制

你到商店,停好車,然後去自動售貨機買汽水。 他們用什麼語言來編程這個自動售貨機? 可能是 C。然後你在商店買東西。 收銀機也是用 C 語言編寫的。當你用信用卡付款時? 你猜對了:信用卡閱讀器很可能是用 C 語言編寫的。

所有這些設備都是嵌入式系統。 它們就像小型計算機,內部有一個微控制器/微處理器,在嵌入式設備上運行一個程序,也稱為固件。 該程序必須檢測按鍵並採取相應措施,並向用戶顯示信息。 例如,鬧鐘必須與用戶交互,檢測用戶按下的是什麼按鈕,有時還要檢測按下的時間長短,並相應地對設備進行編程,同時向用戶顯示相關信息。 例如,汽車的防抱死制動系統必須能夠檢測到輪胎的突然抱死,並採取措施在一小段時間內釋放制動器上的壓力,將其解鎖,從而防止不受控制的打滑。 所有這些計算都是由編程的嵌入式系統完成的。

儘管嵌入式系統上使用的編程語言可能因品牌而異,但由於 C 語言具有靈活性、效率、性能和接近硬件的特性,它們最常使用 C 語言進行編程。

嵌入式系統通常用 C 語言編寫

為什麼仍然使用 C 編程語言?

今天,有許多編程語言允許開發人員在不同類型的項目中比使用 C 更有效率。 有更高級別的語言提供更大的內置庫,可簡化 JSON、XML、UI、網頁、客戶端請求、數據庫連接、媒體操作等的工作。

但儘管如此,仍有很多理由相信 C 編程將在很長一段時間內保持活躍。

在編程語言中,一種尺寸並不適合所有人。 以下是 C 在某些應用程序中無與倫比且幾乎是強制性的一些原因。

便攜性和效率

C 幾乎是一種可移植的彙編語言。 它盡可能地靠近機器,而它幾乎普遍適用於現有的處理器架構。 幾乎所有現有架構都至少有一個 C 編譯器。 而如今,由於現代編譯器生成了高度優化的二進製文件,用手寫彙編來改進它們的輸出並不是一件容易的事。

這就是它的可移植性和效率,“其他編程語言的編譯器、庫和解釋器通常用 C 實現”。 Python、Ruby 和 PHP 等解釋型語言的主要實現是用 C 編寫的。它甚至被編譯器用於其他語言與機器通信。 例如,C 是 Eiffel 和 Forth 的中間語言。 這意味著,這些語言的編譯器不會為每個要支持的架構生成機器代碼,而是只生成中間 C 代碼,而 C 編譯器會處理機器代碼生成。

C 也已成為開發人員之間交流的通用語言。 正如 Dropbox 工程經理和 Cprogramming.com 的創建者 Alex Alllain 所說:

C 是一種很好的語言,可以以大多數人都習慣的方式表達編程中的常見想法。 此外,C 中使用的許多原則——例如,用於命令行參數的argcargv ,以及循環結構和變量類型——將出現在你學習的許多其他語言中,這樣你就可以說話了對人們來說,即使他們不以你們倆共同的方式了解 C。

內存操作

任意內存地址訪問和指針運算是使 C 語言非常適合系統編程(操作系統和嵌入式系統)的一個重要特性。

在硬件/軟件邊界,計算機系統和微控制器將其外圍設備和 I/O 引腳映射到內存地址。 系統應用程序必須讀取和寫入這些自定義內存位置才能與世界通信。 因此,C 操作任意內存地址的能力對於系統編程來說是必不可少的。

例如,可以對微控制器進行架構,使得每次設置地址 0x40008001 的第 4 位時,通用異步接收器/發送器(或 UART,用於與外圍設備通信的通用硬件組件)將發送內存地址 0x40008000 中的字節為 1,並且設置該位後,外圍設備將自動取消設置。

這將是通過該 UART 發送字節的 C 函數的代碼:

 #define UART_BYTE *(char *)0x40008000 #define UART_SEND *(volatile char *)0x40008001 |= 0x08 void send_uart(char byte) { UART_BYTE = byte; // write byte to 0x40008000 address UART_SEND; // set bit number 4 of address 0x40008001 }

函數的第一行將擴展為:

 *(char *)0x40008000 = byte;

這一行告訴編譯器將值0x40008000解釋為指向char的指針,然後取消引用(給出指向的值)該指針(使用最左邊的*運算符),最後將byte值分配給該取消引用的指針。 換句話說:將變量byte的值寫入內存地址0x40008000

下一行將擴展為:

 *(volatile char *)0x40008001 |= 0x08;

在這一行中,我們對地址0x40008001處的值和值0x08 (二進制中的00001000 ,即第 4 位中的 1)執行按位或運算,並將結果保存回地址0x40008001 。 換句話說:我們設置地址 0x40008001 的字節的第 4 位。 我們還聲明地址0x40008001處的值是volatile 。 這告訴編譯器該值可能會被我們代碼外部的進程修改,因此編譯器在寫入該地址後不會對該地址中的值做出任何假設。 (在這種情況下,UART 硬件在我們通過軟件設置它之後將其取消設置。)此信息對於編譯器的優化器很重要。 例如,如果我們在for循環中執行此操作,但沒有指定該值是 volatile,編譯器可能會假設該值在設置後永遠不會改變,並在第一個循環後跳過執行命令。

資源的確定性使用

系統編程不能依賴的一個通用語言特性是垃圾收集,甚至只是某些嵌入式系統的動態分配。 嵌入式應用程序在時間和內存資源方面非常有限。 它們通常用於實時系統,其中無法提供對垃圾收集器的非確定性調用。 如果由於內存不足而無法使用動態分配,那麼擁有其他內存管理機制非常重要,例如將數據放置在自定義地址中,這是 C 指針允許的。 嚴重依賴動態分配和垃圾收集的語言不適合資源有限的系統。

代碼大小

C 的運行時間非常短。 並且其代碼的內存佔用比大多數其他語言要小。

例如,與 C++ 相比,進入嵌入式設備的 C 生成的二進製文件的大小大約是由類似 C++ 代碼生成的二進製文件的一半。 造成這種情況的主要原因之一是異常支持。

異常是 C++ 在 C 上添加的一個很好的工具,如果不觸發和巧妙地實現,它們實際上沒有執行時間開銷(但以增加代碼大小為代價)。

讓我們看一個 C++ 的例子:

 // Class A declaration. Methods defined somewhere else; class A { public: A(); // Constructor ~A(); // Destructor (called when the object goes out of scope or is deleted) void myMethod(); // Just a method }; // Class B declaration. Methods defined somewhere else; class B { public: B(); // Constructor ~B(); // Destructor void myMethod(); // Just a method }; // Class C declaration. Methods defined somewhere else; class C { public: C(); // Constructor ~C(); // Destructor void myMethod(); // Just a method }; void myFunction() { A a; // Constructor aA() called. (Checkpoint 1) { B b; // Constructor bB() called. (Checkpoint 2) b.myMethod(); // (Checkpoint 3) } // b.~B() destructor called. (Checkpoint 4) { C c; // Constructor cC() called. (Checkpoint 5) c.myMethod(); // (Checkpoint 6) } // c.~C() destructor called. (Checkpoint 7) a.myMethod(); // (Checkpoint 8) } // a.~A() destructor called. (Checkpoint 9)

ABC類的方法在其他地方定義(例如在其他文件中)。 因此編譯器無法分析它們,也無法知道它們是否會拋出異常。 因此它必須準備好處理從它們的任何構造函數、析構函數或其他方法調用中拋出的異常。 析構函數不應該拋出(非常糟糕的做法),但用戶無論如何都可以拋出,或者他們可以通過調用一些拋出異常的函數或方法(顯式或隱式)間接拋出。

如果myFunction中的任何調用引發異常,堆棧展開機制必須能夠調用已構造對象的所有析構函數。 堆棧展開機制的一種實現將使用此函數的最後一次調用的返回地址來驗證觸發異常的調用的“檢查點編號”(這是簡單的解釋)。 它通過使用一個輔助的自動生成函數(一種查找表)來實現這一點,該函數將用於堆棧展開,以防從該函數的主體中拋出異常,類似於以下內容:

 // Possible autogenerated function void autogeneratedStackUnwindingFor_myFunction(int checkpoint) { switch (checkpoint) { // case 1 and 9: do nothing; case 3: b.~B(); goto destroyA; // jumps to location of destroyA label case 6: c.~C(); // also goes to destroyA as that is the next line destroyA: // label case 2: case 4: case 5: case 7: case 8: a.~A(); } }

如果從檢查點 1 和 9 拋出異常,則不需要銷毀任何對象。 對於檢查點 3, ba必須被破壞。 對於檢查點 6,必須破壞ca 。 在所有情況下,都必須遵守銷毀命令。 對於檢查點 2、4、5、7 和 8,只需要銷毀對象a

這個輔助函數增加了代碼的大小。 這是 C++ 為 C 添加的空間開銷的一部分。許多嵌入式應用程序無法承受這些額外空間。 因此,嵌入式系統的 C++ 編譯器通常具有禁用異常的標誌。 在 C++ 中禁用異常不是免費的,因為標準模板庫嚴重依賴異常來通知錯誤。 毫無例外地,使用這種修改後的方案需要對 C++ 開發人員進行更多培訓,以檢測可能的問題或發現錯誤。

而且,我們談論的是 C++,它的原則是:“不用為不用的東西付費”。 這種二進制大小的增加對於其他語言來說會變得更糟,這些語言會增加額外的開銷,而其他功能非常有用,但嵌入式系統無法提供。 雖然 C 沒有讓您使用這些額外的功能,但它允許比其他語言更緊湊的代碼佔用空間。

學習 C 的理由

C 不是一門難學的語言,所以學習它的所有好處都會很便宜。 讓我們看看其中的一些好處。

通用語

如前所述,C 是開發人員的通用語言。 書籍或互聯網上的許多新算法的實現首先(或僅)由其作者以 C 語言提供。 這為實現提供了最大可能的可移植性。 我看到程序員在互聯網上努力將 C 算法重寫為其他編程語言,因為他或她不知道 C 的非常基本的概念。

請注意,C 是一種古老且廣泛使用的語言,因此您可以在網絡上找到各種用 C 編寫的算法。 因此,您很可能會從了解這種語言中受益。

理解機器(用 C 語言思考)

當我們與同事討論某些代碼部分的行為或其他語言的某些特性時,我們最終會“用 C 語言交談”:這部分是在傳遞指向對象的“指針”還是複制整個對象? 這裡會發生任何“演員”嗎? 等等。

在分析高級語言的一部分代碼的行為時,我們很少討論(或思考)一部分代碼正在執行的彙編指令。 相反,在討論機器正在做什麼時,我們用 C 語言非常清楚地說話(或思考)。

此外,如果你不能停下來思考你正在做的事情,你最終可能會對如何(神奇地)完成事情產生某種迷信。

用 C 像機器一樣思考

從事許多有趣的 C 項目

許多有趣的項目,從大型數據庫服務器或操作系統內核,到小型嵌入式應用程序,您甚至可以在家里為您的個人滿意度和樂趣進行操作,都是用 C 語言完成的。沒有理由因為一個原因而停止做您可能喜歡的事情你不知道像 C 這樣古老而小巧但功能強大且經過時間驗證的編程語言。

使用 C 進行酷項目

結論

光明會不會統治世界。 C程序員做的。
鳴叫

C 編程語言似乎沒有到期日期。 它與硬件的接近性、出色的可移植性和資源的確定性使用使其成為操作系統內核和嵌入式軟件等低級開發的理想選擇。 它的多功能性、效率和良好的性能使其成為數據庫或 3D 動畫等高複雜性數據操作軟件的絕佳選擇。 今天的許多編程語言在預期用途上都比 C 更好,但這並不意味著它們在所有領域都擊敗了 C。 當性能是優先級時,C 仍然是無與倫比的。

世界在 C 驅動的設備上運行。 無論我們是否意識到,我們每天都在使用這些設備。 對於軟件的許多領域,C 是過去、現在,而且,據我們所知,仍然是未來。

相關:如何學習 C 和 C++ 語言:終極清單