嵌入式C语言自我修养分享课件(二)

一. 编译运行流程介绍

1.编译:编译器会将程序源代码编译成汇编代码
1).预处理:
对源代码中的伪指令进行处理(以#开头的指令)。
·删除所有的#define,展开所有宏定义。
·处理条件指令,例如 #if、#elif、#else、endif等。
·处理头文件包含指令,如 #include,将被包含的文件插入到该预编译指令的位置。
删除所有的注释
添加行号和文件名标识

2.汇编:汇编器会将汇编代码文件翻译成为二进制的机器码,保存在后缀名为.o的目标文件中,这个文件是一个 ELF 格式的文件,表示可执行可链接文件格式,例如目标文件 .o可执行文件 .exe共享目标文件 .so

3.链接:由汇编程序生成的目标文件(.o文件)不能被立即执行,还需要通过链接器,将有关的目标文件彼此相连,使所有目标文件成为一个能够被操作系统载入执行的统一整体。

某个源文件中的函数调用另一个源文件中的函数或者调用库文件中的函数时,都需要链接,否则报错函数未定义。

其中,链接处理分为静态链接和动态链接。
1.静态链接:直接在编译阶段就把静态库加入到可执行文件当中去
2.动态链接:在链接阶段只加入一些描述信息,等到程序执行时再从系统中把相应的动态库加载到内存中去。
优缺点:
静态链接:
优点:不用担心目标用户缺少库文件,在编译阶段已经和其他文件一起编译成汇编代码。
缺点:最终的可执行文件会比较大,多个应用程序之间无法共享库文件,而且库文件中的函数不一定全部用得到,造成内存浪费

动态链接:
优点:可执行文件小。
缺点:需要提前准备用户所需要的库文件。
动态链接是我们经常用到的链接方式,需要开发提前提供所需要的库文件。

4.载入:加载器会将可执行文件的代码和数据从硬盘加载到内存中,然后跳转到程序的第一条指令处开始运行。
为了避免进程所使用的内存地址相互影响,操作系统会为每个进程分配一套独立的虚拟内存地址,然后再提供一种机制,将虚拟内存地址和物理内存地址进行映射。

程序使用的内存地址叫做虚拟内存地址(Virtual Memory Address)。
实际存在硬件里面的空间地址叫物理内存地址(Physical Memory Address)。

二.BSS段简介

要了解BSS段,需要先了解用户空间内存的6种不同的内存段。
从低到高为:
1.程序文件段(.text),包括二进制可执行代码。
2.已初始化数据段(.data),包括静态常量。
3.未初始化数据段(.bss),包括未初始化的静态变量。
4.堆段,包括动态分配的内存,从低地址开始向上增长。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)。
5.文件映射段,包括动态库、共享内存等,从低地址开始向上增长。
6.栈段,包括局部变量和函数调用的上下文等。大小一般是8MB,栈段可以通过系统调用自动地扩充空间,但是不能回收空间,所以栈段设置得太大会导致内存泄露。

内存分页:操作系统把整个虚拟和物理内存空间切成一段段固定尺寸的大小。这样一个连续并且尺寸固定的内存空间,叫做页,在Linux 下,每一页的大小为 4KB。
虚拟地址与物理地址之间通过页表来映射,CPU 中的 MMU (Memory Management Unit,内存管理单元)将虚拟地址转换成物理地址。

综上,一个可执行文件载入到内存需要以下几个步骤:
1.给进程分配虚拟内存空间。
2.创建虚拟地址到物理地址的映射,创建页表。
3.加载代码段和数据段等数据,即将硬盘中的文件拷贝到物理内存页中,并在页表中写入映射关系。
4.把可执行文件的入口地址写入到 CPU 的 指令寄存器(PC)中,即可执行程序。

对于未初始化的全局变量和静态局部变量,编译器将其放置在BSS 段中 。 BSS 段是不占用可执行文件存储空间,但是当程序加载到内存运行时,加载器会在内存中给BSS段开辟一段存储空间。

