【软件开发底层知识修炼】五 gcc-C语言编译器

学习交流加

  • 个人qq:
    1126137994
  • 个人微信:
    liu1126137994
  • 学习交流资源分享qq群:
    962535112

文章目录

  • 1、GCC与gcc
  • 2、gcc的幕后工作
  • 3、实用的gcc选项
    • 3.1、预处理选项-解决宏错误
    • 3.2、-S参数-辅助编写汇编程序的好方法
    • 3.3、获取系统头文件路径
    • 3.4、产生映射文件
    • 3.5、通过选项定义宏
    • 3.6、生成依赖关系
    • 3.7、指定链接库
  • 4、总结

前面的四篇文章终于把处理器系列学完了(点击查看上一篇文章:高速缓存与TLB)。收获很大!!! 接下来就该学习底层软件部分知识。今天学习gcc的基本概念与简单用法。

1、GCC与gcc

  • GCC (GNU Compiler Collection)

    • GNU 编译器集合,包含众多语言的编译器,包括C,C++,Java等等
    • GCC多用于嵌入式操作系统的编译,如Linux,VxWorks,Android等等
  • gcc 单指GCC中的C语言编译器

    • gcc 多用于内核开发以及少数应用程序开发

2、gcc的幕后工作

想了解更多更详细的关于编译链接深层次内容,请阅读书籍《CSAPP》第7章与《程序员的自我修养》,因为这里我的学习记录只记录结果与常用的几个编译方法。

我们先来看一个简单的程序:

test.c源程序:

#include <stdio.h>
#include "func.h"int g_global = 0;
int g_test = 1;int main(int argc, char *argv[])
{func();printf("&g_global = %p\n", &g_global);printf("&g_test = %p\n", &g_test);printf("&func = %p\n", &func);printf("&main = %p\n", &main);return 0;
}

func.h头文件:

#include <stdio.h>void func()
{
#ifdef TESTprintf("TEST = %s\n", TEST);
#endifreturn;
}

在Linux下使用gcc进行编译:

gcc test.c -o test

然后运行:

./test

结果如下:

&g_global = 0x804a020
&g_test = 0x804a014
&func = 0x80483c4
&main = 0x80483c9

很明显,上述程序很简单,大一的新生都知道为什么。但是今天我们不是学习这个程序的,而是想要了解,运行 gcc test.c -o test 这个命令后,是如何一步一步生成可执行文件test的。

实际上,上述C程序从源文件到二进制可执行文件,有以下四个步骤:

  1. 预处理 cpp
  2. C编译 cc
  3. 汇编 as
  4. 链接 ld

大概编译一个源程序为二进制文件的过程如下图所示:
在这里插入图片描述

当然,上面没有列出链接器,在生成file.o后,还需要将file.o与系统的库文件进行链接,生成最终的可执行文件。

从而,我们就知道了,gcc其实内部包含了预处理器,编译器,汇编器,链接器这四部分。

这四部分这里只是来简单介绍一下(网上一大堆,本文侧重点不在此):

  • 预处理器:预处理,将源程序的宏定义与带‘#’的部分展开
  • 编译器:将预处理后得到的文件进行第一次编译得到对应的汇编源程序
  • 汇编器:将第二步得到的汇编源程序进行第二次编译即汇编,得到二进制文件(可重定位文件)
  • 链接器:将可重定位文件与相应的库进行链接生成最终的可执行文件

3、实用的gcc选项

本文的重点来了,上述的内容过于简单,而本节的内容虽然不难,但是并不被大多数人所了解,所以是本文的重点学习记录。

下面将要学习的gcc选项,在工作中具有很强的实用性。

3.1、预处理选项-解决宏错误

gcc -E file.c -o file.i

实用上述编译选项 -E 可以得到预处理后的文件,有时候我们在程序中定义的宏可能有错误,而这种错误又很难找,此时如果能得预处理后的文件,就可以方便定位错误。

3.2、-S参数-辅助编写汇编程序的好方法

写汇编程序很难,但是如果先写成C语言,再将这个C语言转化成汇编语言,就会很简单。gcc编译工具中,-S选项,可以达到这个目的。比如以下程序:
foo.c程序:

