一、GCC/G++
gcc xxx.c -o xxx
二、静态库的制作和使用
Linux中静态库的后缀为libxxx.a,windows中静态库的后缀为libxxx.lib
静态库的制作命令:
ar rcs libxxx.a xx1.o xx2.o ...
r - 将文件插入备存文件中
c - 建立备存文件
s - 索引
静态库的使用
gcc xxx.c -o xxx -I ./include -L ./lib -l xxx
三、动态库的制作和使用
Linux中动态库的后缀为libxxx.so,windows中动态库的后缀为libxxx.dll
动态库的制作:
(1)获取.o文件
gcc -c -fpic/fPIC a.c b.c ...
(2)gcc得到动态库
gcc -shared a.o b.o -o libxxx.so
使用动态库前需要先添加环境变量
方法:进入/etc/profile,将动态库的路径添加进去,并且新开终端或者手动输入命令source /etc/profile
使用动态库的方法:
gcc xxx.c -o xxx -I ./include -L ./lib -l xxx
四、MakeFile
程序编译过程
预处理
gcc -E test.c -o test.i
编译
gcc -S test.i -o test.s
汇编
gcc -c test.s -o test.o
链接
gcc test.o -o test
gcc test.c -o test
1、MakeFile变量
IMMEDIATE = DEFERRED
IMMEDIATE ?= DEFERRED
IMMEDIATE := IMMEDIATE
IMMEDIATE += DEFERRED or IMMEDIATE
“=” : 引用变量时原样展开,有点类似 C 语言里面的 define;
“?=”: 条件赋值,在之前没有赋值的情况下才给变量赋值;
“:=”: 立即赋值,在变量定义时就直接展开;
“+=”: 追加变量。
引用变量时使用$(variable)
2、自动推导规则
在使用 make 编译.c 源文件时,编译.c 源文件规则的命令可以不用明确给出。这是因为 make 本身存在一个默认的规则,能够自动完成对.c 文件的编译并生成对应的.o 文件,它执 行命令“cc -c”来编译.c 源文件
3、预定义变量
AR : 归档维护程序的名称,默认值为 ar
CC : C 编译器的名称,默认值为 cc
CXX : C++ 编译器的名称,默认值为 g++
$@ : 目标的完整名称
$< : 第一个依赖文件的名称
$^ : 所有的依赖文件
%: 通配符,匹配一个字符串,两个%匹配的是同一个字符串
如以下
add.o:add.c
gcc -c add.c
div.o:div.c
gcc -c div.c
sub.o:sub.c
gcc -c sub.c
mult.o:mult.c
gcc -c mult.c
main.o:main.c
gcc -c main.c
可转换为
%.o:%.c
CC -c $< -o $@
五、GDB调试
1、通常,在为调试而编译时,我们会()关掉编译器的优化选项(`-O`), 并打开调 试选项(`-g`)。另外,`-Wall`在尽量不影响程序行为的情况下选项打开所有 warning,也可以发现许多问题,避免一些不必要的 BUG。
例如:
gcc -g -Wall test.c -o test
2、常用GDB命令
六、Linux文件I/O
内核会为每个进程维护一个打开文件的列表,该列表称为文件表。文件表是由一些非负整数进行索引的,这些非负整数称为文件描述符。文件描述符使用int类型表示,其范围是0~1023,当函数出错无法返回文件描述符时通常返回-1。
每个进程都至少包含三个文件描述符:0、1、2分别表示标准输入、标准输出和标准错误,在Linux的C标准库中提供了三个宏来表示,分别是:STDIN_FILENO,STDOUT_FILENO,STDERR_FILENO。
1、打开文件open()
#include <sys/types>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char * name, int flags)
int open(const char * name, int flags, mode_t mode)
作用:打开文件,创建文件
参数:- const char * name:文件地址
- flags:是由一个或多个标志位的按或组合,支持三种访问模式:O_RDONLY(只读),O_WRONLY(只写),O_RDWR(读写)。同时flags还可以与其他的一些标志位进行按位或运算修改打开文件的行为,如O_APPEND、O_ASYNC等等,比较常用的是O_CREAT在文件不存在时创建文件。
- mode:当创建文件时需要指定mode的值,mode是常见的UNIX权限位集合,比如八进制的0644,最终写入磁盘的权限位是由mode参数和用户的文件创建掩码(umask)取反并执行按位与操作得到的。
2、关闭文件close()
#include <unistd>
int close (int fd)
作用:关闭文件
参数:- fd:文件描述符
3、create()函数
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int creat (const char * name, mode_t mode)
作用:创建文件,和open函数添加O_CREATE标志位作用一样
参数:- name:文件名称
- mode:常见的UNIX权限位集合
4、read()函数
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t len)
作用:读取文件
参数:- fd:文件操作符,通常是open()函数的返回值。
- buf:读取到的字节要写入的字符数组
- len:读取到的字节的长度
返回值:执行成功会返回写入buf中的字节数,失败则返回-1,到达文件末尾时返回0.实际上read()函数有很多可能的结果:
读取文件中的所有字节:
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#define BUFFER_SIZE 4096
int main() {
int fd = open("file.txt", O_RDONLY); // 打开文件,返回文件描述符
if (fd == -1) {
perror("open");
return 1;
}
char buffer[BUFFER_SIZE];
ssize_t bytesRead;
ssize_t totalBytesRead = 0;
while ((bytesRead = read(fd, buffer, sizeof(buffer))) > 0) {
totalBytesRead += bytesRead;
// 在这里可以对读取到的数据进行处理,比如输出到标准输出
}
if (bytesRead == -1) {
perror("read");
return 1;
}
close(fd); // 关闭文件
return 0;
}
5、write()函数
#include <unistd.h>
ssize_t write(int fd, const void * buf, size_t count)
参数:- fd :文件描述符
- buf:写入的字符
- count:写入字符的字节数
返回值:执行成功返回写入的字节数并更新文件位置,失败返回-1。
Append模式:当以Append模式打开文件描述符(参数设置O_APPEND),写操作不是从文件描述符的当前位置开始,而是从当前文件的末尾开始。这样可以保证即使存在多个进程,所有的写操作还是能够保证是追加写,比如日志更新操作。
6、lseek()函数
#include <sys/types.h>
#include <unistd.h>
off_t lseek (int fd, off_t offset, int whence)
参数:- fd:文件描述符
- offset:偏移量
- whence:
SEEK_SET:设置文件指针的偏移量
SEEK_CUR:设置偏移量,当前位置 + offset
SEEK_END:设置偏移量,文件大小 + offset
返回值:成功返回文件指针的位置,失败返回-1
使用方法:
1)移动文件指针到文件头:lseek(fd, 0, SEEK_SET)
2)获取当前文件指针的位置:lseek(fd, 0, SEEK_CUR)
3)获取文件长度:lseek(fd,0,SEEK_END)
4)拓展文件长度,从10b到110b:lseek(fd,100,SEEK_END)
示例,将一个文件增加100b的大小:
int main() {
int fd = open("hello.txt", O_RDWR);
if (fd == -1) {
perror("open");
return -1;
}
int ret = lseek(fd, 100, SEEK_END);
if (ret == -1) {
perror("lseek");
return -1;
}
//写一个空数据
write(fd, " ", 1);
close(fd);
return 0;
}
7、一组stat()函数
#include <sys/types.h>
#include <sys.stat.h>
#include <unistd.h>
int stat(const char *pathname, struct stat *statbuf);
参数:- pathname:文件路径
- statbuff:结构体变量,传出参数,用来保存获取到的文件信息
作用:返回由参数path指定的文件的文件信息
int lstat(const char *pathname, struct stat *statbuf);
参数:- pathname:文件路径
- statbuf:结构体变量,传出参数,用来保存获取到的文件信息
作用:用于获取软连接文件的信息
int fstat(int fd, struct stat *statbuf);
参数:- pathname:文件路径
- statbuf:结构体变量,传出参数,用来保存获取到的文件信息
作用:返回由文件描述符指定的文件信息
示例,打印一个文件的大小
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
int main() {
struct stat statbuf;
int ret = stat("a.txt", &statbuf);
if (ret == -1) {
perror("stat");
return -1;
}
printf("size:%ld\n", statbuf.st_size);
return 0;
}
示例:自己实现“ls -l”
//模拟实现ls -l
//-rw-r--r-- 1 root root 13 Jul 11 00:11 a.txt
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>
int main(int argc, char * argv[]) {
//判断输入参数是否正确
if (argc < 2) {
printf("%s filename\n", argv[0]);
return -1;
}
//通过stat函数获取用户传入的文件的信息
struct stat st;
int ret = stat(argv[1], &st);
if (ret == -1) {
perror("stat");
return -1;
}
//获取文件类型和文件权限
char perms[11] = {0}; //用于保存文件类型和文件权限的字符串
switch (st.st_mode & __S_IFMT) {
case __S_IFLNK:
perms[0] = 'l';
break;
case __S_IFDIR:
perms[0] = 'd';
break;
case __S_IFREG:
perms[0] = '-';
break;
case __S_IFBLK:
perms[0] = 'b';
break;
case __S_IFCHR:
perms[0] = 'c';
break;
case __S_IFSOCK:
perms[0] = 's';
break;
case __S_IFIFO:
perms[0] = 'p';
break;
default:
perms[0] = '?';
break;
}
//判断文件的访问权限
perms[1] = (st.st_mode & S_IRUSR) ? 'r' : '-';
perms[2] = (st.st_mode & S_IWUSR) ? 'w' : '-';
perms[3] = (st.st_mode & S_IXUSR) ? 'x' : '-';
//判断文件所在组的权限
perms[4] = (st.st_mode & S_IRGRP) ? 'r' : '-';
perms[5] = (st.st_mode & S_IWGRP) ? 'w' : '-';
perms[6] = (st.st_mode & S_IXGRP) ? 'x' : '-';
//其他人的权限
perms[7] = (st.st_mode & S_IROTH) ? 'r' : '-';
perms[8] = (st.st_mode & S_IWOTH) ? 'w' : '-';
perms[9] = (st.st_mode & S_IXOTH) ? 'x' : '-';
//硬连接数
int linkNum = st.st_nlink;
//文件所有者
char * fileUser = getpwuid(st.st_uid)->pw_name;
//文件所在组
char * fileGrp = getgrgid(st.st_uid)->gr_name;
//文件大小
long int fileSize = st.st_size;
//修改时间
char * time = ctime(&st.st_mtime);
//输出
char buf[1024] = {0};
sprintf(buf, "%s %d %s %s %ld %s %s", perms, linkNum, fileUser, fileGrp, fileSize, time, argv[1]);
printf("%s", buf);
return 0;
}
8、chmod()函数
#include <sys/stat.h>
int chmod(const char *pathname, mode_t mode);
参数:- pathname:文件路径
- mode:需要修改的权限
作用:修改文件的权限
示例代码:修改文件的权限
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
int main() {
int ret = chmod("a.txt", 0775);
if (ret == -1) {
perror("chmod");
return -1;
}
return 0;
}
9、truncate()函数
#include <unistd.h>
#include <sys/types.h>
int truncate(const char *path, off_t length);
参数:- path:文件路径
- length:最终文件的大小
作用:缩减或者扩展文件到指定尺寸
示例代码,讲文件的大小改为5
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
int main() {
int ret = truncate("b.txt", 5);
if (ret == -1) {
perror("truncate");
return -1;
}
return 0;
}
10、access()函数
#include <unistd.h>
int access(const char *pathname, int mode);
参数:- pathname:文件路径
- mode:R_OK:判断是否有读的权限
W_OK:判断是否有写的权限
X_OK:判断是否有执行的权限
F_OK:判断是否存在
作用:判断某个文件是否有某个权限,或者判断文件是否存在
示例代码:判断文件是否存在
#include <unistd.h>
#include <stdio.h>
int main() {
int ret = access("a.txt",F_OK);
if (ret == -1) {
perror("access");
return -1;
}
printf("文件存在!!\n");
return 0;
}
11、getcwd()函数
#include <unistd.h>
char *getcwd(char *buf, size_t size);
参数:- buf:存储路径,指向一个数组,
- size:数组的大小
返回值:返回指buf的指针
作用:获取当前的工作目录
12、chdir()函数
#include <unistd.h>
int chdir(const char *path);
参数:需要修改的工作路径
作用:修改进程的工作目录
示例,获取当前的工作路径并修改
#include <unistd.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
int main() {
//获取当前的工作目录
char buf[128];
getcwd(buf, sizeof(buf));
printf("当前的工作目录是:%s\n",buf);
//修改工作目录
int ret = chdir("/home/wmz/Desktop/Lesson/Lesson13");
if (ret == -1) {
perror("chdir");
return -1;
}
//创建一个新文件
int fd = open("chdir.txt", O_CREAT | O_RDWR, 0664);
if (fd == -1) {
perror("open");
return -1;
}
close(fd);
//获取当前的工作目录
char buf1[128];
getcwd(buf1, sizeof(buf1));
printf("当前的工作目录是:%s\n",buf1);
return 0;
}
13、opendir()函数
#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
参数:要打开的目录名称
作用:打开一个目录
返回值:成功调用会返回目录dir的文件描述符,目录流,失败返回-1
14、readdir()函数
#include <dirent.h>
struct dirent *readdir(DIR *dirp);
参数:opendir打开的文件流
作用:读取目录中的数据
返回值:一个结构体dirent代表读取到的文件信息,读取到末尾或者失败了则返回NULL
15、closedir()函数
#include <sys/types.h>
#include <dirent.h>
int closedir(DIR *dirp);
参数:opendir打开的目录流
作用:关闭opendir打开的目录流
返回值:成功时关闭目录流并返回0,失败则返回-1
示例代码,打开一个目录,并统计目录下的文件个数,然后关闭目录
#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int getFileNum(const char * path);
// 读取某个目录下所有的普通文件的个数
int main(int argc, char * argv[]) {
if(argc < 2) {
printf("%s path\n", argv[0]);
return -1;
}
int num = getFileNum(argv[1]);
printf("普通文件的个数为:%d\n", num);
return 0;
}
// 用于获取目录下所有普通文件的个数
int getFileNum(const char * path) {
// 1.打开目录
DIR * dir = opendir(path);
if(dir == NULL) {
perror("opendir");
exit(0);
}
struct dirent *ptr;
// 记录普通文件的个数
int total = 0;
while((ptr = readdir(dir)) != NULL) {
// 获取名称
char * dname = ptr->d_name;
// 忽略掉. 和..
if(strcmp(dname, ".") == 0 || strcmp(dname, "..") == 0) {
continue;
}
// 判断是否是普通文件还是目录
if(ptr->d_type == DT_DIR) {
// 目录,需要继续读取这个目录
char newpath[256];
sprintf(newpath, "%s/%s", path, dname);
total += getFileNum(newpath);
}
if(ptr->d_type == DT_REG) {
// 普通文件
total++;
}
}
// 关闭目录
closedir(dir);
return total;
}
16、dup()函数和dup2()函数
#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd)
参数:- oldfd:旧的文件描述符
- newfd:新的文件描述符
作用:dup用来复制参数oldfd所指的文件描述符,当复制成功是,返回最小的尚未被使用过的文件描述符,若有错误则返回-1
dup2
可以用参数newfd
指定新文件描述符的数值。若参数newfd
已经被程序使用,则系统就会将newfd
所指的文件关闭,若newfd
等于oldfd
,则返回newfd
,而不关闭newfd
所指的文件。
示例代码
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
int main() {
int fd1 = open("a.txt", O_RDWR | O_CREAT, 0664);
int fd2 = dup(fd1);
if (fd1 == -1) {
perror("open");
return -1;
}
if (fd2 == -1) {
perror("dup");
return -1;
}
printf("fd1: %d, fd2: %d\n", fd1, fd2);
close(fd1);
char * str = "hello world";
int ret = write(fd2, str, strlen(str));
if (ret == -1) {
perror("write");
return -1;
}
close(fd2);
return 0;
}
17、fcntl()函数
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd);
参数:- fd:文件描述符
-cmd:表示对文件描述符进行如何操作
F_DUPFD:表示复制文件描述符,复制第一个参数fd,得到新的文件描述符
int ret = fcntl(fd, F_DUPFD);
F_GETFL:获取制定文件描述符的文件状态flag
获取的flag和我们通过open函数传递的是一个东西
F_SETFL:设置文件描述符的状态flag
必选项:O_RDONLY,O_WRONLY,O_RDWR
可选项:O_APPEND,O_NONBLOCK
O_APPEND:表示追加数据
O_NONBLOCK:设置成非阻塞
阻塞和非阻塞:描述函数的调用行为
作用:根据文件描述词来操作文件的特性。
示例代码:
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
int main() {
// //1、复制文件描述符
// int fd = open("1.txt", O_RDONLY);
// int ret = fcntl(fd,F_DUPFD);
//2、获取文件状态的flag
int fd = open("1.txt", O_RDWR);
if (fd == -1) {
perror("open");
return -1;
}
//获取文件描述符的状态flag
int flag = fcntl(fd, F_GETFL);
flag = flag | O_APPEND;
//修改文件描述符状态的flag,加入O_APPEND
int ret = fcntl(fd, F_GETFL,flag);
if (ret == -1) {
perror("fcntl");
return -1;
}
char * str = "aaaa";
write(fd, str, strlen(str));
close(fd);
return 0;
}