在这里插入图片描述
secfion header table 中会记录BSS段的大小,在符号表中会记录每个变量的地址和大小。

加载器会根据这个表的信息,在数据段的后面分配指定大小的内存空间并清零,根据符号表中各个变量的地址,在这个内存空间中给这些未初始化的全局变量、静态变量分配存储空间。

总结:BSS段的设计是为了减少文件体积。编译器对数据段和BSS 段符号的处理流程是相同的,不同点在于,在可执行文件内不给BSS段分配存储空间在程序运行时再分配存储空间和地址

三.静态链接和动态链接库

为什么需要静态链接和动态链接
一个软件项目中,为了完成特定功能,除了自定义函数,我们还可以使用别人己经封装好的函数库,例如C标准库、研发提供的库以及第三方库,库函数的使用避免了“造轮子”的重复工作,提高 了代码复用率,大大减轻了软件开发的工作量。

1.静态链接库
编译阶段,链接器将我们引用的函数代码或变量,链接到可执行文件里,和可执行程序组装在一起,这种库称为静态库。

静态库的制作和使用都很简单,使用AR命令就可以将多个目标文件打包为一个静态库。

举例:
新建一个test.c文件
在这里插入图片描述
新建一个main.c文件
在这里插入图片描述
将test.c 一整个文件打包成静态库。ar rcs
在这里插入图片描述
ar rcs 也可以写成ar -rcs
看下可执行程序的大小
在这里插入图片描述

使用ar命令制作静态库的常用命令:
-c :建立备存文件
-r:如果指定的文件己经在库中存在,则替换它
-s :无论库是否更新都强制重新生成新的符号表
-d:从库中删除指定的文件。
-o :对压缩文档成员进行排序。
-q:向库中追加指定文件。
-L:打印库中的目标文件。
-x:解压库中的目标文件。

可以看到,main函数中只调用了add函数,但是实际上,链接器将4个函数全部组装到可执行文件中了,导致并没有完全使用所有的函数,这会让最终生成的可执行文件的体积大大增加。
使用readelf -s a.out可以查看可执行文件中所链接的函数
在这里插入图片描述
为了解决这种链接多个,只使用部分的问题,可以由有两种方法
第一种就是每个函数都建立一个.c文件,然后将多个目标文件打包。
gcc -c add.c sub.c mul.c div.c
ar rcs libtest.a add.o sub.o mul.o div.o
gcc main.c -L. -ltest
这样产生的可执行文件就只使用了add函数。
C语言标准库就是这种做法,printf()函数单独定义在printf.c里,scanf()函数单独定义在scanf.c文件中, 如果你调用了一个printf()函数,则链接器只是将printf()函数的目标文件链接到你的可执行文件中。

虽然这种方法可以减小可执行文件的体积,但是当多个程序引用相同的公共代码时,这些公共代码会多次加载到内存,浪费内存资源。尤其对于一些内存配置较低的嵌入式系统,当过多的进程并发运行时,系统就可能因为内存爆满而无法流畅运行。
为了解决这个问题,动态链接的方法就产生了。

2.动态链接库
动态链接对静态链接做了一些优化:对一些公用的代码,如库,在链接期间暂不链接,而是推迟到程序运行时再进行链接。这些在程序运行时才参与链接的库被称为动态链接库。程序运行时,除了可执行文件,这些动态链接库也要跟着一起加载到内存,参与链接和重定位过程,否则程序可能就会报未定义错误,无法运行。

动态库的文件变成了以.so为后缀。一个软件采用动态链接,版本升级时主程序的业务逻辑或框架不需要改变,只需要更新对应的.dll或.so文件就可以 了,简单方便,也避免了用户重复安装卸载软件。以上面为例,我们可以将add.c , sub.c、 mul.c、 div.c 封装成动态库libtest.so,然后在程序运行时动态加载到内存。

