一、C語言的起源
C 語言是貝爾實驗室的 Dennis Ritchie 于 1969 年 ~ 1973 年間創(chuàng)建的。美國國家標準學會(ANSI)在 1989 年頒布了 ANSI C 標準,后來 C 語言的標準化成了國際標準化組織(ISO)的責任。這些標準定義了 C 語言和一系列函數(shù)庫,即 C 標準庫。
- C 語言與 Unix 的關系
C 語言從一開始就是作為一種用于 Unix 系統(tǒng)的程序語言開發(fā)出來的。大部分 Unix 內(nèi)核,以及所有支撐工具和函數(shù)庫都是用 C 語言編寫的。
- C 語言小而簡單
C 語言的設計是由一個人而非一個協(xié)會掌控的,它是一個簡潔明了、沒有什么冗贅的設計。C 語言經(jīng)典著作 《The C Programming Language》(K & R)中包含大量例子和練習描述了完整的 C 語言及其標準庫,而全書還不到 300 頁。
- C 語言是為實踐目的設計的
二、C 語言概述
C 語言目前最新版本由 ISO/IEC 9899:2011 定義。當前版本一般稱為 C11,但是 C11 中一些語言元素是可選的,這就意味著遵循 C11 的編輯并沒有實現(xiàn)該變準中的所有功能。
C11 標準是 ISO/IEC 9899:2011 - Information technology -- Programming languages -- C 的簡稱,曾用名為 C1X。
C11 標準是 C 語言標準的第三版,前一個標準版本是 C99 標準。2011年12月8日,國際標準化組織(ISO)和國際電工委員會(IEC) 旗下的C語言標準委員會(ISO/IECJTC1/SC22/WG14)正式發(fā)布了 C11 標準。
2.1 標準庫
C 標準庫也在 C11 標準中指定,標準庫定義了編寫 C 程序時常用的常量、符號和函數(shù),還提供了一些 C 語言的一些可選擴展。標準庫在一系列標準文件:頭文件(.h)中指定。
三、創(chuàng)建 C 程序
C 程序的創(chuàng)建由以下四個步驟:
編輯
編譯
鏈接
執(zhí)行
下面就來詳細分析每個步驟:
3.1、編輯
編輯過程就是創(chuàng)建和修改 C 程序的源代碼。
3.2、編譯
編譯器將源代碼轉(zhuǎn)換為機器語言,在編譯過程中會找出并報告錯誤。該階段的輸入是在編輯期產(chǎn)生文件(源文件)。編譯器的輸出結果稱為對象代碼(object code),存放它們的文件稱為對象文件(object file),在 Linux/Unix 通常是.o。
下面是一個在 Linux 下編譯的示例:
hello.c 源文件:
#include <stdio.h>
int main()
{
printf("hello,world\n");
return 0;
}
編譯指令:
cc -c hello.c 或 gcc -c hello.c
gcc 指令的一般格式為:gcc [選項] 要編譯的文件 [選項] [目標文件]
以上指令中 hello.c 是需要編譯的源文件,生成的文件為 hello.o;如果省略了 -c 這個參數(shù),那么程序還會自動鏈接,生成的文件默認為 a.out。
編譯部分分為三個階段:
- 預處理階段
該階段會修改或添加代碼,預處理器(cpp)會根據(jù)以字符 # 開頭的命令,修改原始的 C 程序。比如在程序中第一行為 #include <stdio.h>,那么該命令告訴預處理器讀取頭文件 stdio.h 的內(nèi)容,并把它直接插入程序文本中。結果就得到另一個 C 程序,通常以 .i 作為文件擴展名。
指令如下:
gcc -E hello.c -o hello.i
- 編輯階段
該階段是生成對象代碼的實際編譯過程,編譯器(ccl)將 .i 文件翻譯成文本文件(文件擴展名為 .s),它包含一個匯編語言程序。
在這個階段中,gcc 首先要檢查代碼的規(guī)范性、是否有語法錯誤等,以確定代碼的實際要做的工作,在檢查無誤后,gcc 把代碼翻譯成匯編語言。用戶可以使用“-S”選項來進行查看,該選項只進行編譯而不進行匯編,生成匯編代碼。匯編語言是非常有用的,它為不同高級語言不同編譯器提供了通用的語言。
如:C 編譯器和 Fortran 編譯器產(chǎn)生的輸出文件用的都是一樣的匯編語言。
指令如下:
gcc -S hello.i -o hello.s
hello.s 文件內(nèi)容如下:
.file "hello.c"
.section .rodata
.LC0:
.string "hello,world"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
leaq .LC0(%rip), %rdi
call puts@PLT
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 7.2.0-8ubuntu3) 7.2.0"
.section .note.GNU-stack,"",@progbits
- 匯編階段
接下來匯編器(as)將 .s 文件翻譯成機器語言指令,把這些指令打包成一種叫做可重定位目標程序(relocatable object program)的格式,并將結果保存在 .o 文件中,該文件是一個二進制文件。
指令如下:
gcc –c hello.s –o hello.o
3.3、鏈接
鏈接器(ld)將原代碼文件中由編譯器產(chǎn)生的各種對象模塊組合起來,再從 C 語言提供的程序庫中添加必要的代碼模塊,將他們組合成一個可執(zhí)行文件。鏈接器也可以檢測和報告錯誤。如果連接成功會產(chǎn)生一個可執(zhí)行文件,在 Linux/Unix 環(huán)境下,該文件無擴展名。
指令如下:
gcc hello.o -o hello
以上幾個步驟可以使用以下指生成可執(zhí)行文件:
gcc -o hello hello.c
3.4、執(zhí)行
經(jīng)過以上幾個步驟,hello.c 已經(jīng)被編譯系統(tǒng)翻譯成成了可執(zhí)行目標文件 hello,并被保存在磁盤上。在 Linux/Unix 系統(tǒng)下只要在終端輸入可執(zhí)行文件名就可以執(zhí)行程序。
指令:
linux> ./hello
hello,world
以上步驟可以總結為下圖:

