一、什么是文件描述符?文件描述符有什么特点?什么是文件描述符溢出?如何避免?
文件描述符是系统为了标识一个进程所访问的文件而分配的非负整数。
文件描述符特点
1.非负整数标识:像0、1、2这类数字常被用来表示标准输入(stdin)、标准输出(stdout)、标准错误(stderr)。
2.进程私有:每一个进程都有属于自己的文件描述符表,相同的文件描述符在不同进程中所代表的文件可能不一样。
3.最小未使用原则:系统在分配文件描述符时,会优先选用最小的未被使用的数值。
4.抽象访问接口:不管是普通文件、网络套接字、设备,都可以用文件描述符进行操作。
5.内核维护:文件描述符对应的文件表项由内核负责维护。
文件描述符溢出
当进程打开的文件描述符数量超出了系统所允许的最大限制时,就会出现文件描述符溢出的情况。此时,系统将无法再分配新的文件描述符,从而导致类似于 EMFILE(进程级限制)或ENFILE(系统级限制)这样的错误。
溢出原因
1.文件或套接字泄漏:在使用完文件或套接字后,没有调用 close()函数关闭。
2.循环中重复打开文件:在循环中不断地打开新文件,却没有及时关闭。
3.高并发处理:在处理大量并发连接时,没有对文件描述符进行有效管理。
4.系统限制过低:系统默认的文件描述符数量上限设置地比较低。
如何避免
1.及时关闭文件描述符:
int fd = open("example.txt", O_RDONLY);
if (fd != -1)
{// 使用文件描述符进行操作close(fd); // 操作完成后立即关闭
}
2.检查打开操作的返回值:
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1)
{perror("socket failed");exit(EXIT_FAILURE);
}
二、系统默认已打开的文件分别是什么?
系统默认会打开三个标准文件,分别对应着程序最基本的输入输出操作。
1.标准输入(stdin)
其文件指针为 stdin。
主要用于程序接收来自键盘的输入内容。
像 scanf()、getchar()这类函数在执行时,默认使用的就是标准输入。
2.标准输出(stdout)
其文件指针是 stdout。
作用是将程序产生的输出显示在屏幕上。
printf()、puts()默认会把内容输出到标准输出。
3.标准错误(stderr)
文件指针为 stderr。
用于输出程序运行过程中出现的错误信息。
它与标准输出是独立的,把标准错误的输出内容显示在屏幕上。
int main()
{char name[50];// 从标准输入读取数据printf("请输入您的姓名:");fgets(name, sizeof(name), stdin);// 向标准输出写入数据printf("您好,%s", name);// 向标准错误写入数据fprintf(stderr, "这是一条错误信息示例\n");return 0;
}
三、给一个文件(aaa.bin)设置可读可写可执行权限,命令是什么?
可以用 chmod ()函数来实现
int main()
{const char *filename = "aaa.bin";int result = chmod(filename, 0777);if (result == 0) {printf("文件权限设置成功!\n");} else {perror("文件权限设置失败");return 1;}return 0;
}
0777:用户、组、其他用户都拥有读、写、执行权限。
函数返回 0 ,成功,否则失败。
四、文件描述符和文件流指针是否可以相互转换?
1.文件描述符转换为文件流指针
借助 fdopen()函数:
FILE *fdopen(int fd, const char *mode);
示例:
int main()
{int fd = 1; // 标准输出的文件描述符FILE *fp = fdopen(fd, "w"); // 将文件描述符1转换为写模式的文件流指针if (fp != NULL) {fprintf(fp, "Hello, world!\n"); // 向文件流指针写入内容fclose(fp); // 关闭文件流指针(不会关闭底层文件描述符)}return 0;
}
1.转换之后,文件流指针和原文件描述符共享同一个文件偏移量。
2.关闭文件流指针时,不会关闭底层的文件描述符。
2.文件流指针转换为文件描述符
借助 fileno()函数:
int fileno(FILE *stream);
示例:
int main()
{FILE *fp = fopen("test.txt", "w"); // 打开文件,获取文件流指针if (fp != NULL) {int fd = fileno(fp); // 将文件流指针转换为文件描述符if (fd != -1) {write(fd, "Hello, world!\n", 14); // 使用文件描述符写入内容}fclose(fp); // 关闭文件流指针(会关闭底层文件描述符)}return 0;
}
五、Vim 中,需要搜索 main 这个单词,怎么做?还有哪些常用指令?
搜索 main 单词:
1.进入普通模式(按下 ESC)
2.输入 /main\>,\> 是单词边界标记,确保只品牌完整的 main 单词,而不是 maintenance 之类的。
3.按下回车键开始搜索。
4.查找下一个:n 查找上一个:N。
Vim 常用指令:
1.移动光标
基本移动:h(左移)、j(下移)、k(上移)、l(右移)。
单词移动:w(移动到下一个单词开头)、b(移动到上一个单词开头)、e(移动到下一个单词末尾)。
行内移动:0(移动到行首)、$(移动到行尾)、^(移动到第一个非空白字符)。
屏幕移动:H(移动到屏幕顶部)、M(移动到屏幕中间)、L(移动到屏幕底部)。
翻页:Ctrl + f(向前翻页)、Ctrl + b(向后翻页)。
2.编辑文本
插入模式:i(在光标前插入)、a(在光标后插入)、I(在行首插入)、A(在行尾插入)、o(在当前行下方新开一行并插入)、O(在当前行上方新开一行并插入)。
删除文本:x(删除当前字符)、dw(删除到下一个单词开头)、de(删除到当前单词末尾)、dd(删除整行)、d0(删除从光标到行首的内容)。
复制粘贴:yy(复制整行)、yw(复制下一个单词)、p(在光标后粘贴)、P(在光标前粘贴)。
撤销重做:u(撤销上一次操作)、Ctrl + r(重做被撤销的操作)。
3.保存与退出
:w:保存文件
:q:退出Vim。
:wq或:x:保存文件并退出。
:q!:不保存修改,强制退出。
4.替换
:0,$s/old/new/g:替换整个文件中的 old 为 new。
六、对于 a.out 程序中调用的函数,如果发生错误,系统是如何提供报错信息?
1.返回值与错误码
返回特殊值:像 fopen 再打开文件失败时会返回 NULL,read 读取失败时则返回 -1。
设置 errno:函数执行出错时,会把全局变量 errno 设为特定的错误码。可以利用 perror()或 strerror()函数将错误码转换为对应的错误信息。
#include <stdio.h>
#include <errno.h>int main()
{FILE *fp = fopen("nonexistent.txt", "r");if (fp == NULL) {perror("fopen failed"); // 输出:fopen failed: No such file or directoryprintf("errno = %d: %s\n", errno, strerror(errno));return 1;}// ...
}
2.标准错误输出
有些函数会直接把错误信息输出到标准错误流(stderr),比如 perror()。
七、Makefile 是什么作用?要如何使用它?
Makefile 的作用是对程序的编译流程进行自动化管理,可以提升开发效率。
主要作用:
1.自动化编译流程:借助定义一系列的规则,Makefile 可以自动处理源文件的编译顺序以及依赖关系。
2.只重新编译必要的文件:它会依据文件的修改时间来判断,仅对发生变化的文件进行重新编译,节省时间。
3.简化编译变量:无需每次输入复杂的编译命令,只需执行 make 命令就可以了。
基本结构与使用方法:
1.基本格式:
目标: 依赖项命令(必须以Tab键开头)
2.示例:
# 定义变量
CC = gcc
CFLAGS = -Wall -g# 默认目标
all: myprogram# 链接目标文件生成可执行文件
myprogram: main.o utils.o$(CC) $(CFLAGS) -o myprogram main.o utils.o# 编译main.c生成main.o
main.o: main.c utils.h$(CC) $(CFLAGS) -c main.c# 编译utils.c生成utils.o
utils.o: utils.c utils.h$(CC) $(CFLAGS) -c utils.c# 清理编译生成的文件
clean:rm -f *.o myprogram
使用 .PHONY 来声明像 all、clean 这类不对应实际文件的目标:
.PHONY: all clean
示例项目:
项目包含main.c、utils.c、utils.h三个文件:
CC = gcc
CFLAGS = -Wall -gall: myprogrammyprogram: main.o utils.o$(CC) $(CFLAGS) -o myprogram main.o utils.oclean:rm -f *.o myprogram
八、Makefile 中如何指定编译需要的头文件位置?怎么指定编译需要的库的位置?
指定头文件位置
使用 -I(大挨) 参数来指定头文件的搜索路径:
CFLAGS = -I/path/to/headers -I/path/to/more_headers# 示例规则
target: source.cgcc $(CFLAGS) -o target source.c
指定库文件位置与链接库
使用 -L 参数指定库文件的搜索路径,使用 -l (小诶漏)参数指定需要链接的库:
LDFLAGS = -L/path/to/libs -L/path/to/more_libs
LDLIBS = -lmylib -lanotherlib# 示例规则
target: source.ogcc $(LDFLAGS) -o target source.o $(LDLIBS)
完整的 Makefile 示例:
# 编译器设置
CC = gcc # 使用GCC作为C语言编译器
CFLAGS = -Wall -Iinclude # 编译选项:-Wall开启所有警告,-I指定头文件搜索路径
LDFLAGS = -Llib # 链接选项:-L指定库文件搜索路径
LDLIBS = -lmylib # 需要链接的库:-l指定库名(自动查找libmylib.so或libmylib.a)# 文件与目标定义
SRCS = src/main.c src/module.c # 源文件列表
OBJS = $(SRCS:.c=.o) # 自动生成目标文件列表(.c替换为.o)
TARGET = myprogram # 最终生成的可执行文件名称# 默认目标(执行make时的默认动作)
all: $(TARGET)# 链接规则:如何从目标文件生成可执行文件
$(TARGET): $(OBJS)$(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS) # $@表示目标文件,$^表示所有依赖文件# 编译规则:通用规则,如何从.c文件生成.o文件
%.o: %.c$(CC) $(CFLAGS) -c $< -o $@ # $<表示第一个依赖文件(.c文件)# 清理规则:删除生成的文件
clean:rm -f $(OBJS) $(TARGET) # 删除所有目标文件和可执行文件