#include <stdio.h>void foo(){printf("This is foo().\n");                                             }

我们使用如下命令进行编译:

gcc -S -O2 foo.c -o foo.s

将会生成一个foo.c相同作用的汇编程序foo.s,如下:

	.file	"foo.c".section	.rodata.str1.1,"aMS",@progbits,1
.LC0:.string	"This is foo().\n".text.p2align 4,,15
.globl foo.type	foo, @function
foo:pushl	%ebpmovl	%esp, %ebpsubl	$24, %espmovl	$.LC0, 4(%esp)movl	$1, (%esp)call	__printf_chkleaveret.size	foo, .-foo.ident	"GCC: (Ubuntu/Linaro 4.4.4-14ubuntu5.1) 4.4.5".section	.note.GNU-stack,"",@progbits

使用-S 参数时,我们可以根据需要使用-O优化选项。从foo.s的内容可以看出,“This is foo().\n” 这个字符串是放在.rodata段的。看来获取C程序对应的汇编代码,对C语言实现方面的细节,也有所帮助。

3.3、获取系统头文件路径

gcc -v file.c 

获取file.c使用的系统头文件的位置

3.4、产生映射文件

如果我们想要知道程序中各个符号的内存布局的信息,可以使用如下命令:

gcc -Wl,-Map=file.map file.c -o file

3.5、通过选项定义宏

有时候程序中需要的某一个常量会依赖工作环境的不同而改变,这个时候,我们可以将这个常量定义为宏,但是这样,我们还是需要每次都在源程序中将宏的值改变,这也很麻烦,此时就可以利用编译选项 -D,在编译的命令行进行宏定义。

还有就是程序中或许会存在下属这样的代码:
test.c程序:

#include <stdio.h>
#include "func.h"int g_global = 0;
int g_test = 1;int main(int argc, char *argv[])
{func();printf("&g_global = %p\n", &g_global);printf("&g_test = %p\n", &g_test);printf("&func = %p\n", &func);printf("&main = %p\n", &main);return 0;
}

test.h头文件:

#include <stdio.h>void func()
{
#ifdef TESTprintf("TEST = %s\n", TEST);
#endifreturn;
}

在头文件中,有一处定义 # ifdef TEST …

很明显,上面的两个文件,都没有定义这个TEST,所以程序运行结果如下:

  &g_global = 0x804a020&g_test = 0x804a014&func = 0x80483c4&main = 0x80483c9

但是可能在某个场合,又必须要使用TEST定义,那么此时,我们肯定不愿意在程序中改来改去,此时就利用编译器的 -D选项,来定义这个TEST。如下编译命令:

gcc -D'TEST="test" ' test.c -o test

运行程序后,结果如下:

TEST = test
&g_global = 0x804a020
&g_test = 0x804a014
&func = 0x80483c4
&main = 0x80483e1

3.6、生成依赖关系

大多数人应该知道make,如果不知道也没有关系。
在makefile中,make需要通过依赖关系来决定,每次构建时哪些文件需要重新编译。使用gcc的-M选项,可以得到make所需要的源文件的依赖关系。-MM选项可以让gcc生成不包含系统文件的依赖关系。

比如有如下源文件:
main.c源文件(main.h与foo.c的内容是什么都行)

#include <stdio.h>
#include "main.h"
#include "foo.c"int main(){printf("Hello world!\n");return 0;                                                                   
}

对其进行如下编译

gcc -M main.c

将得到如下输出:
在这里插入图片描述

可以看到,这句是make所需要的main.c的依赖关系。

如果使用如下命令的话:

gcc -MM main.c

将得到如下输出:
在这里插入图片描述
结果显而易见!!!

3.7、指定链接库

当一个可执行程序的生成,需要使用其他库时,需要在链接时加以指定。这就需要用到gcc 的-l与-L选项。

假设一个程序叫做main.c,它编译成可执行程序不光需要系统的标准库,还需要一个库:libfoo.a 且这个libfoo.a与main.c在同一个目录,那么在编译main.c时,需要以下命令:

gcc -o main -L. main.c -lfoo

