【精讲】C语言库函数
一、系统调用和库函数
1.系统调用
操作系统向用户提供的函数接口,通过这些函数接口进入内核空间,使用内核提供的功能
2.库函数
别人实现好的函数接口,使用者不需要自己实现,直接调用就可以
-
GNU C库(glibc)提供的函数接口
open/read/write/lseek/close 这些函数接口直接和系统调用关联
-
标准C库提供的函数接口:
fopen/fgetc/fgets/fread/fputc/fputs/fwrite/fseek/ftell/rewind/fclose
问题:库和实现的函数的代码有什么区别?
实现的代码是一个文本文件,可见,可以被别人使用,安全性不好
库是别人代码编译之后的二进制文件,不可见,可以被别人使用,安全性好
二、Linux 下的库
gcc编译流程:预处理、编译、汇编、链接(链接库和xxx.o文件生成一个可执行文件)
1.动态库
windows : name.dll
Linux : libname.so
当编译器链接动态库的时候,会在可执行文件的头信息中记录库的名字,便于在操作系统执行这个可执行文件的时候,让操作系统去加载对应的动态库。
注意:
- 当操作执行可执行文件时候,会先读取可执行文件的头信息,然后加载头信息中记录的动态库到内存中运行
- 在可执行文件中会调用一些函数接口,而这些函数接口并不是自己实现的,而是在动态库中实现的,所以这个程序在执行的时候,需要动态库
2.静态库
windows : name.lib
Linux : libname.a
当编译器链接静态库的时候,如果在可执行文件中有调用静态库的函数接口,则会将静态库中实现好的函数接口代码拷贝到可执行文件中
注意:
- 由于链接的时候,需要将库函数接口实现代码拷贝到可执行文件中,所以生成可执行文件积较大
- 由于可执行文件中有静态库中函数接口的实现代码,运行的时候不需要静态库
3.链接静态库和动态库的优缺点
-
动态库优点:
多个程序链接同一个动态库的时候,在执行的时候只需要加载一次动态库,占用内存体积小 -
动态库缺点:
可执行程序在运行时候,没有动态库无法运行,必须让操作系统加载动态库 -
静态库优点:
可执行在执行的时候,不需要加载动态库,可以直接运行 -
静态库优点:
多个程序链接静态库的时候,需要拷贝多份静态库的代码,占用的内存较多
4.制作动态库
<1>.将file.c编译程file.o
gcc -c file.c -o file.o
<2>.将file.o生成动态库
gcc -shared file.o -o libfile.so
<3>.相关命令
- A.查看文件中的符号信息
nm file.o 或 num libfile.so
- B.删除文件中的符号信息
strip file.o 或 strip libfile.so
5.编译器链接库
<1>告诉编译器头文件的路径
-I 头文件路径
注意:
gcc编译器默认在/usr/include目录下寻找头文件
<2>告诉编译器需要链接的库
-L 库的路径 -l库名
注意:
- A. gcc编译器默认在/usr/lib和/lib目录下寻找需要链接库文件
- B. gcc编译器默认只认识它自带的库,别人的库它不认识
6.编译器错误解析
[email protected]:~/lib/dll-lib$ gcc main.c
main.c:2:18: fatal error: head.h: No such file or directory
#include <head.h>
^
compilation terminated.
这个错误是因为编译器没有找到head.h头文件,我们可以将head.h拷贝到/usr/include目录(不推荐)
或
通过 -I 指定头文件的路径
[email protected]:~/lib/dll-lib$ gcc main.c -I ./include/
/tmp/ccDMXNiG.o: In function `main':
main.c:(.text+0x19): undefined reference to `add'
main.c:(.text+0x3d): undefined reference to `sub'
collect2: error: ld returned 1 exit status
这个错误是因为编译器没有找到add和sub函数所在的库文件
解决方法是通过-L 指定库路径 -l指定库的名字
[email protected]:~/lib/dll-lib$ gcc main.c -I ./include/ -las
/usr/bin/ld: cannot find -las
collect2: error: ld returned 1 exit status
这个错误是编译器没有找到libas.so库,我们
可以通过-L 指定库路径让编译器找到它
7.一些疑问
问题1:
编译器已经链接动态库成功,为什么操作系统运行程序的时候,还需要加载动态库?
由于链接的是动态库,编译器并没有将需要的动态库中实现的函数接口代码拷贝到可执行程序中
只是在可执行程序的头头信息中记录了动态库的名字,
所以可执行程序执行的时候需要动态库此时操作系统会加载动态库
问题2:
编译器在链接动态库的时候,已经在可执行程序的头信息中记录了动态库的名字,如何查看?
Linux 可执行程序可以看成如下结构
|头信息(elf格式) |
|机器码 |
查看的命令:
readelf -a a.out | grep "Shared"
问题3:
操作系统为什么可以加载C库成功,而无法正确加载自己的动态库?
因为操作加载动态库默认路径如下:
- /lib 和 /usr/lib目录下去寻找
- 在LD_LIBRARY_PATH环境变量记录的路径下寻找
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:自己库所在的路径
8.制作静态库
<1>.将file.c编译程file.o
gcc -c file.c -o file.o
<2>.将file.o生成动态库
ar libfile.a file.o
问题:
如果动态和静态库同时存在,编译器在链接的时候,默认链接谁?
默认链接动态库,如果想链接静态库则需要在编译的时候加上-static参数
上一篇: C语言 静态库制作
下一篇: Linux Gcc编译C语言程序