四、示例
使用前面的 hello.c 作為示例:
#include <stdio.h>
int main()
{
printf("Hello world!\n"); // print
return 0;
}
4.1、注釋
C語言的注釋分為兩種:
以“//”開頭的;
在“/** **/”之間的。
4.2、預處理指令
以下代碼行:
#include <stdio.h>
嚴格來說他并不是可執(zhí)行程序的一部分,但是它很重要,程序缺少它是不可以執(zhí)行的。符號“#”表示這是一個預處理指令,告訴編譯器在編譯源代碼之前,要先做些操作。以上介紹的編譯過程的預處理階段就是處理這些預處理指令的。預處理指令相當多,大多放在源程序文件的開頭部分。
注意:在一些系統(tǒng)中,頭文件名是不區(qū)分大小寫的,但在 #include 指令中,這些文件名通常是小寫的。
4.3、main() 函數(shù)
int main()
{
printf("Hello world!\n");
return 0;
}
每個 C 程序必須有一個 main() 函數(shù) —— 每個程序都是由這個函數(shù)開始執(zhí)行的。
五、預處理器
以上實例中展示了如何使用預處理指令 —— 把頭文件的內(nèi)容添加到源文件中。編譯的預處理階段做的工作遠不止這些。除了指令之外,源文件還可以包含宏。宏是提供給預處理器的指令,來添加或是修改程序中的語句。
宏可以很簡單,例如只定義一個符號:INDEX_FOOT,只要出現(xiàn)這個符號就使用 10 代替,如下所示:
# define INDEX_FOOT 10
宏也可以很復雜,根據(jù)特定條件可以將大量代碼添加到源文件中,例如在 Android 的 Binder 中,c 層代碼常使用宏添加代碼到源文件中,代碼如下所示:
#define DECLARE_META_INTERFACE(FregService)
static const android::String16 descriptor;
static android::sp<I##INTERFACE> asInterface(const android::sp<android::IBinder>& obj);
virtual const android::String16& getInterfaceDescriptor() const;
I##INTERFACE();
virtual ~I##INTERFACE();
在 IFregService.c 中展開后:
static const android::String16 descriptor; // 描述接口名稱
static android::sp<IFregService> asInterface(const android::sp<android::IBinder>& obj); // 將IBinder對象轉(zhuǎn)化為IFregService接口
virtual const android::String16& getInterfaceDescriptor() const;
IFregService(); // 構造函數(shù)
virtual ~IFregService(); // 析構函數(shù)