gcc 学习笔记(一) - 编译C程序 及 编译过程

一. C程序编译过程

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

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

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

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

[email protected]:~/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 是连接生成的可执行文件;

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

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

[email protected]:~/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;
}

-- 编译过程 :

[email protected]:~/test$ gcc -DNUM=5 main.c
[email protected]:~/test$ ./a.out
Hello World ! num = 5

2. 编译

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

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

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

-- 编译过程 及 结果 :

[email protected]:~/test$ gcc -S main.c
[email protected]:~/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
[email protected]:~/test$

3. 汇编

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

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

[email protected]:~/test$ ls
main.c
[email protected]:~/test$ gcc -c main.c
[email protected]:~/test$ ls
main.c  main.o

4. 连接

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

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

[email protected]:~/test$ gcc main.o
[email protected]:~/test$ ./a.out
Hello World ! num = 5

二. 编译C程序

1. 编译单个C程序

C语言程序示例 : 简单的Hello World;

/*************************************************************************
    > File Name: main.c
    > Author: octopus
    > Mail: octopus_work.163.com
    > Created Time: 2014年04月19日 星期六 16时22分26秒
 ************************************************************************/

#include<stdio.h>

int main(int argc, char **argv)
{
	printf("Hello World! \n");
	return 0;
}

简单编译 : 使用 gcc main.c 命令, 会生成 a.out 可执行文件, 使用 ./a.out 可以执行编译好的C程序;

[email protected]:~/gcc$ gcc main.c
[email protected]:~/gcc$ ./a.out
Hello World! 

指定输出文件编译 : 如果不想使用 a.out 作为输出文件, 可以使用 -o 参数指定输出文件, 如果该文件存在就会覆盖;

-- 命令 : gcc main.c -o main;

[email protected]:~/gcc$ gcc main.c -o main
[email protected]:~/gcc$ ./main
Hello World!

显示警告选项 : -Wall 选项, 可以在编译的时候, 将警告信息输出到终端中;

-- 编译输出警告信息 : gcc -Wall main.c;

人为制造警告 : 在 printf 输出的时候, 使用 %s 作为一个 int 数据的占位符;

/*************************************************************************
    > File Name: main.c
    > Author: octopus
    > Mail: octopus_work.163.com
    > Created Time: 2014年04月19日 星期六 16时22分26秒
 ************************************************************************/

#include<stdio.h>

int main(int argc, char **argv)
{
	printf("Hello World!  num = %s\n", 4);
	return 0;
}

-- 执行编译 : gcc -Wall main.c, 编译的时候报出警告, 但是编译通过, 但是运行的时候就出错了;

[email protected]:~/gcc$ gcc main.c
main.c: 在函数‘main’中:
main.c:12:2: 警告: 格式 ‘%s’ expects argument of type ‘char *’, but argument 2 has type ‘int’ [-Wformat]
[email protected]:~/gcc$ ./a.out
段错误 (核心已转储)

2. 编译多个文件

由三个文件组成的程序 : kill.h, kill.c, main.c, 其中 main.c 是主函数入口, 调用 kill.c 定义的方法;

-- kill.h 内容 : 声明 kill 方法, 引用了该头文件, 即可使用 kill 方法;

/*************************************************************************
    > File Name: kill.h
    > Author: octopus
    > Mail: octopus_work.163.com
    > Created Time: 2014年04月19日 星期六 20时51分59秒
 ************************************************************************/

#ifndef KILL

int kill(char *);

#endif

-- kill.c 内容 : 主要实现 kill.h 中声明的 kill 方法;

/*************************************************************************
    > File Name: kill.c
    > Author: octopus
    > Mail: octopus_work.163.com
    > Created Time: 2014年04月19日 星期六 20时53分53秒
 ************************************************************************/

#include<stdio.h>

int kill(char *ch)
{
	printf("%s \n", ch);
	return 0;
}

-- mian.c内容 : 引用 kill.h 库;

/*************************************************************************
    > File Name: main.c
    > Author: octopus
    > Mail: octopus_work.163.com
    > Created Time: 2014年04月19日 星期六 16时22分26秒
 ************************************************************************/

#include<stdio.h>
#include"kill.h"

int main(int argc, char **argv)
{
	printf("Hello World! \n");
	kill("fuck");
	return 0;
}

引用头文件库符号区别 : #include"kill.h" #include<kill.h> ;

-- #include "kill.h" : 先在当前目录搜索 kill.h 头文件, 在到系统中搜索该头文件;