注意:

  • -L告诉gcc编译器,当前可以从哪个目录查找库文件,此处-L后面跟了一个**点‘.’**表示当前目录。
  • -l选项,告诉编译器需要连接的库名。这里并没有写“lib”前缀和“.a后缀”。-lfoo就是代表指定libfoo.a库参与链接。

更加详细的内容参考《程序员的自我修养》

4、总结

今天学习了gcc的简单概念,与gcc的常用的参数选项。

本文章参考狄泰软件学院相关课程与《专业嵌入式软件》第4章的内容内容
想学习的可以加狄泰软件学院群,
群聊号码:199546072

学习探讨加个人:
qq:1126137994
微信:liu1126137994

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/423082.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

思维模型分享

思维模型1.思维建模1.1 模型的用途1.2构建模型的3种方法1.3多样性预测定理1.4分类模型1.5 变差模型1.5.1 解释变差的百分比1.6 模型误差分解定理1.7 多模型思维1.8 对人类行为建模1.8.1 理性行为者模式2.模型思维2.1 正态分布2.2中心极限定理2.2.1 平方根法则2.2.2 检验显著性2…

在工程中最常用的 vim使用技巧

学习交流加&#xff08;可免费帮忙下载CSDN资源&#xff09;&#xff1a;个人微信&#xff1a; liu1126137994学习交流资源分享qq群1&#xff08;已满&#xff09;&#xff1a; 962535112学习交流资源分享qq群2&#xff1a; 780902027 文章目录1、vim编辑常用快捷键2、文件索引…

动态规划过程

动态规划过程应用背包问题&#xff1a;分享一下 有一个背包&#xff0c;容量是4磅&#xff0c;现有如下产品 1)要求达到的目标为装入的背包的总价值最大&#xff0c;并且要求重量不能超出 2&#xff09; 要求转入的物品不能重复 思路分析&#xff1a;算法其实是模型建立的过程 …

【剑指offer - C++/Java】5、用两个栈实现队列

学习交流加 个人qq&#xff1a; 1126137994个人微信&#xff1a; liu1126137994学习交流资源分享qq群&#xff1a; 962535112 牛客网题目链接&#xff1a;用两个栈实现队列 文章目录1、题目分析2、代码Java代码&#xff1a;C代码3、总结题目描述&#xff1a; 用两个栈来实现一…

【剑指offer - C++/Java】6、旋转数组的最小数字

题目链接&#xff1a;旋转数组的最小数字 文章目录1、题目描述2、题目分析3、代码3.1 Java代码3.2、C代码4、总结1、题目描述 把一个数组最开始的若干个元素搬到数组的末尾&#xff0c;我们称之为数组的旋转。 输入一个非减排序的数组的一个旋转&#xff0c;输出旋转数组的最小…

设计模式-责任链模型