将每个函数都写入对应的.c文件里,然后使用以下指令
在这里插入图片描述

可以发现生成的可执行程序运行时报错了,说找不到动态库文件,这是因为动态链接库要放到/lib 、/usr/lib等系统默认的库路径下 ,否则a.out就会动态链接失败。
有两种方法可以解决
第一种是将这个.so文件拷贝到/lib或者/usr/lib下,让系统可以找到。
第二种是使用LD_LIBRARY_PATH环境变量(临时有效)。
添加后可以正常运行
在这里插入图片描述
看下可执行程序的大小
在这里插入图片描述
发现是比静态库占用的空间小一点。至于小得不多,是因为本身执行程序就很小,少了三个函数组合到可执行程序中的大小。
使用readelf -s a.out发现只链接了使用的add函数。
在这里插入图片描述

综上:
.a文件就是静态库文件,.so文件是动态库文件。
在小型项目函数不是很多的情况下建议使用静态库,因为将每个函数都写入对应的.c文件,不如一起写入一个.c文件中,减少了时间成本,且小型项目的可执行程序在静态库和动态库的差别不是很大。
在大型多并发的项目中,建议使用动态库,防止多个程序引用相同的公共代码时,这些公共代码会多次加载到内存,浪费内存资源,造成内存爆满,无法流畅运行。

在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
  • 打赏
    打赏
  • 0
    评论
宋宝华嵌入式 C/C++语言精华文章集锦 C/C+语言 struct 深层探索 ............................................................................2 C++中 extern "C"含义深层探索........................................................................7 C 语言高效编程的几招...............................................................................11 想成为嵌入式程序员应知道的 0x10 个基本问题 .........................................................15 C 语言嵌入式系统编程修炼...........................................................................22 C 语言嵌入式系统编程修炼之一:背景篇 ............................................................22 C 语言嵌入式系统编程修炼之:软件架构篇 ........................................................24 C 语言嵌入式系统编程修炼之三:内存操作 ..........................................................30 C 语言嵌入式系统编程修炼之四:屏幕操作 ..........................................................36 C 语言嵌入式系统编程修炼之五:键盘操作 ..........................................................43 C 语言嵌入式系统编程修炼之六:性能优化 ..........................................................46 C/C++语言 void 及 void 指针深层探索 .................................................................50 C/C++语言可变参数表深层探索 .......................................................................54 C/C++数组名与指针区别深层探索 .....................................................................60 C/C++程序员应聘常见面试题深入剖析(1) ..............................................................62 C/C++程序员应聘常见面试题深入剖析(2) ..............................................................67 一道著名外企面试题的抽丝剥茧 ......................................................................74 C/C++结构体的一个高级特性――指定成员的位数 .......................................................78 C/C++中的近指令、远指针和巨指针 ...................................................................80 从两道经典试题谈 C/C++中联合体(union)的使用 ......................................................81 基于 ARM 的嵌入式 Linux 移植真实体验 ................................................................83 基于 ARM 的嵌入式 Linux 移植真实体验(1)――基本概念 ...........................................83 基于 ARM 的嵌入式 Linux 移植真实体验(2)――BootLoader .........................................96 基于 ARM 的嵌入式 Linux 移植真实体验(3)――操作系统 ..........................................111 基于 ARM 的嵌入式 Linux 移植真实体验(4)――设备驱动 ..........................................120 基于 ARM 的嵌入式 Linux 移植真实体验(5)――应用实例 ..........................................135 深入浅出 Linux 设备驱动编程 .......................................................................144 1.Linux 内核模块..............................................................................144 2.字符设备驱动程序 ...........................................................................146 3.设备驱动中的并发控制 .......................................................................151 4.设备的阻塞与非阻塞操作 .....................................................................157

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:深蓝海洋 设计师:CSDN官方博客 返回首页
评论

打赏作者

菠萝印象威

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值