-- #include <kill.h> : 直接去系统库中寻找头文件, 不会搜索当前目录;

编译文件 : 使用 gcc -Wall main.c kill.c -o kill 进行编译;

[email protected]:~/gcc$ gcc -Wall main.c kill.c -o kill
[email protected]:~/gcc$ ./kill
Hello World!
fuck

3. 独立编译文件

开发需求 : 当一个项目比较大的时候, 整个项目编译时间会很长, 如果改变一个函数就需要重新编译整个项目, 就会很浪费时间;

-- 解决方案 : 程序被存储在多个源文件中, 每个源文件都单独进行编译;

单独编译多个源文件步骤 :  首先生成 对象文件, 再将对象文件链接生成可执行文件;

-- 编译对象文件 : 将源程序编译成不可执行的文件, 生成 .o 后缀的对象文件;

-- 链接程序 : gcc 中有一个链接器将所有的对象文件链接到一起, 生成一个可执行文件;

解析对象文件 : 文件中存放的是机器码, 机器码中对其他文件中的 函数 或者 变量引用的地址没有解析, 当链接程序的时候才将这些地址写入;

生成对象文件 : -c 参数用于生成 对象文件;

-- 生成kill.o对象文件 : gcc -Wall -c kill.c , 会生成 kill.o 文件, 该对象文件中引用 kill 方法, 该方法对应的地址没有被解析;

[email protected]:~/gcc$ gcc -Wall -c kill.c
[email protected]:~/gcc$ ls
kill.c  kill.h  kill.o  main.c

-- 生成 main.o对象文件 : gcc -Wall -c mian.c, 生成 main.o 文件;

[email protected]:~/gcc$ gcc -Wall -c main.c
[email protected]:~/gcc$ ls
kill.c  kill.h  kill.o  main.c  main.o

链接对象文件 : gcc main.o kill.o -o main 命令, 链接 main.o 和 kill.o 两个对象文件;

-- 不许要-Wall参数 : 链接程序只有两种结果, 成功 或者 失败, 不许要警告信息了;

-- 链接器 : gcc中ld链接器 用来链接对象文件;

[email protected]:~/gcc$ gcc main.o kill.o -o main
[email protected]:~/gcc$ ./main
Hello World!
fuck 

对象文件的链接次序 : 大部分编译器都可以随意排列顺序, 但是有的编译器需要注意链接次序;

-- 编译器和连接器次序 : 编译器和链接器搜索外部函数 是 从左到右进行查找;

-- 文件次序 : 调用函数的 对象文件, 该文件应该先于 定义函数的 对象文件, 这里 main.o 应该在 kill.o 之前;

-- 错误排查 : 如果在编译程序的时候, 列出了所有的文件, 但是还出现了 未定义 错误, 就需要注意 文件排列的问题;

修改文件流程 : 当修改了一个文件之后, 只需要 重新编译这个文件即可, 之后将这个新编译的对象文件 与 原来的对象文件进行链接, 即可生成新的可执行文件;

-- 重新编译 : 当修改了一个文件之后, 只需要将这个文件重新编译成 对象文件即可;

-- 重新链接 : 将新编译的对象文件, 与之前已经编译好的 其它源文件的对象文件进行链接即可;

,

gcc 学习笔记(一) - 编译C程序 及 编译过程,码迷,mamicode.com

时间: 2024-10-29 19:07:43

gcc 学习笔记(一) - 编译C程序 及 编译过程的相关文章

学习笔记之yum及程序包编译

Yum用法 背景:由于RPM包在实现程序包管理是会存在一些问题比如程序包会出现一些依赖关系等,这些都必须手动解决,虽然可以忽略一些关系,并且能使用--nodeps来安装,但是这会导致安装之后程序功能的不正常,因此为了能更好的解决这种关系,但手动解决这种关系又是一种很麻烦的事,所以就有高于rpm管理的工具yum来实现,下面我们就来仔细看看yum的用法. 学前须知: yum 和dnf是centos系列的好工具 Yum是什么?在安装程序时能实现在前端自动解决依赖关系.Yum其实是一个cs架构的工具,y

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

一. C程序编译过程 编译过程简单介绍 : C语言的源文件 编译成 可运行文件须要四个步骤, 预处理 (Preprocessing) 扩展宏, 编译 (compilation) 得到汇编语言, 汇编 (assembly) 得到机器码, 连接 (linking) 得到可运行文件; -- 查看每一个步骤的编译细节 : "-E" 相应 预处理, "-S" 相应 编译, "-c" 相应 汇编, "-O" 相应 连接; -- 每一个步骤

