参考:
从hello.c到可执行文件hello
在Unix系统中,从源文件到目标文件的转化是由编译器驱动程序完成的:
root> gcc -o hello hello.c
这个转化可以分为4个阶段,执行这4个阶段的预处理器、编译器、汇编器和链接器一起构成了编译系统compilation system。
1)预处理阶段:cpp根据#开头的命令,修改原始程序。比如#include
2)编译阶段:编译器ccl将文本翻译成汇编语言。汇编语言是一个通用的低级机器语言指令。输出结果以.s后缀
3)汇编阶段:汇编器as将汇编语言翻译成机器语言,并且打包成可重定位目标程序relocatable object program的格式,输出结果以.o后缀
4)链接阶段:因为hello调用了printf函数,而它是标准C库的一个函数,因而有一个名为printf.o的目标程序,因此需要使用链接器合并,最终变成可执行文件。
注意:
1)一般UNIX系统中目标文件名后缀是.o,但MS-DOS中则是.obj
2)UNIX系统中,如果编译的源文件只有一个,中间产生的.o文件会在产生完可执行文件.out后被自动删除;但如果源文件有多个,不会删除,这样的话如果改动某个源程序,编译器会只重新编译改动过的,后面再一起链接。MS-DOS在单个源文件时也不会删除目标文件
linux键入./hello
1)键盘键入后,USB控制器将输入通过I/O总线经过I/O桥读入寄存器,然后再经过I/O桥、内存总线存入内存。
2)当键入回车时,shell知道输入结束了,随后加载可执行的hello文件,这些指令将代码和数据从磁盘复制到主存(使用直接存储器存取DMA技术能将数据直接从磁盘复制到主存)
3)代码和数据加载完成后,CPU开始执行指令,最后将输出“hello, world\n”从主存复制到寄存器文件,再经过总线接口、I/O桥到达显示器
GNU项目
GNU项目(GNU’s Not Unix,1984年提出)已经开发出一个包含Unix操作系统的所有主要部件的环境,但内核除外(由Linux项目独立发展)。GNU环境包括EMACS编辑器、GCC编译器(GNU Compiler Collection)、GDB调试器、汇编器、链接器、处理二进制文件的工具喝其他一些不见。
GCC可以使用不同版本的C语言编译程序,参数是-std=xx
C版本 | GCC命令行值 |
---|---|
GNU89 | 无,-std=gnu89 |
ANSI, ISO C90 | -ansi, -std=c89 |
ISO C99 | -std=c99 |
ISO C11 | -std=c11 |
C90被称为C89是因为它的标准化工作是从1989年开始的。
ANSI C的任何一种实现中,都有两种环境。一是翻译环境translation environment,即将源代码转换为可执行的机器指令;二是执行环境execution environment,用于实际执行代码。这两种环境不必在同一台机器上。交叉编译器cross compiler就是在一台机器上编译,但是产生的可执行代码可以在不同机器使用。独立环境freestanding environment是指不存在操作系统的环境,比如嵌入式系统(微波炉控制器)
字符
三字母词trigraph:原本是为了减少字符集规模,不过其实不太常用。
#include
#include
void main() {
printf("??) ??( ??! ??< ??> ??' ??= ??/ ??-");
}
编译+执行。默认是不开启的,gcc要使用-trigraphs开启
gcc -o hello hello.c -trigraphs
./hello
] [ | { } ^ # ~
注释
如果需要注释掉一段代码,如果代码里有长段注释,可能/**/的效果不好,此时可以考虑#if和#endif,这样更安全
#if 0
statements //要注释掉的代码
#endif
C中只有4种基本数据类型:整型、浮点、指针和聚合类型(如array和struct)。
整型包括:字符、短整型、整型和长整型。都有signed有符号和unsigned无符号两种版本。
标准只规定了长整型至少应该跟整型一样长,整型至少应该跟短整型一样长。因此,缺省的int是16位还是32位,通常是由编译器决定的。一般来说是字长。limits.h定义了不同的整数类型的最大最小值。
char本质是小整型值,缺省的char可能是signed char或者unsigned char,所以为了可移植起见,char变量的值应该在两者的交集中(比如ASCII),也可以显式声明(某些机器处理signed char更快,但是一些库函数的参数声明是char,显式声明可能有兼容性问题)。
int ch;
while( (ch = getchar()) != EOF && ch != '\n' );
为什么要把ch声明为int?EOF是一个整型值,如果使用char可能导致EOF解释错误
在整型字面值后添加L或l可以让整数被解释long,U或u则可以解释为unsigned,UL也可以组合
十进制整型字面值在缺省情况下,它的类型是能容纳这个值的最短类型。八进制需要以0开头,十六进制需要以0x开头。当然,使用\转义的时候,八进制格式为\ddd,十六进制格式为\xddd(此时算字符常量,所以输出是字符)
printf("\127\n"); // W
printf("\x00f\n"); // 如果数值较大会报错
宽字符常量wide character literal:当运行时环境支持一种宽字符集时,可以使用。比如使用Unicode字符集
#include
...
wchar_t c = L'Xs';
MessageBoxW(0, L"你好世界", L"I am", 0); // MessageBoxW默认以宽字符处理
枚举enumerated类型
枚举类型的值是符号常量,不是字符串。声明枚举类型的时候,实际就给符号名赋予了整数值,如下面的CUP实际就是0,PINT是1,以此类推。
enum Jar_Type {CUP, PINT, QUART, GALLON}; // 声明类型
enum Jar_Type milk_jug; // 声明枚举类型的变量
当然也可以给部分符号赋值,对于没被显式赋值的符号,就是前一个符号名的值+1
enum Jar_Type {CUP = 8, PINT, QUART, GALLON}; // 声明类型
enum Jar_Type milk_jug = PINT; // 声明枚举类型的变量
printf("%d", milk_jug); // 9
浮点数
包括float、double和long double,它们的范围存储在float.h中。浮点数必须至少有一个小数点或者指数(E/e),浮点数字面值缺省情况下是double,除非后跟L/l表示是long double,或者后跟F/f表示是float。