责任链模式场景: OA审批系统 CEO 审批项目 经费 500万<x ≤\leq≤ 1000万总监 审批的经费 300万 < x ≤\leq≤ 500万总监秘书 审批的经费 100万< x ≤\leq≤ 300万研发经理 审批的经费 50万 <x ≤\leq≤ 100万 传统方式 是 (接收到请求后&#xff0c;根据相应金额&…

【OS学习笔记】三 计算机的启动过程

学习交流加 个人qq&#xff1a; 1126137994个人微信&#xff1a; liu1126137994学习交流资源分享qq群&#xff1a; 962535112 上一篇文章迈进了汇编的大门&#xff0c;点击链接查看上一篇文章&#xff1a;汇编语言和汇编软件 上一篇文章大概学会以下内容&#xff1a; 了解汇编…

【OS学习笔记】四 什么是虚拟机

虚拟机是软件 对于第一次听说虚拟机&#xff08;Virtual Machine&#xff0c;VM&#xff09;的人来说&#xff0c;可能以为还要再花钱买一台计算机&#xff0c;这恐怕是他们最担心的。所谓虚拟机&#xff0c;就是在你的计算机上再虚拟出另一台计算机来。这台虚拟出来的计算机&…

TCP/IP协议族之运输层(TCP流量控制和拥塞控制 [1])

TCP的流量控制 1. 利用滑动窗口实现流量控制 如果发送方把数据发送得过快&#xff0c;接收方可能会来不及接收&#xff0c;这就会造成数据的丢失。所谓流量控制就是让发送方的发送速率不要太快&#xff0c;要让接收方来得及接收。 利用滑动窗口机制可以很方便地在TCP连接上实现…

英语词源记忆法

英语词源记忆法后续持续更新中

【OS学习笔记】五 VirtualBox的下载、安装和配置

上一篇文章学习了&#xff1a;计算机的启动过程&#xff08;点击链接查看上一篇文章&#xff09; 今天来接着上一篇文章&#xff0c;解决我们学习中的实验环境问题。 参考&#xff1a;X86汇编语言-从实模式到保护模式。作者李忠。纯学习笔记。如有侵权请联系我删除 1、下载O…

spring体系思维导图

spring体系思维导图1. spring 思维导图2. springboot 思维导图3. springcloud 思维导图之前整理的&#xff0c;后续持续更新中1. spring 思维导图 2. springboot 思维导图 3. springcloud 思维导图

idea模板

idea模板1.类注解模板2.方法注解模板3.自定义代码生成模板每次下载新版本的idea 或者换笔记本都需要重新&#xff0c;配置注解&#xff0c;而且从网上找了很多都或多或少有问题&#xff0c;每次要花费一些时间配置&#xff0c;这里整理一下。自定义代码生成模版&#xff0c;设置…

【OS学习笔记】六 实模式:编写主引导扇区代码

上一篇文章学习了&#xff1a;计算机的启动过程&#xff08;点击链接查看上一篇文章&#xff09; 这篇文章学习记录为&#xff1a;编写主引导扇区代码。 参考&#xff1a;《X86汇编语言-从实模式到保护模式》-李忠。纯学习笔记&#xff0c;更详细内容请阅读正版书籍。如有侵权…

【OS学习笔记】七 Bochs的下载、安装和配置

参考&#xff1a;《X86汇编语言-从实模式到保护模式》-李忠。纯学习笔记&#xff0c;更详细内容请阅读正版书籍。 1 开源的BOCHS虚拟机软件 Bochs是开源软件。它用软件来模拟处理器取指令和执行指令的过程&#xff0c;以及整个计算机硬件。当它开始运行时&#xff0c;就直接模…

【OS学习笔记】八 实模式:编写主引导扇区代码-另一种更高效的写法

学习交流加 个人qq&#xff1a; 1126137994个人微信&#xff1a; liu1126137994学习交流资源分享qq群&#xff1a; 962535112 上一篇文章&#xff0c;我们用比较原始的方法编写了主引导扇区的代码。点击链接查看上一篇文章&#xff1a;编写主引导扇区代码 本片文章将学习以下内…

【OS学习笔记】九 实模式:从汇编的角度理解栈结构

上一篇文章以一种更加高效的方法编写了主引导扇区的代码。主要是引入了循环和跳转指令。点击链接查看上一篇文章&#xff1a;编写主引导扇区代码-另一种更高效的写法 本篇文章&#xff0c;继续上一篇文章的学习。同样还是编写汇编代码加载到主引导扇区让CPU直接执行。但是我们…

【OS学习笔记】十 实模式:实现一个程序加载器-程序加载器如何将用户程序加载到内存并执行

上一篇文章学习了以下内容&#xff1a; 用一种不同的分段方法&#xff0c;从另一个不同的的角度理解处理器的分段内存访问机制使用循环和条件转移指令来优化主引导扇区代码 点击链接查看上一篇文章&#xff1a;点击链接查看 对于主引导扇区部分。大概前几篇文章已经学的差不…

【OS学习笔记】十一 实模式:中断-软中断和硬中断基本原理

上一篇文章我们模拟操作系统的加载器程序&#xff0c;使用汇编语言实现了一个程序加载器&#xff1a;点击链接查看上一篇文章&#xff1a;程序加载器的实现原理 本篇文章&#xff0c;是实模式学习的结尾。在经过了那么多坎坷&#xff0c;终于学完了8086的实模式&#xff01;&a…