Linux 程序设计学习笔记----进程管理与程序开发(下)

转载请注明出处:http://blog.csdn.net/suool/article/details/38419983,谢谢! 进程管理及其控制 创建进程 fork()函数 函数说明具体参见:http://pubs.opengroup.org/onlinepubs/009695399/functions/fork.html 返回值:Upon successful completion, fork() shall return 0 to the child process and shall re

C++ Primer 学习笔记_90_用于大型程序的工具 --异常处理[续3]

用于大型程序的工具 --异常处理[续3] 九.auto_ptr类[接上] 5.auto_ptr对象的复制和赋值是破坏性操作 auto_ptr和内置指针对待复制和赋值有非常关键的区别.当复制auto_ptr对象或者将它的值赋给其他auto_ptr对象的时候,将基础对象的所有权从原来的auto_ptr对象转给副本,原来的auto_ptr对象重置为未绑定状态. auto_ptr<string> strPtr1(new string("HELLO!")); auto_ptr<

C++ Primer 学习笔记_96_用于大型程序的工具 --多重继承与虚继承[续1]

用于大型程序的工具 --多重继承与虚继承[续1] 四.多重继承下的类作用域 成员函数中使用的名字和查找首先在函数本身进行,如果不能在本地找到名字,就继续在本类中查找,然后依次查找每个基类.在多重继承下,查找同时检察所有的基类继承子树 -- 在我们的例子中,并行查找 Endangered子树和Bear/ZooAnimal子树.如果在多个子树中找到该名字,则那个名字的使用必须显式指定使用哪个基类;否则,该名字的使用是二义性的. [小心地雷] 当一个类有多个基类的时候,通过对所有直接基类同时进行名字查

C++ Primer 学习笔记_79_模板与泛型编程 --模板编译模型

模板与泛型编程 --模板编译模型 引言: 当编译器看到模板定义的时候,它不立即产生代码.只有在用到模板时,如果调用了函数模板或定义了模板的对象的时候,编译器才产生特定类型的模板实例. 一般而言,当调用函数时[不是模板],编译器只需看到函数的声明.类似的,定义类类型的对象时,类定义必须可用,但成员函数的定义不是必须存在的.因此,应该将类定义和函数声明放在头文件中,而普通函数和类成员函数的定义放在源文件中. 模板则不同:要进行实例化,编译器必须能够访问定义模板的源代码.当调用函数模板或类模板的成员函

C++ Primer 学习笔记_91_用于大型程序的工具 --命名空间

用于大型程序的工具 --命名空间 引言: 在一个给定作用域中定义的每个名字在该作用域中必须是唯一的,对庞大.复杂的应用程序而言,这个要求可能难以满足.这样的应用程序的全局作用域中一般有许多名字定义.由独立开发的库构成的复杂程序更有可能遇到名字冲突 -- 同样的名字既可能在我们自己的代码中使用,也可能(更常见地)在独立供应商提供的代码中使用. 库倾向于定义许多全局名字 -- 主要是模板名.类型名或函数名.在使用来自多个供应商的库编写应用程序的时候,这些名字中有一些几乎不可避免地会发生冲突,这种名字

C++ Primer 学习笔记_92_用于大型程序的工具 --命名空间[续1]

用于大型程序的工具 --命名空间[续1] 二.嵌套命名空间 一个嵌套命名空间即是一个嵌套作用域 -- 其作用域嵌套在包含它的命名空间内部.嵌套命名空间中的名字遵循常规规则:外围命名空间中声明的名字被嵌套命名空间中同一名字的声明所屏蔽.嵌套命名空间内部定义的名字局部于该命名空间.外围命名空间之外的代码只能通过限定名引用嵌套命名空间中的名字. 嵌套命名空间可以改进库中代码的组织: namespace cplusplus_primer { namespace QueryLib { class Quer

C++ Primer 学习笔记_89_用于大型程序的工具 --异常处理[续2]

用于大型程序的工具 --异常处理[续2] 八.自动资源释放 考虑下面函数: void f() { vector<string> v; string s; while (cin >> s) { v.push_back(s); } string *p = new string[v.size()]; //... delete p; } 在正常情况下,数组和vector都在退出函数之前被撤销,函数中最后一个语句释放数组,在函数结束时自动撤销vector. 但是,如果在函数内部发生异常,则将