【嵌入式开发】gcc 学习笔记(一) - 编译C程序 及 编译过程(一)

一. C程序编译过程




编译过程简介 : C语言的源文件 编译成 可执行文件需要四个步骤, 预处理 (Preprocessing) 扩展宏, 编译 (compilation) 得到汇编语言, 汇编 (assembly) 得到机器码, 连接 (linking) 得到可执行文件;


-- 查看每个步骤的编译细节 : "-E" 对应 预处理, "-S" 对应 编译, "-c" 对应 汇编, "-O" 对应 连接;


-- 每个步骤对应的工具 : 预处理器 (CPP - The C Preprogressor), 编译器 (cc1), 汇编器 (as), 连接器 (ld);


-- 查看总体编译细节 : 使用 "-v" 参数, 可以查看总体编译细节;



octopus@octopus:~/test$ gcc -v main.c 
使用内建 specs。
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/i686-linux-gnu/4.6/lto-wrapper
目标:i686-linux-gnu
配置为:../src/configure -v --with-pkgversion='Ubuntu/Linaro 4.6.3-1ubuntu5' --with-bugurl=file:///usr/share/doc/gcc-4.6/README.Bugs --enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.6 --enable-shared --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.6 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-gnu-unique-object --enable-plugin --enable-objc-gc --enable-targets=all --disable-werror --with-arch-32=i686 --with-tune=generic --enable-checking=release --build=i686-linux-gnu --host=i686-linux-gnu --target=i686-linux-gnu
线程模型:posix
gcc 版本 4.6.3 (Ubuntu/Linaro 4.6.3-1ubuntu5) 
COLLECT_GCC_OPTIONS='-v' '-mtune=generic' '-march=i686'
 /usr/lib/gcc/i686-linux-gnu/4.6/cc1 -quiet -v -imultilib . -imultiarch i386-linux-gnu main.c -quiet -dumpbase main.c -mtune=generic -march=i686 -auxbase main -version -fstack-protector -o /tmp/ccUWUvbm.s
GNU C (Ubuntu/Linaro 4.6.3-1ubuntu5) 版本 4.6.3 (i686-linux-gnu)
    由 GNU C 版本 4.6.3 编译, GMP 版本 5.0.2,MPFR 版本 3.1.0-p3,MPC 版本 0.9
GGC 准则:--param ggc-min-expand=100 --param ggc-min-heapsize=131072
忽略不存在的目录“/usr/local/include/i386-linux-gnu”
忽略不存在的目录“/usr/lib/gcc/i686-linux-gnu/4.6/../../../../i686-linux-gnu/include”
#include "..." 搜索从这里开始:
#include <...> 搜索从这里开始:
 /usr/lib/gcc/i686-linux-gnu/4.6/include
 /usr/local/include
 /usr/lib/gcc/i686-linux-gnu/4.6/include-fixed
 /usr/include/i386-linux-gnu
 /usr/include
搜索列表结束。
GNU C (Ubuntu/Linaro 4.6.3-1ubuntu5) 版本 4.6.3 (i686-linux-gnu)
    由 GNU C 版本 4.6.3 编译, GMP 版本 5.0.2,MPFR 版本 3.1.0-p3,MPC 版本 0.9
GGC 准则:--param ggc-min-expand=100 --param ggc-min-heapsize=131072
Compiler executable checksum: 09c248eab598b9e2acb117da4cdbd785
COLLECT_GCC_OPTIONS='-v' '-mtune=generic' '-march=i686'
 as --32 -o /tmp/cciJfMAd.o /tmp/ccUWUvbm.s
COMPILER_PATH=/usr/lib/gcc/i686-linux-gnu/4.6/:/usr/lib/gcc/i686-linux-gnu/4.6/:/usr/lib/gcc/i686-linux-gnu/:/usr/lib/gcc/i686-linux-gnu/4.6/:/usr/lib/gcc/i686-linux-gnu/
LIBRARY_PATH=/usr/lib/gcc/i686-linux-gnu/4.6/:/usr/lib/gcc/i686-linux-gnu/4.6/../../../i386-linux-gnu/:/usr/lib/gcc/i686-linux-gnu/4.6/../../../../lib/:/lib/i386-linux-gnu/:/lib/../lib/:/usr/lib/i386-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/i686-linux-gnu/4.6/../../../:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-v' '-mtune=generic' '-march=i686'
 /usr/lib/gcc/i686-linux-gnu/4.6/collect2 --sysroot=/ --build-id --no-add-needed --as-needed --eh-frame-hdr -m elf_i386 --hash-style=gnu -dynamic-linker /lib/ld-linux.so.2 -z relro /usr/lib/gcc/i686-linux-gnu/4.6/../../../i386-linux-gnu/crt1.o /usr/lib/gcc/i686-linux-gnu/4.6/../../../i386-linux-gnu/crti.o /usr/lib/gcc/i686-linux-gnu/4.6/crtbegin.o -L/usr/lib/gcc/i686-linux-gnu/4.6 -L/usr/lib/gcc/i686-linux-gnu/4.6/../../../i386-linux-gnu -L/usr/lib/gcc/i686-linux-gnu/4.6/../../../../lib -L/lib/i386-linux-gnu -L/lib/../lib -L/usr/lib/i386-linux-gnu -L/usr/lib/../lib -L/usr/lib/gcc/i686-linux-gnu/4.6/../../.. /tmp/cciJfMAd.o -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/gcc/i686-linux-gnu/4.6/crtend.o /usr/lib/gcc/i686-linux-gnu/4.6/../../../i386-linux-gnu/crtn.o




