兴海北路

---男儿仗剑自横行
<2008年3月>
2425262728291
2345678
9101112131415
16171819202122
23242526272829
303112345

统计

  • 随笔 - 85
  • 文章 - 0
  • 评论 - 17
  • 引用 - 0

常用链接

留言簿(6)

随笔分类

随笔档案

收藏夹

全是知识啊

搜索

  •  

最新评论

阅读排行榜

评论排行榜

GCC编译背后(第一部分:预处理和编译)
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

  • posted on 2008-03-14 15:22 随意门 阅读(891) 评论(0)  编辑 收藏 引用


    只有注册用户登录后才能发表评论。
    网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理