【嵌入式开发】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
二. 编译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程序;
octopus@octopus:~/gcc$ gcc main.c octopus@octopus:~/gcc$ ./a.out Hello World!
指定输出文件编译 : 如果不想使用 a.out 作为输出文件, 可以使用 -o 参数指定输出文件, 如果该文件存在就会覆盖;
-- 命令 : gcc main.c -o main;
octopus@octopus:~/gcc$ gcc main.c -o main octopus@octopus:~/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, 编译的时候报出警告, 但是编译通过, 但是运行的时候就出错了;
octopus@octopus:~/gcc$ gcc main.c main.c: 在函数‘main’中: main.c:12:2: 警告: 格式 ‘%s’ expects argument of type ‘char *’, but argument 2 has type ‘int’ [-Wformat] octopus@octopus:~/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 进行编译;
octopus@octopus:~/gcc$ gcc -Wall main.c kill.c -o kill octopus@octopus:~/gcc$ ./kill Hello World! fuck
3. 独立编译文件
开发需求 : 当一个项目比较大的时候, 整个项目编译时间会很长, 如果改变一个函数就需要重新编译整个项目, 就会很浪费时间;
-- 解决方案 : 程序被存储在多个源文件中, 每个源文件都单独进行编译;
单独编译多个源文件步骤 : 首先生成 对象文件, 再将对象文件链接生成可执行文件;
-- 编译对象文件 : 将源程序编译成不可执行的文件, 生成 .o 后缀的对象文件;
-- 链接程序 : gcc 中有一个链接器将所有的对象文件链接到一起, 生成一个可执行文件;
解析对象文件 : 文件中存放的是机器码, 机器码中对其他文件中的 函数 或者 变量引用的地址没有解析, 当链接程序的时候才将这些地址写入;
生成对象文件 : -c 参数用于生成 对象文件;
-- 生成kill.o对象文件 : gcc -Wall -c kill.c , 会生成 kill.o 文件, 该对象文件中引用 kill 方法, 该方法对应的地址没有被解析;
octopus@octopus:~/gcc$ gcc -Wall -c kill.c octopus@octopus:~/gcc$ ls kill.c kill.h kill.o main.c
-- 生成 main.o对象文件 : gcc -Wall -c mian.c, 生成 main.o 文件;
octopus@octopus:~/gcc$ gcc -Wall -c main.c octopus@octopus:~/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链接器 用来链接对象文件;
octopus@octopus:~/gcc$ gcc main.o kill.o -o main octopus@octopus:~/gcc$ ./main Hello World! fuck
对象文件的链接次序 : 大部分编译器都可以随意排列顺序, 但是有的编译器需要注意链接次序;
-- 编译器和连接器次序 : 编译器和链接器搜索外部函数 是 从左到右进行查找;
-- 文件次序 : 调用函数的 对象文件, 该文件应该先于 定义函数的 对象文件, 这里 main.o 应该在 kill.o 之前;
-- 错误排查 : 如果在编译程序的时候, 列出了所有的文件, 但是还出现了 未定义 错误, 就需要注意 文件排列的问题;
修改文件流程 : 当修改了一个文件之后, 只需要 重新编译这个文件即可, 之后将这个新编译的对象文件 与 原来的对象文件进行链接, 即可生成新的可执行文件;
-- 重新编译 : 当修改了一个文件之后, 只需要将这个文件重新编译成 对象文件即可;
-- 重新链接 : 将新编译的对象文件, 与之前已经编译好的 其它源文件的对象文件进行链接即可;
,
【嵌入式开发】gcc 学习笔记(一) - 编译C程序 及 编译过程的更多相关文章
- 微信小程序开发:学习笔记[7]——理解小程序的宿主环境
微信小程序开发:学习笔记[7]——理解小程序的宿主环境 渲染层与逻辑层 小程序的运行环境分成渲染层和逻辑层. 程序构造器
- 【4412嵌入式开发板学习笔记】认识uboot
转自迅为讨论群:http://www.topeetboard.com 重要说明:这份笔记不是4412开发配套的,是我在网上看视频的时候下载上课老师的笔记后修改的.所以我试了一下笔记上的uboot命令, ...
- gcc 学习笔记(一) - 编译C程序 及 编译过程
一. C程序编译过程 编译过程简介 : C语言的源文件 编译成 可执行文件需要四个步骤, 预处理 (Preprocessing) 扩展宏, 编译 (compilation) 得到汇编语言, 汇编 (a ...
- 微信小程序开发:学习笔记[3]——WXSS样式
微信小程序开发:学习笔记[3]——WXSS样式 快速开始 介绍 WXSS(WeiXin Style Sheets)是一套用于小程序的样式语言,用于描述WXML的组件样式,也就是视觉上的效果. WXSS ...
- 微信小程序开发:学习笔记[2]——WXML模板
微信小程序开发:学习笔记[2]——WXML模板 快速开始 介绍 WXML 全称是 WeiXin Markup Language,是小程序框架设计的一套标签语言,结合小程序的基础组件.事件系统,可以构建 ...
- 微信小程序开发:学习笔记[5]——JavaScript脚本
微信小程序开发:学习笔记[5]——JavaScript脚本 快速开始 介绍 小程序的主要开发语言是 JavaScript ,开发者使用 JavaScript 来开发业务逻辑以及调用小程序的 API 来 ...
- 微信小程序开发:学习笔记[4]——样式布局
微信小程序开发:学习笔记[4]——样式布局 Flex布局 新的布局方式 在小程序开发中,我们需要考虑各种尺寸终端设备上的适配.在传统网页开发,我们用的是盒模型,通过display:inline | b ...
- 微信小程序开发:学习笔记[1]——Hello World
微信小程序开发:学习笔记[1]——Hello World 快速开始 1.前往微信公众平台下载微信开发者工具. 地址:https://mp.weixin.qq.com/debug/wxadoc/dev/ ...
- 微信小程序开发:学习笔记[9]——本地数据缓存
微信小程序开发:学习笔记[9]——本地数据缓存 快速开始 说明 本地数据缓存是小程序存储在当前设备上硬盘上的数据,本地数据缓存有非常多的用途,我们可以利用本地数据缓存来存储用户在小程序上产生的操作,在 ...
随机推荐
- ACM Least Common Multiple
The least common multiple (LCM) of a set of positive integers is the smallest positive integer which ...
- Apache ActiveMQ实战(2)-集群
ActiveMQ的集群 内嵌代理所引发的问题: 消息过载 管理混乱 如何解决这些问题--集群的两种方式: Master slave Broker clusters ActiveMQ的集群有两种方式: ...
- Xcode在playground的quick look框中显示对象自定义视图
对于一般对象,playground中默认的quick look显示已经够用,比如简单的字符串,Int,或简单的自定义Class等等. 不过对于有些情况,我们需要自定义对象在playground中的显示 ...
- springMVC源码分析--SimpleControllerHandlerAdapter(三)
上一篇博客springMVC源码分析--HandlerAdapter(一)中我们主要介绍了一下HandlerAdapter接口相关的内容,实现类及其在DispatcherServlet中执行的顺序,接 ...
- ListView下拉刷新上拉加载更多实现
这篇文章将带大家了解listview下拉刷新和上拉加载更多的实现过程,先看效果(注:图片中listview中的阴影可以加上属性android:fadingEdge="none"去掉 ...
- java解决Url带中文参数乱码问题
首先打开Tomcat安装目录,打开conf文件,打开server.xml,找到这段代码: <Connector port="8080" protocol="HTTP ...
- JBOSS EAP 6 系列一 新特性
在项目中,采用的架构是Springmvc+spring+EJB+Jpa等架构,当然服务器是Jboss,本次Jboss我们采用的是JBossEap6.2,Jboss7的新特性与Jboss4.5的大的改变 ...
- 【Android应用开发】分享一个录制 Android 屏幕 gif 格式的小技巧
因为写博客总是需要录制 Android 软件的演示效果, 研究了将近一小时找到了合适的工具; 录制流程 : -- 1. 录制 Android 手机屏幕内容 : 使用 拍大师 软件录制 Android ...
- Linux2.6--虚拟文件系统
虚拟文件系统(有时也称作虚拟文件交换,更常见的是简称做VFS)作为内核子系统,为用户空间程序提供了文件和文件系统相关的接口.系统中的所有文件系统不但依赖VFS共存,而且也依赖VFS系统协同 ...
- mysql数据库连接池使用(一)dbcp方式的配置
Apache的数据库连接池 DBCP的常用配置说明,因为项目中用到了需要对其封装,所以必须先了解怎么配置以及各个配置字段的含义,理解的基础上开发我们自己的数据库连接池.可以参考官网dbcp官网. db ...