回顾:
brk/sbrk
int brk(void *p);
void *sbrk(int);
维护一个位置。brk/sbrk改变这个位置
brk改变绝对位置。
sbrk相对改变位置。
补充:全新的类型。
永远记住:c的基本类型就:整数(1字节,2字节,4字节),小数(4字节,8字节)
unsigned signed
所有全新类型都是使用typedef重新定义。学会找到新的类型的C的原型。
类型重定义的好处:
1. 维护方便
2. 移植
3. 容易理解
一、 映射虚拟内存
没有任何额外维护数据的内存分配。
mmap(分配)/munmap(释放)
1. 函数说明
void *mmap(
void *start, //指定映射的虚拟地址,0表示由系统指定开始位置)
size_t length, //映射空间大小 pagesize倍数
int prot, //映射权限 PROT_NONE | PROT_READ PROT_WRITE PROT_EXEC
int flags, //映射方式
int fd, //文件描述符号
offset_t off); //文件中的映射开始位置(必须是pagesize的倍数)
映射方式:
内存映射:匿名映射。
文件映射:映射到某个文件
只有文件映射最后两个参数有效。
MAP_ANONYMOUS 内存映射方式
MAP_SHARED MAP_PRIVATE(二选一)
2. 案例
#include <unistd.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <stdio.h>
main()
{
int *p=mmap(
NULL,
getpagesize(),//获取系统分配内存的大小
PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_SHARED,
0,0);
*p=20;
*(p+1)=30;
*(p+2)=40;
printf("%d\n",p[2]);
munmap(p,4096);//输出40
}
3. 总结
选择什么样的内存管理方法?
智能指针(指针池)
STL
new
malloc (小而多数据)
brk/sbrk (同类型的大块数据,动态移动指针)
mmap/munmap(控制内存访问/使用文件映射/控制内存共享)
4. 应用
二、 编程工具与动态库
1.gcc
-o 输出文件名
-O -O0 -O1 -O2 -O3 编译优化
-g -g0 -g1 -g2 -g3 产生调试信息
-W all error
-Wall 显示所有警告
-Werror 把警告当错误
-w 关闭警告
-c 只编译不连接
-E 预编译
-S 汇编
-D 在命令行定义宏
-x 指定编译的语言类型
c++ / c / .S / none 自动判定
-std=C89 / C99
编译过程:-E -c -S 自动调用连接器
连接器 ld
补充:
.c C程序
.cpp C++程序
.CC C++程序
.h 头文件
.hpp C++里面的头文件
.o 目标文件
.a 归档文件 | 静态库文件
.so 共享文件 | 动态库文件
.i 预编译文件
.s 汇编文件
三、 静态库的编译
1. 编译过程(得到*.a文件,即achieve)
1.1. 编译成目标文件
-static 可选
gcc -c -static 代码文件.c //得到.o文件
1.2. 归档成静态库
ar工具
ar -r 在目标文件中插入静态库
ar -t 显示归档文件中的内容
ar -r 静态库文件 被归档的文件 ar -r ku.a ku1.o ku.o 得到ku.a文件
nm工具(察看函数符号表)
nm 静态库 或者 动态库 或者 目标文件 或者 执行文件
1.3. 使用静态库
gcc 代码 静态库
gcc diaoyongku.c ku.a -omain
使用静态库完成如下程序:
输入一个菱形半径,打印菱形。
输入整数封装成IOTool
菱形的打印封装成Graphics
注意:静态库在 gcc diaoyongku.c ku.a -omain 后已经将静态库代码写入到主文件中,因此生成程序可以直接运行,不需要配置任何参数。
1.4 总结:
1.4.1 什么是库?
函数等代码封装的二进制已经编译的归档文件
1.4.2 ar归档工具
1.4.3 采用库的方式管理代码优点:
容易组织代码
复用
保护代码版权
1.4.4 静态库的静态的含义:
编译好的程序运行的时候不依赖库。
库作为程序的一部分编译连接。
1.4.5 静态库本质:
就是目标文件集合(归档)
1.4.6 -static可选
2. 库的规范与约定
2.1 库命名规则:
lib库名.a.主版本号.副版本号.批号
lib库名.a
2.2 库使用规则
-l 库名
-L 库所在目录
gcc main.c -omian -lku -L. //调用的ku.a文件,尾缀省略了。指定库 ku.a所在的目录:当前目录
四、 动态库的编译
1. 什么是动态库?(共享库)
动态库是可以执行,静态库不能执行。
动态库没有main,不能独立执行。
动态库不会连接成程序的一部分。
程序执行的时候,必须需要动态库文件。
2. 工具
ldd 察看程序需要调用的动态库,只能察看可执行文件(共享库或者执行文件*.elf)。
ldd mian //main函数在执行过程中调用了几个动态库
ldd
readelf -h mian 察看执行程序头.
nm 察看库中的函数符号
3. 动态库的编译
3.1 编译
-c -fpic(可选)
gcc -c -fpic iotool1.c //编译得到目标.o文件
gcc -c -fpic iotool2.c //编译得到目标.o文件
3.2 连接
-shared
gcc -shared -odemo.so iotool1.o iotool2.o//得到demo.so动态库文件
4. 使用动态库
gcc 代码 动态库文件名(这里包含尾缀和lib头)
gcc 代码 -l库名 -L动态库所在路径
gcc mian.c -ldemo -L. -omian
注:这里的使用只是完成了外部的链接与调用,即完成了编译封装工作,但是执行程序还是无法运行。这里注意,程序运行不是按照编译封装时动态库的路径去查找动态库,并加载运行的,而是有单独的机制。编译链接时只是指定主程序调用动态库中函数所在动态库中的位置,编译成功的文件在执行时还需要一个地址映射到该动态库文件的文位置,这里一般是内存中的地址映射,系统会根据动态库加载的目录先把动态库映射到内存中,在程序运行时直接去内存中访问各动态库,以提高速度。见6、7。
5. 标准命名规则:
lib库名.so 动态库
lib库名.a 静态库
-l 库名 -L 库所在路径
6. 问题:
6.1 执行程序怎么加载动态库?
见7
6.2 动态库没有作为执行程序的一部分,为什么连接需要指定动态库以及目录?
连接器需要确定函数在动态库的中的位置
7. 动态库的加载:
7.1 找到动态库
7.2 加载动态库到内存
7.3 映射到用户的内存空间
系统对动态库查找规则:
/lib
/usr/lib
到环境变量LD_LIBRARY_PATH指定的路径中查找
export LD_LIBRARY_PATH =.:~:..:~soft01
解释: . 表示当前目录
.. 表示上级目录
~ 表示主目录
: 表示分隔符
~soft01 表示指定soft01用户的主目录
缓冲机制:
把/lib:/usr/lib:LD_LIBRARY_PATH加载到缓冲。
/sbin/ldconfig -v 刷新缓冲中so的搜索数据
五、 使用libdl.so库
动态库加载的原理
动态库中函数的查找已经封装成库libdl.so,这就是为啥要用它
头文件 #include <dlfcn.h>
dlopen 打开一个动态库
dlsym 在打开动态库找一个函数
dlclose 关闭动态库
dlerror 返回错误
#include <dlfcn.h>
main()
{
void *handle=dlopen("./libdemo4.so",RTLD_LAZY);
void(*fun)(int)=dlsym(handle,"diamond");
fun(5);
dlclose(handle);
}
说明:
1. 图片中红线部分可省略,因为是系统的库,所以在系统目录及内存中已有映射,所以无需再指定。
2. 为什么需要调用libdl.so系统动态库文件?因为程序在执行过程中要用到,同时作为演示系统动态库如何使用的作用。
3. 为什么不用添加libdemo4.so动态库文件?因为在代码中用另一种方式导入了已经。
六、 总结:
6.1 编译连接动态库
6.2 使用动态库
6.3 怎么配置让程序调用动态库
6.4 掌握某些工具的使用:nm ldd lddconfig
objdump 对二进制文件进行输出
strip 去掉多余的信息.
七、 工具make的使用与makefile脚本
1. 背景:
make 编译脚本解释,通过脚本文件替代gcc
编译的脚本叫makefile
make -f 脚本文件 目标
2. 脚本文件
2.1 文本文件 demo.mk
2.2 基本构成语法:
基本单位:目标target
目标名:依赖目标
\t(这是Tab键,文件的格式要求)目标指令1
\t目标指令2
demo:
gcc iotool.c -c
gcc graphic.c -c
gcc iotool.o graphic.o -shared -olibdemo.so
gcc main.c -ldemo -omain -L.
依赖调用,空格表示多依赖