一、信息就是位 + 上下文
作者使用的標題是:信息就是位 + 上下文,那么問題來了:什么是位?什么是上下文?
計算機系統是由硬件和系統軟件組成的,它們共同工作來運行應用程序。所有計算機系統都有相似的硬件和軟件組件,它們執行着相似的功能。
從某種意義上來說,本書的目的就是要幫助你了解當你在系統上執行 hello 程序時,系統發生了什么以及為什么會這樣。
// hello 程序
#include <stdio.h>
int main()
{
printf("hello, world\n");
}
hello 程序的生命周期是從一個源程序(或者說源文件)開始的,即程序員利用編輯器創建並保存的文本文件,文件名是 hello.c。源程序實際上就是一個由值 0 和 1 組成的位(bit)序列,8 個位被組織成一組,稱為字節。每個字節表示程序中某個文本字符。
hello 源程序是文本編輯器編寫的一個文件,使用 HxD (免費的十六進制和磁盤數據編輯器)對源文件進行源碼查看:

的字節序列可能表示一個整數、浮點數、字符串或者機器指令。
構成計算機信息存儲單元是 bit(位),如果以 1 位為單位記錄信息,未免有點低效。如果采用 8 個位為一個字節單位,可以組合出 256 種不同的符號表示,這樣傳輸也方便,也便於計算機存儲信息。
知道位的概念還不夠,計算機對當前的某一個位中的信息可以通過字符編碼規則得知,但是文本文件除外,還有純二進制的文件(圖片、音視頻),計算機如何得知某幾個位連接起來是一串有意義的數據,並知道從哪里截取開始到哪里結束,這就需要通過上下文來得知。
二、ASCII 碼
ASCII(American Standard Code for Information Interchange,美國標准信息交換代碼)是基於拉丁字母的一套電腦編碼系統,主要用於顯示現代英語和其他西歐語言。它是現今最通用的單字節編碼系統,並等同於國際標准ISO/IEC 646。
計算機里,一個字節有 8 個 bit(位),用二進制(1 和 0)組合表示,則有 2 的 8 次方種可能數字,從零開始計數,則為 0 - 255,早期的計算機科學家們以為這 256 種數字夠表示日常使用的符號信息了(英文字母,阿拉伯數字,控制字符等)。
-
第一部分:ASCII 非打印控制字符表
ASCII 表上的數字 0–31 分配給了控制字符,用於控制像打印機等一些外圍設備。例如,12 代表換頁/新頁功能。此命令指示打印機跳到下一頁的開頭。
-
第二部分:ASCII 打印字符
數字 32–126 分配給了能在鍵盤上找到的字符,當您查看或打印文檔時就會出現。數字 127 代表 DELETE 命令。
-
第三部分:擴展 ASCII 打印字符
擴展的 ASCII 字符滿足了對更多字符的需求。擴展的 ASCII 包含 ASCII 中已有的 128 個字符(數字 0–32 顯示在下圖中),又增加了 128 個字符,總共是 256 個。
ASCII 碼表有它的局限性:隨着全世界人都在使用計算機,而全世界使用符號的種類遠超過了 256 種,因此 ASCII 碼表已經裝不下更多的符號信息(非英語國家的文字符號信息:拉丁文、中文、日韓文等),於是計算機科學家們又規定出了 Unicode(中文譯為:萬國碼、國際碼、統一碼、單一碼),即全球通用字符集的標准。
Unicode 標准規定:使用兩個字節單位表示一個數字,因此可以表示符號的種類為 2 的16 次方,也就是 65536 種,這樣全世界人使用的符號信息都可以囊括。
值得注意的是 Unicode 是一種計算機工業標准,而不是指定是什么字符集實現,它涵兼容了 ASCII 碼表規則,因此目前所有的計算都會至少認識 ASCII 碼表規則。
現在國際常用的UTF-8 編碼就是遵循了 Unicode 標准的一種字符集實現。
三、程序被其他程序翻譯成不同的格式
hello.c 是肉眼可直接識別的文本文件,但作為機器來說,它只認識二進制數字(物理邏輯上對應的是:不同大小電流或高低的電壓),文件均附生在操作系統之上,我們需要一套完整的流程將源文件翻譯成機器可以直接看懂並執行的可執行文件,執行這四個階段的程序(預處理器、編譯器、匯編器和鏈接器)一起構成了編譯系統(compilation system)。

使用 gcc++ 編譯工具編譯 hello.c 源文件:

gcc 常用編譯命令
1. 無選項編譯鏈接
用法:gcc hello.c
作用:將 hello.c 預處理、匯編、編譯並鏈接形成可執行文件。這里未指定輸出文件,默認輸出為a.out
。2. 選項 -o
用法:gcc hello.c -o hello
作用:將 hello.c 預處理、匯編、編譯並鏈接形成可執行文件 hello。-o 選項用來指定輸出文件的文件名。3. 選項 -E
用法:gcc -E hello.c -o hello.i
作用:將 hello.c 預處理輸出 hello.i 文件。4. 選項 -S
用法:gcc -S hello.i
作用:將預處理輸出文件 hello.i 匯編成 hello.s 文件。5. 選項 -c
用法:gcc -c hello.s
作用:將匯編輸出文件 hello.s 編譯輸出 hello.o 文件。6. 無選項鏈接
用法:gcc hello.o -o hello
作用:將編譯輸出文件test.o鏈接成最終可執行文件test。7. 選項 -O
用法:gcc -O1 hello.c -o hello
作用:使用編譯優化級別1編譯程序。級別為1~3,級別越大優化效果越好,但編譯時間越長。
-
預處理階段。預處理器(cpp)根據以字符 # 開頭的命令,修改原始的 C 程序。比如 hello.c 中第 1 行的 #include 命令告訴預處理器讀取系統頭文件 stdio.h 的內容,並把它直接插入到程序文本中。結果就得到了另一個 C 程序,通常是以 .i 作為文件擴展名。
-
編譯階段。編譯器(cc1)將文本文件 hello.i 翻譯成文本文件 hello.s,它包含一個匯編語言程序。匯編語言程序中的每條語句都以一種標准的文本格式確切地描述了一條低級機器語言指令。匯編語言是非常有用的,因為它為不同高級語言的不同編譯器提供了通用的輸出語言。例如,C 編譯器和 Fortran 編譯器產生的輸出文件用的都是一樣的匯編語言。
-
匯編階段。接下來,匯編器(as)將 hello.s 翻譯成機器語言指令,把這些指令打包成一種叫做可重定位目標程序(relocatable object program)的格式,並將結果保存在目標文件 hello.o 中。hello.o 文件是一個二進制文件,它的字節編碼是機器語言指令而不是字符。如果我們在文本編輯器中打開 hello.o 文件,看到的將是一堆亂碼。
-
鏈接階段。請注意,hello 程序調用了 printf 函數,它是每個 C 編譯器都會提供的標准 C 庫中的一個函數。printf 函數存在於一個名為 printf.o 的單獨的預編譯好了的目標文件中,而這個文件必須以某種方式合並到我們的 hello.o 程序中。鏈接器(ld)就負責處理這種合並。結果就得到 hello 文件,它是一個可執行目標文件(或者簡稱為可執行文件),可以被加載到內存中,由系統執行。