静态库制作及使用步骤:
1.将.c生成.o文件
gcc -c add.c -o add.o
2.使用ar工具制作静态库
ar rcs lib自定义库名.a 后面需要的.c文件
3.编译静态库到可执行文件中
gcc test.c 自制的库.a -o test
注意:如果程序中没有函数声明,编译器会自动给个隐式声明,但会保warning
还有头文件是静态库的配套产品。
上图中的是为了防止多次载用头文件
动态库制作及使用:
1.将.c生成.o文件,(生成与位置无关的代码 -fPIC)
gcc -c add.c -o add.o -fPIC
2.使用 gcc -shared 制作动态库
gcc -shared lib库名.so add.o sub.o div.o
3.编译可执行程序时,指定所使用的动态库。 -l:指定库名(去掉lib前缀和.so后缀) -L:指定库路径
gcc test.c -o a.out -lmymath -L./lib
4.运行可以执行程序 ./a.out 出错!!!
原因:
链接器:工作于链接阶段,工作时需要-l和-L
动态链接器:工作于程序运行阶段,工作时需要提供动态库所在目录位置。
解决方式:
【1】通过环境变量:export LD_LIBRARY_PATH=动态路径 ./a.out成功!!!(临时生效,终端重启后环境变量失效);
【2】永久生效:写入终端配置文件。.bashrc (建议使用绝对路径)
1.vi ~/.bashrc
2.写入export LD_LIBRARY_PATH=动态库路径 保存
3. …bashrc/ source .bashrc /重启 终端 -->让修改后的.bashrc生效
4. ./a.out成功!!!
【3.】 拷贝自定义动态库到./lib(标准C库所在目录位置)
【4】 配置文件法:
1. sudo vi /etc/ld.so.conf
2. 写入动态库绝对路径 保存
3. sudo ldconfig -v 使用配置文件生效
4. ./a.out 成功!!! —使用 ldd a.out 查看。
gdb调试工具:(大前提:程序是你自己写的)
基础指令:
-g:使用该参数编译可以执行文件,得到调试表。
gdb a.out
list:list 1 列出源码。根据源码指定,行号设置断点。
b/break:break 20 在20行位置设置断点。
run/r:运行程序
next/n:下一条指令(会越过函数)
step/s:下一条指令(会进入函数)
print/p:p i 查看变量的值
continue:继续执行断点后续指令。
quit:退出gdb当前调试。
其他指令:
run:使用run查找段错误出现的位置。
finish:结束当前函数调用。
set args:设置main函数命令行参数
run 字串1 字串2 … :设置main函数命令行参数
info b:查看断点信息表
b 20 if i = 5:设置条件断点。
ptype:查看变量类型。
bt:列出当前程序正存活着的栈帧。
系统read函数:
ssize_t read(int fd,void *buf,size_t count);
参数:
fd:文件描述符
buf:存数据的缓冲区
count:缓冲区大小
返回值:
成功:读到的字节数。
失败:-1,设置errno
0:读到文件末尾
-1:并且errno = EAGIN或EWOULDBLOCK,说明不是read失败,而是read在以非阻塞方式读一个设备文件(网络文件),并且文件无数据。
系统write函数:
ssize_t write(int fd,const void *buf,size_count);
参数:
fd:文件描述符
buf:待写出数据的缓冲区
count:数据大小
返回值:
成功:写入的字节数
失败:-1,设置errno
错误处理函数 与errno相关。
printf(“xxx error:%d\n”,errno);
char *strerror(int errnum);
printf(“xxx error:%d\n”,strerror(errno));
void perror(const char *s);
perror(“open”)
系统调用和库函数的区别:
两个没有谁效率高还是低,只不过运用的需求不一样;系统调用应用在快速把用户内容输入输出到终端的需求;而库函数在向终端或者磁盘输入输出大数据的时候使用。
文件描述符:
PCB进程控制块:本质 结构体。
成员:文件描述符表。
文件描述符:0/1/2/3/4/5。。。/1023 表中可用的最小的。
0 - STDIN_FILENO
1 - STDOUT_FILENO
2 - STDERR_FILENO
文件描述符图:
阻塞,非阻塞:是设备文件,网络文件的属性。
产生阻塞的场景:读设备文件。读网络文件。(读常规文件无阻塞概念)
/dev/tty – 终端文件。
open(“/dev/tty”,O_RDWR|O_NONBLOCK) —设置/dev/tty非阻塞状态(默认为阻塞状态)
fcntl函数:
int flags = fcntl(fd,F_GETFL);
flgs |= O_NONBLOCK
fcntl(fd,F_SETFL,flgs);
获取文件状态:F_GETFL;
设置文件状态:F_SETFL;
lseek函数:
off_t lseek(int fd,off_t offset,int whence);
参数:
fd:文件描述符
offset:偏移量
whence:起始偏移量:SEEK_SET/SEEK_CUR/SEEK_END
返回值:
成功:较起始位置偏移量
失败:-1 errno
应用场景:
1.文件的“读”,“写”使用同一偏移位置。
2.使用lseek获取,拓展文件大小。
3.使用lseek拓展文件大小:要想使文件大小真正拓展,必须引起IO操作。
4.使用truncate函数,直接拓展文件。
传入参数:
1.指针作为函数参数。
2.同常有const关键字修饰。
3.指针指向有效区域,在函数内部做读操作。
传出参数:
1.指针作为函数参数。
2.在函数调用之前,指针指向的空间可以无意义,但必须有效。
3.在函数内部,做写操作。
4.函数调用结束后,充当函数返回值。
传入传出参数:
1.指针作为函数参数。
2.在函数调用之前,指针指向的空间有实际意义。
3.在函数内部,先做读操作,后做写操作。
4.函数调用结束后,充当函数返回值。注意这个充当加黑寓意,就是它可以当返回值,但是不是返回值。
文件存储:
inode:本质为结构体,存储文件的属性信息。如:权限,大小,时间,用户,盘块位置。。。也叫做文件属性管理结构,大多数的inode都存储在磁盘上
**dentry:**目录项,其本质依然是结构体,重要成员变量有两个{文件名,inode,…},而文件内容(data)保存在磁盘盘块中。
stat/lstat 函数:
头文件:<sys/stat.h>
结构体对象:struct stat a;
int stat(const char *path,struct stat *buf);
参数:
path:文件路径
buf:(传出参数)存放文件属性。
返回值:
成功:0;
失败:-1 errno;
获取文件大小:buf.st_size
获取文件类型:buf.st_mode
文件类型有以下几种:
获取文件权限:buf.st_mode
符号穿透:stat会;lstat不会。
注意:什么是符号穿透呢,详细一点讲就是比如建立软链接(快捷方式),用stat函数的话,它会直接穿透到源文件,反映出文件的类型等属性;而用lstat函数,它不会穿透到源文件,而是在建立的快捷方式这一层。
unlink函数特性:清除文件时,如果文件的硬链接数到0了,没有dentry(目录节点)对应,但该文件仍不会立马被释放。要等到所有打开该文件的进程关闭该文件,系统才会挑时间将该文件释放掉。
隐式回收:当进程结束运行时,所有该进程打开的文件会被关闭,申请的内存空间会被释放。
readlink函数
读取符号链接文件本身内容,得到链接所指向的文件名。
ssize_t readlink(const char *path,char *buf,size_t bufsiz);
成功:返回实际读到的字节数;
失败:-1设置errno为相应值。
rename函数
重命名一个文件。
int rename(const char *oldpath,const char *newpath);
成功:0;
失败:-1设置errno为相应值。
目录操作
目录操作函数:
DIR *opendir(char *name);
int close(DIR *dp);
struct dirent *readdir(DIR *dp);
struct dirent{
innode;
char dname[256];
}
重定向函数
int dup(int oldfd);
int dup2(int oldfd,int newfd);
头文件:
#include <unistd.h>
dup函数是把原来open打开的函数的文件描述符大小给一个新的文件描述符,dup2函数是后面指向前面的区域(也是把旧的文件描述符给新的文件描述符)