一、C语言的起源
C 语言是贝尔实验室的 Dennis Ritchie 于 1969 年 ~ 1973 年间创建的。美国国家标准学会(ANSI)在 1989 年颁布了 ANSI C 标准,后来 C 语言的标准化成了国际标准化组织(ISO)的责任。这些标准定义了 C 语言和一系列函数库,即 C 标准库。
- C 语言与 Unix 的关系
C 语言从一开始就是作为一种用于 Unix 系统的程序语言开发出来的。大部分 Unix 内核,以及所有支撑工具和函数库都是用 C 语言编写的。
- C 语言小而简单
C 语言的设计是由一个人而非一个协会掌控的,它是一个简洁明了、没有什么冗赘的设计。C 语言经典著作 《The C Programming Language》(K & R)中包含大量例子和练习描述了完整的 C 语言及其标准库,而全书还不到 300 页。
- C 语言是为实践目的设计的
二、C 语言概述
C 语言目前最新版本由 ISO/IEC 9899:2011 定义。当前版本一般称为 C11,但是 C11 中一些语言元素是可选的,这就意味着遵循 C11 的编辑并没有实现该变准中的所有功能。
C11 标准是 ISO/IEC 9899:2011 - Information technology -- Programming languages -- C 的简称,曾用名为 C1X。
C11 标准是 C 语言标准的第三版,前一个标准版本是 C99 标准。2011年12月8日,国际标准化组织(ISO)和国际电工委员会(IEC) 旗下的C语言标准委员会(ISO/IECJTC1/SC22/WG14)正式发布了 C11 标准。
2.1 标准库
C 标准库也在 C11 标准中指定,标准库定义了编写 C 程序时常用的常量、符号和函数,还提供了一些 C 语言的一些可选扩展。标准库在一系列标准文件:头文件(.h)中指定。
三、创建 C 程序
C 程序的创建由以下四个步骤:
编辑
编译
链接
执行
下面就来详细分析每个步骤:
3.1、编辑
编辑过程就是创建和修改 C 程序的源代码。
3.2、编译
编译器将源代码转换为机器语言,在编译过程中会找出并报告错误。该阶段的输入是在编辑期产生文件(源文件)。编译器的输出结果称为对象代码(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 这个参数,那么程序还会自动链接,生成的文件默认为 a.out。
编译部分分为三个阶段:
- 预处理阶段
该阶段会修改或添加代码,预处理器(cpp)会根据以字符 # 开头的命令,修改原始的 C 程序。比如在程序中第一行为 #include <stdio.h>,那么该命令告诉预处理器读取头文件 stdio.h 的内容,并把它直接插入程序文本中。结果就得到另一个 C 程序,通常以 .i 作为文件扩展名。
指令如下:
gcc -E hello.c -o hello.i
- 编辑阶段
该阶段是生成对象代码的实际编译过程,编译器(ccl)将 .i 文件翻译成文本文件(文件扩展名为 .s),它包含一个汇编语言程序。
在这个阶段中,gcc 首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,gcc 把代码翻译成汇编语言。用户可以使用“-S”选项来进行查看,该选项只进行编译而不进行汇编,生成汇编代码。汇编语言是非常有用的,它为不同高级语言不同编译器提供了通用的语言。
如:C 编译器和 Fortran 编译器产生的输出文件用的都是一样的汇编语言。
指令如下:
gcc -S hello.i -o hello.s
hello.s 文件内容如下:
.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)将原代码文件中由编译器产生的各种对象模块组合起来,再从 C 语言提供的程序库中添加必要的代码模块,将他们组合成一个可执行文件。链接器也可以检测和报告错误。如果连接成功会产生一个可执行文件,在 Linux/Unix 环境下,该文件无扩展名。
指令如下:
gcc hello.o -o hello
以上几个步骤可以使用以下指生成可执行文件:
gcc -o hello hello.c
3.4、执行
经过以上几个步骤,hello.c 已经被编译系统翻译成成了可执行目标文件 hello,并被保存在磁盘上。在 Linux/Unix 系统下只要在终端输入可执行文件名就可以执行程序。
指令:
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>
严格来说他并不是可执行程序的一部分,但是它很重要,程序缺少它是不可以执行的。符号“#”表示这是一个预处理指令,告诉编译器在编译源代码之前,要先做些操作。以上介绍的编译过程的预处理阶段就是处理这些预处理指令的。预处理指令相当多,大多放在源程序文件的开头部分。
注意:在一些系统中,头文件名是不区分大小写的,但在 #include 指令中,这些文件名通常是小写的。
4.3、main() 函数
int main()
{
printf("Hello world!\n");
return 0;
}
每个 C 程序必须有一个 main() 函数 —— 每个程序都是由这个函数开始执行的。
五、预处理器
以上实例中展示了如何使用预处理指令 —— 把头文件的内容添加到源文件中。编译的预处理阶段做的工作远不止这些。除了指令之外,源文件还可以包含宏。宏是提供给预处理器的指令,来添加或是修改程序中的语句。
宏可以很简单,例如只定义一个符号:INDEX_FOOT,只要出现这个符号就使用 10 代替,如下所示:
# define INDEX_FOOT 10
宏也可以很复杂,根据特定条件可以将大量代码添加到源文件中,例如在 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对象转化为IFregService接口
virtual const android::String16& getInterfaceDescriptor() const;
IFregService(); // 构造函数
virtual ~IFregService(); // 析构函数