by falcon <zhangjinw@gmail.com>
平时在Linux下写代码,直接用"gcc -o out
in.c"就把代码编译好了,但是这后面到底做了什么事情呢?如果学习过编译原理则不难理解,一般高级语言程序编译的过程莫过于:预处理、编译、汇编、链
接。gcc在后台实际上也经历了这几个过程,我们可以通过-v参数查看它的编译细节,如果想看某个具体的编译过程,则可以分别使用-E,-S,-c和-
O,对应的后台工具则分别为cpp,cc1,as,ld。下面我们将逐步分析这几个过程以及相关的内容,诸如语法检查、代码调试、汇编语言等。
1、预处理
开篇简述:预处理是C语言程序从源代码变成可执行程序的第一步,主要是C语言编译器对各种预处理命令进行处理,包括头文件的包含、宏定义的扩展、条件编译的选择等。
以前没怎么“深入”预处理,脑子对这些东西总是很模糊,只记得在编译的基本过程(词法分析、语法分析)之前还需要对源代码中的宏定义、文件包含、条件编译
等命令进行处理。这三类的指令很常见,主要有#define, #include和#ifdef ...
#endif,要特别地注意它们的用法。(更多预处理的指令请查阅相关资料)
#define除了可以独立使用以便灵活设置一些参数外,还常常和#ifdef ...
#endif结合使用,以便灵活地控制代码块的编译与否,也可以用来避免同一个头文件的多次包含。关于#include貌似比较简单,通过man找到某个
函数的头文件,copy进去,加上<>就okay。这里虽然只关心一些技巧,不过预处理还是蕴含着很多潜在的陷阱(可参考<C
Traps & Pitfalls>),我们也需要注意的。下面仅介绍和预处理相关的几个简单内容。
打印出预处理之后的结果:gcc -E hello.c
这样我们就可以看到源代码中的各种预处理命令是如何被解释的,从而方便理解和查错。
实际上gcc在这里是调用了cpp的(虽然我们通过gcc的-v仅看到cc1),cpp即The C Preprocessor,主要用来预处理宏定义、文件包含、条件编译等。下面介绍它的一个比较重要的选项-D。
在命令行定义宏:gcc -Dmacro hello.c
这个等同于在文件的开头定义宏,即#define maco,但是在命令行定义更灵活。例如,在源代码中有这些语句。
#ifdef DEBUG
printf("this code is for debugging\n");
#endif
如果编译时加上-DDEBUG选项,那么编译器就会把printf所在的行编译进目标代码,从而方便地跟踪该位置的某些程序状态。这样-DDEBUG就可以当作一个调试开关,编译时加上它就可以用来打印调试信息,发布时则可以通过去掉该编译选项把调试信息去掉。
本节参考资料:
[1] C语言教程第九章:预处理
http://www.bc-cn.net/Article/kfyy/cyy/jc/200409/9.html
[2] 更多
http://www.hemee.com/kfyy/c/6626.html
http://www.91linux.com/html/article/program/cpp/20071203/8745.html
http://www.janker.org/bbs/programmer/2006-10-13/327.html
2、编译(翻译)
开篇简要:编译之前,C语言编译器会进行词法分析、语法分析(-fsyntax-only),接着会把源代码翻译成中间语言,即汇编语言。如果想看到这个
中间结果,可以用-S选项。需要提到的是,诸如shell等解释语言也会经历一个词法分析和语法分析的阶段,不过之后并不会进行“翻译”,而是“解释”,
边解释边执行。
把源代码翻译成汇编语言,实际上是编译的整个过程中的第一个阶段,之后的阶段和汇编语言的开发过程没有什么区别。这个阶段涉及到对源代码的词法分析、语法检查(通过-std指定遵循哪个标准),并根据优化(-O)要求进行翻译成汇编语言的动作。
如果仅仅希望进行语法检查,可以用-fsyntax-only选项;而为了使代码有比较好的移植性,避免使用gcc的一些特性,可以结合-std和-
pedantic(或者-pedantic-erros)选项让源代码遵循某个C语言标准的语法。这里演示一个简单的例子。
Quote: |
$ cat hello.c #include <stdio.h> int main() { printf("hello, world\n") return 0; } $ gcc -fsyntax-only hello.c hello.c: In function ‘main’: hello.c:5: error: expected ‘;’ before ‘return’ $ vim hello.c $ cat hello.c #include <stdio.h> int main() { printf("hello, world\n"); int i; return 0; } $ gcc -std=c89 -pedantic-errors hello.c #默认情况下,gcc是允许在程序中间声明变量的,但是turboc就不支持 hello.c: In function ‘main’: hello.c:5: error: ISO C90 forbids mixed declarations and code
|
语法错误是程序开发过程中难以避免的错误(人的大脑在很多条件下都容易开小差),不过编译器往往能够通过语法检查快速发现这些错误,并准确地告诉你语法错
误的大概位置。因此,作为开发人员,要做的事情不是“恐慌”(不知所措),而是认真阅读编译器的提示,根据平时积累的经验(最好在大脑中存一份常见语法错
误索引,很多资料都提供了常见语法错误列表,如<C
Traps&Pitfalls>和最后面的参考资料[12]也列出了很多常见问题)和编辑器提供的语法检查功能(语法加亮、括号匹配提示
等)快速定位语法出错的位置并进行修改。
语法检查之后就是翻译动作,gcc提供了一个优化选项-O,以便根据不同的运行平台和用户要求产生经过优化的汇编代码。例如,
Quote: |
$ gcc -o hello hello.c #采用默认选项,不优化 $ gcc -O2 -o hello2 hello.c #优化等次是2 $ gcc -Os -o hellos hello.c #优化目标代码的大小 $ ls -S hello hello2 hellos #可以看到,hellos比较小,hello2比较大 hello2 hello hellos $ time ./hello hello, world
real 0m0.001s user 0m0.000s sys 0m0.000s $ time ./hello2 #可能是代码比较少的缘故,执行效率看上去不是很明显 hello, world
real 0m0.001s user 0m0.000s sys 0m0.000s
$ time ./hellos #虽然目标代码小了,但是执行效率慢了些 hello, world
real 0m0.002s user 0m0.000s sys 0m0.000s
|
根据上面的简单演示,可以看出gcc有很多不同的优化选项,主要看用户的需求了,目标代码的大小和效率之间貌似存在一个“纠缠”,需要开发人员自己权衡。
下面我们通过-S选项来看看编译出来的中间结果,汇编语言,还是以之前那个hello.c为例。
Quote: |
$ gcc -S hello.c #默认输出是hello.s,可自己指定,输出到屏幕-o -,输出到其他文件-o file $ cat hello.s cat hello.s .file "hello.c" .section .rodata .LC0: .string "hello, world" .text .globl main .type main, @function main: leal 4(%esp), %ecx andl $-16, %esp pushl -4(%ecx) pushl %ebp movl %esp, %ebp pushl %ecx subl $4, %esp movl $.LC0, (%esp) call puts movl $0, %eax addl $4, %esp popl %ecx popl %ebp leal -4(%ecx), %esp ret .size main, .-main .ident "GCC: (GNU) 4.1.3 20070929 (prerelease) (Ubuntu 4.1.2-16ubuntu2)" .section .note.GNU-stack,"",@progbits
|
不知道看出来没?和我们在课堂里学的intel的汇编语法不太一样,这里用的是AT&T语法格式。如果之前没接触过AT&T的,可以看看
参考资料[2]。如果想学习Linux下的汇编语言开发,从下一节开始哦,下一节开始的所有章节基本上覆盖了Linux下汇编语言开发的一般过程,不过这
里不介绍汇编语言语法。
这里需要补充的是,在写C语言代码时,如果能够对编译器比较熟悉(工作原理和一些细节)的话,可能会很有帮助。包括这里的优化选项(有些优化选项可能在汇
编时采用)和可能的优化措施,例如字节对齐(可以看看这本书"Linux_Assembly_Language_Programming"的第六小节)、
条件分支语句裁减(删除一些明显分支)等。
本节参考资料
[1] Guide to Assembly Language Programming in Linux(pdf教程,社区有下载)
http://oss.lzu.edu.cn/modules/wfdownloads/singlefile.php?cid=5&lid=94
[2] Linux汇编语言开发指南(在线):
http://www.ibm.com/developerworks/cn/linux/l-assembly/index.html
[3] PowerPC 汇编
http://www.ibm.com/developerworks/cn/linux/hardware/ppc/assembly/index.html
[4] 用于 Power 体系结构的汇编语言
http://www.ibm.com/developerworks/cn/linux/l-powasm1.html
[5] Linux Assembly HOWTO
http://mirror.lzu.edu.cn/tldp/HOWTO/Assembly-HOWTO/
[6] Linux 中 x86 的内联汇编
http://www.ibm.com/developerworks/cn/linux/sdk/assemble/inline/index.html
[7] Linux Assembly Language Programming
http://mirror.lzu.edu.cn/doc/incoming/ebooks/linux-unix/Linux_EN_Original_Books