1. 预处理





预处理命令 : 源程序中 以 "#" 开头的命令是 预处理命令, 如 "#include", "#define", "ifndef" 等;




预处理过程 : 预处理将 include 的文件插入到 源文件中, 展开 define 宏定义, 根据条件 编译代码;




编译下面的源程序 :



/*************************************************************************
    > File Name: main.c
    > Author: octopus
    > Mail: octopus_work.163.com 
    > Created Time: 2014年04月30日 星期三 17时31分08秒
 ************************************************************************/
#include<stdio.h>
#define NUM 5
int main(int argc, char **argv)
{
    printf("Hello World ! num = %d \n", NUM);
    return 0;
}




预处理结果 : 预处理 源程序 产生的结果会放到 ".i" 后缀的文件中, 默认情况下 ".i" 后缀文件是不写到磁盘中的, 如果加上 "-save-temps" 参数, 就会将所有的中间文件都保存到磁盘中;


-- 分析下面的例子 : 使用 gcc -save-temps main.c 命令编译源程序, 所有的中间文件都会保留, main.i 是预处理结果, main.s 是编译结果, main.o 是汇编结果, a.out 是连接生成的可执行文件;



octopus@octopus:~/test$ ls
main.c
octopus@octopus:~/test$ gcc -save-temps main.c
octopus@octopus:~/test$ ls
a.out  main.c  main.i  main.o  main.s
octopus@octopus:~/test$ ./a.out 
Hello World ! num = 5




查看预处理细节 : 使用 gcc -E mian.c 命令, 会输出编译细节, 打印出上千行, 这里只贴出部分;



octopus@octopus:~/test$ gcc -E main.c 
# 1 "main.c"
# 1 "<built-in>"
# 1 "<命令行>"
# 1 "main.c"


...
# 1 "/usr/include/stdio.h" 1 3 4
# 28 "/usr/include/stdio.h" 3 4
# 1 "/usr/include/features.h" 1 3 4
# 324 "/usr/include/features.h" 3 4
...
# 9 "main.c" 2
int main(int argc, char **argv)
{
 printf("Hello World ! \n");
 return 0;
}



在 gcc 命令行中进行宏定义 : 使用 gcc -DNUM=5 main.c 命令, 在程序中就可以使用 NUM 宏定义了, "-DNUM" 相当于在程序中定义了 "#define NUM 5";


-- main.c 内容 :



/*************************************************************************
    > File Name: main.c
    > Author: octopus
    > Mail: octopus_work.163.com 
    > Created Time: 2014年04月30日 星期三 17时31分08秒
 ************************************************************************/
#include<stdio.h>
int main(int argc, char **argv)
{
    printf("Hello World ! num = %d \n", NUM);
    return 0;
}
-- 编译过程 : 
octopus@octopus:~/test$ gcc -DNUM=5 main.c 
octopus@octopus:~/test$ ./a.out 
Hello World ! num = 5



2. 编译



编译流程 : 编译器在编译阶段依次执行 词法分析, 语法分析, 代码优化, 存储分配, 代码生成 五个步骤;


-- 多次扫描方案 : 编译器每次扫描代码只完成一项工作, 如 第一次扫描 只进行词法分析, 第二次扫描进行 语法分析, 扫描多次完成上面的五个步骤;




生成中间的汇编中间文件 : 使用 gcc -S main.c 编译上面的 main.c 源程序, 可以得到 mian.s 汇编语言文件, 这是产生的中间汇编程序;


-- 编译过程 及 结果 :



octopus@octopus:~/test$ gcc -S main.c 
octopus@octopus:~/test$ cat main.s 
    .file   "main.c"
    .section    .rodata
.LC0:
    .string "Hello World ! num = %d \n"
    .text
    .globl  main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    andl    $-16, %esp
    subl    $16, %esp
    movl    $.LC0, %eax
    movl    $5, 4(%esp)
    movl    %eax, (%esp)
    call    printf
    movl    $0, %eax
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
    .ident  "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3"
    .section    .note.GNU-stack,"",@progbits
octopus@octopus:~/test$



3. 汇编



汇编过程 : 汇编 就是将 汇编语言代码 翻译成 机器码, 也就是 ".o" 后缀的对象文件, 该过程 使用 汇编器 as 实现;




获取中间文件 : "-c" 选项可以保留 汇编过程中的 ".o" 后缀的中间文件, 使用 gcc -c main.c 命令, 可以获得 main.o 对象文件;



octopus@octopus:~/test$ ls
main.c
octopus@octopus:~/test$ gcc -c main.c 
octopus@octopus:~/test$ ls
main.c  main.o


4. 连接



链接过程 : 使用 ld 连接器, 将 汇编 过程中生成的 ".o" 对象文件, 与其它 对象文件 和 库文件连接起来, 生成可执行的二进制文件;




连接示例 : 使用 gcc main.o 将汇编过程生成的对象文件 main.o , 生成可执行文件 a.out ;



octopus@octopus:~/test$ gcc main.o 
octopus@octopus:~/test$ ./a.out 
Hello World ! num = 5
上一篇:【UML 建模】UML建模语言入门-视图,事物,关系,通用机制(一)


下一篇:【C 语言】结构体相关 的 函数 指针 数组(二)