🦄 个人主页: 小米里的大麦-CSDN博客
🎏 所属专栏: Linux_小米里的大麦的博客-CSDN博客
🎁 GitHub主页: 小米里的大麦的 GitHub
⚙️ 操作环境: Visual Studio 2022

在这里插入图片描述

文章目录

    • 基础 `IO` —— `C` 语言文件 `I/O` 操作基础
      • 前言
      • 1. C 语言文件操作函数汇总
        • 1. 文件打开与关闭
        • 2. 字符与字符串读写
        • 3. 格式化读写
        • 4. 二进制文件读写
        • 5. 文件定位与状态
        • 6. 其他辅助函数
      • 2. 什么是当前路径?
      • 3. `w` 总是先清空,再写入
      • 4. `a` 是追加写
        • 特性说明
        • 特性说明
        • 坑点说明
        • 坑点说明
      • 5. `open()` 函数
        • 1. `flags` 参数详解(支持“位或 |”组合使用)
        • 2. `mode` 参数(仅在 `O_CREAT` 创建新文件时才用)
        • 3. 战代码示例:打开 + 写入 + 关闭
      • 6. 文件描述符
        • 1. C 代码示例:文件描述符本质是数组下标
        • 解释说明:
        • 2. 内核层的结构体解析([推荐 linux-2.6.11.1.tar.gz —— 09-Mar-2005 00:59 44M 这个版本查看源码](https://www.kernel.org/pub/linux/kernel/v2.6/))
          • 1. `files_struct`(表示进程级的打开文件表)
          • 2. `struct file`(表示一个已打开的文件实例)
          • 3. `inode`(文件元数据结构)
        • 3. 三者之间的联系总结
        • 4. 文件描述符的分配规则
      • 7. 文件描述符 VS `FILE *`
        • 1. 文件描述符(`int fd`)
        • 2. `FILE *` 指针
        • 3. 二者之间的关系图
        • 4. 相互转换方法
        • 5. 区别对比总结
    • 共勉

基础 IO —— C 语言文件 I/O 操作基础

前言

1. 文件的基本概念

文件的定义:

  • 文件 = 内容(数据) + 属性(如权限、修改时间、所有者等)。
  • 属性是文件管理的重要依据,贯穿文件的存储、访问和控制全流程。

2. 文件的两种状态

  1. 打开的文件

    • 触发条件:由进程主动打开(如读写操作)。
    • 存储位置:加载到内存中。
    • 核心机制:
      • 进程关联:每个打开的文件需与进程绑定,形成“进程-文件”的 多对一关系1: n 关系:一个进程可打开多个文件)。
      • 内核管理:操作系统为每个打开的文件创建 文件打开对象(如 struct XXX),记录文件属性、状态及链表指针(如 next),便于统一管理。
  2. 未打开的文件

    • 存储位置:磁盘上。
    • 核心问题:如何高效组织海量未打开文件,支持 快速增删查改
    • 管理目标:通过目录结构、文件系统层级等实现文件分类与定位。

3. 操作系统如何管理打开的文件

  1. 管理原则 —— 先描述,后组织:

    • 描述:为每个打开的文件创建内核对象(如 struct file),记录文件属性(如读写位置、权限)和操作接口。
    • 组织:通过链表、哈希表等数据结构管理所有打开的文件对象,实现高效访问。
  2. 关键数据结构示例

    struct file
    {mode_t permissions;    // 文件权限off_t read_offset;     // 当前读位置struct inode *inode;   // 指向磁盘文件的元数据(如 inode)struct file *next;     // 链表指针,用于组织多个打开的文件
    };
    
  3. 核心目标

    • 高效管理大量打开的文件。
    • 确保进程间文件操作的隔离性与安全性(如通过文件描述符隔离)。

1. C 语言文件操作函数汇总

CSDN 相关文章

1. 文件打开与关闭
函数参数与模式返回值功能描述示例
fopen(const char *filename, const char *mode)
模式:"r"(读)、"w"(写覆盖)、"a"(追加)、"rb"(二进制读)等
FILE*(成功)
NULL(失败)
打开文件并返回文件指针FILE *fp = fopen("test.txt", "r");
fclose(FILE *stream)0(成功)
EOF(失败)
关闭文件流并释放资源fclose(fp);
freopen(const char *filename, const char *mode, FILE *stream)FILE*(新流)
NULL(失败)
重定向已打开的流到新文件freopen("log.txt", "a", stdout);
2. 字符与字符串读写
函数参数与说明返回值功能描述注意事项
fputc(int char, FILE *stream)写入的字符(成功)
EOF(失败)
向文件写入一个字符适用于文本文件
fgetc(FILE *stream)读取的字符(成功)
EOF(失败或结尾)
从文件读取一个字符需用 feof 检测结尾
fputs(const char *str, FILE *stream)非负值(成功)
EOF(失败)
向文件写入字符串(不自动加 \n确保字符串以 \0 结尾
fgets(char *str, int n, FILE *stream)str(成功)
NULL(失败或结尾)
从文件读取一行字符串(最多 n-1 字符)保留换行符,末尾补 \0
3. 格式化读写
函数参数与格式说明返回值功能描述示例
fprintf(FILE *stream, const char *format, ...)写入的字符数(成功)
负值(失败)
按格式向文件写入数据fprintf(fp, "Value: %d", 42);
fscanf(FILE *stream, const char *format, ...)成功匹配的参数数量(成功)
EOF(失败)
按格式从文件读取数据fscanf(fp, "%d", &num);
4. 二进制文件读写
函数参数与说明返回值功能描述注意事项
fwrite(const void *ptr, size_t size, size_t count, FILE *stream)成功写入的项数向二进制文件写入数据块参数顺序:数据指针、项大小、项数量
fread(void *ptr, size_t size, size_t count, FILE *stream)成功读取的项数从二进制文件读取数据块需检查返回值以确认实际读取量
5. 文件定位与状态
函数参数与说明返回值功能描述示例
fseek(FILE *stream, long offset, int origin)
originSEEK_SET(文件头)、SEEK_CUR(当前位置)、SEEK_END(文件尾)
0(成功)
非零(失败)
移动文件指针到指定位置fseek(fp, 10, SEEK_SET);
ftell(FILE *stream)当前偏移量(成功)
-1L(失败)
获取文件指针当前位置long pos = ftell(fp);
rewind(FILE *stream)重置文件指针到文件开头rewind(fp);
feof(FILE *stream)非零值(到结尾)
0(未到结尾)
检测文件指针是否到达结尾if (feof(fp)) { ... }
ferror(FILE *stream)非零值(有错误)
0(无错误)
检测文件操作是否出错if (ferror(fp)) { ... }
6. 其他辅助函数
函数参数与说明返回值功能描述示例
fflush(FILE *stream)0(成功)
EOF(失败)
强制将缓冲区数据写入文件fflush(fp);
remove(const char *filename)0(成功)
非零(失败)
删除指定文件remove("temp.txt");
rename(const char *oldname, const char *newname)0(成功)
非零(失败)
重命名或移动文件rename("old.txt", "new.txt");

2. 什么是当前路径?

#include <stdio.h>
#include <unistd.h>
int main()
{FILE* fp = fopen("temp.txt", "w");		// 打开文件,如果文件不存在则创建,如果存在则覆盖if (fp == NULL){perror(" fopen");					// 如果打开文件失败,打印错误信息return 1;}fclose(fp);								// 关闭文件sleep(1);								// 休眠 1 秒return 0;
}

image-20250423181745027

由上我们知道,当 fopen 以写入的方式打开一个文件时,若该文件不存在,则会自动在当前路径创建该文件,那么这里所说的当前路径指的是什么呢?答案是 进程的当前路径——cwd。验证:

#include <stdio.h>
#include <unistd.h>
int main()
{chdir("/home/hcc");						// 改变当前工作目录为/home/hcc(注意:目录不存在,可能会导致后续操作失败)printf("Pid: %d\n", getpid());          // 打印进程 IDFILE* fp = fopen("temp.txt", "w");      // 打开文件,如果文件不存在则创建,如果存在则覆盖if (fp == NULL){perror(" fopen");		            // 如果打开文件失败,打印错误信息return 1;}fclose(fp);				                // 关闭文件sleep(100);			                    // 休眠 100 秒return 0;
}

image-20250423195323426

3. w 总是先清空,再写入

fopenw 模式,当以 w 模式打开文件时:

  • 如果文件不存在,则创建新文件。
  • 如果文件已存在,则清空原有内容并重新开始写入。
#include <stdio.h>
#include <unistd.h>         // 包含系统调用的头文件,如 getpid 等
#include <string.h>         // 包含字符串处理函数的头文件,如 strlen 等int main()
{printf("Pid: %d\n", getpid());// 打开文件 temp.txt,模式为 "w",表示以写方式打开文件// 如果文件不存在,则创建该文件// 如果文件已存在,则覆盖原有内容FILE* fp = fopen("temp.txt", "w");if (fp == NULL){perror("fopen");return 1;}const char* message = "abcd";       // 定义一个字符串常量 message,字符串常量是只读的,不能被修改// 将字符串 message 写入文件// fwrite 函数用于将数据写入文件// 第一个参数是要写入的数据的指针// 第二个参数是每个数据单元的大小,这里是 1 字节(即每个字符)// 第三个参数是数据单元的数量,这里是字符串的长度加 1(包括字符串结束符'\0')// 第四个参数是文件指针fwrite(message, strlen(message) + 1, 1, fp);fclose(fp);                         // 关闭文件return 0;
}

image-20250423202401670

好像没什么问题,w 会先清空文件,再将新文件内容进行写入,但是当我们打开这个 temp.txt 文件就会发现一点问题:

image-20250423203204561

注意:w 即使不写入数据,只打开文件也会清空文件数据! 因为 w 是先清空再写入。

image-20250423204157421

echofopen 的关系

  • 底层实现echo 是 shell 命令,但其重定向功能依赖于操作系统提供的文件 I/O 机制。
  • 类比 fopen("w"):当执行 echo ... > file 时,系统调用类似 fopen(file, "w") 的操作,确保输出内容替换原有数据。

虽然 echo 本身不直接调用 fopen,但其重定向功能在底层实现了与 fopen("w") 相同的效果:覆盖原有文件内容。因此,可以认为 echo 的重定向机制在功能上模拟了 fopenw 模式。

echo "现在有文件数据哦!" > temp.txt			# 创建初始文件
echo "你好!" > temp.txt						# 使用 echo 覆盖内容
cat temp.txt								# 查看结果结果显示:temp.txt的内容被完全替换为“你好!”,证明echo的重定向行为等同于fopen的w模式。

image-20250423205108315

4. a 是追加写

特性说明
追加模式写入内容总在文件末尾,不覆盖原有数据。
自动创建文件若文件不存在,a 模式会创建新文件。
文本模式 vs 二进制默认为文本模式("a"),若需二进制追加,使用 "ab"
缓冲区依赖写入内容需等待缓冲区满或显式刷新(fflush)后才写入磁盘。

1. 基本追加写入

#include <stdio.h>
int main()
{FILE* file = fopen("log.txt", "a");             // 以追加模式打开文件if (file == NULL){perror("打开文件失败");return 1;}fprintf(file, "这是一个测试文件\n");             // 写入内容到文件末尾fclose(file);                                  // 关闭文件return 0;
}
特性说明
  • 追加行为:每次写入均追加到文件末尾,不覆盖原有内容。
  • 自动创建:若文件不存在,a 模式会自动创建新文件。

2. 文件不存在时的自动创建

#include <stdio.h>
int main()
{FILE* file = fopen("file.txt", "a");    // 文件不存在时自动创建if (file == NULL){perror("写入文件失败");return 1;}fprintf(file, "已创建并追加的文件。\n");fclose(file);return 0;
}
特性说明
  • 权限问题:若当前目录无写权限,a 模式会失败(需处理 fopen 返回的 NULL)。

3. 未关闭文件导致的数据丢失

#include <stdio.h>
int main()
{FILE* file = fopen("data.txt", "a");if (file == NULL){perror("Error");return 1;}fprintf(file, "重要数据!");  // 写入后未关闭文件// 错误:未调用 fclose(file)return 0;
}
坑点说明
  • 未关闭文件:若程序异常终止或忘记调用 fclose,缓冲区中的数据可能未写入磁盘。
  • 解决方案:始终确保写入后调用 fclose,或显式调用 fflush(file) 强制刷新缓冲区。

4. 并发写入的潜在问题

#include <stdio.h>
int main()
{FILE* file1 = fopen("shared.txt", "a");FILE* file2 = fopen("shared.txt", "a");  // 同一文件被多次打开fprintf(file1, "来自 file1 的消息\n");fprintf(file2, "来自 file2 的消息\n");fclose(file1);fclose(file2);return 0;
}
坑点说明
  • 并发写入:若多个进程/线程同时追加文件,内容可能交错(如 "来自 file2 的消息\n")。
  • 解决方案:在多线程/多进程场景中,需通过锁机制或原子操作保证顺序。

5. open() 函数

题外话:比特位方式的标志位传递。其核心原理是通过 二进制位的每一位 来表示不同的标志状态,利用位运算符(如 |&)高效地组合和检测多个标志(了解,以后详解)。

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>#define ONE (1<<0)                                          // 0001 -> 十进制 1
#define TWO (1<<1)                                          // 0010 -> 十进制 2
#define THREE (1<<2)                                        // 0100 -> 十进制 4
#define FOUR (1<<3)                                         // 1000 -> 十进制 8void show(int flags)
{if(flags&ONE) printf("hello function1\n");              // 如果第 0 位是 1if(flags&TWO) printf("hello function2\n");              // 如果第 1 位是 1if(flags&THREE) printf("hello function3\n");            // 如果第 2 位是 1if(flags&FOUR) printf("hello function4\n");             // 如果第 3 位是 1
}int main()
{printf("-----------------------------\n");show(ONE);printf("-----------------------------\n");show(TWO);printf("-----------------------------\n");show(ONE|TWO);printf("-----------------------------\n");show(ONE|TWO|THREE);printf("-----------------------------\n");show(ONE|THREE);printf("-----------------------------\n");show(THREE|FOUR);printf("-----------------------------\n");
}

open() 是 Linux 系统调用中最核心、最常用的函数之一,是一切文件/设备操作的起点。

1. 头文件

#include <fcntl.h>      // 提供 open 函数、O_RDONLY 等常量
#include <sys/types.h>
#include <sys/stat.h>

2. 函数原型 & 参数解释

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);  // 创建文件时用到

3. 参数说明

参数说明
pathname文件路径(绝对或相对路径)
flags文件打开方式和行为控制(重点!)
mode权限位(只有当创建新文件时才用!

4. 返回值

返回值含义
>= 0打开成功,返回的是 文件描述符(整数)
< 0打开失败,返回 -1具体错误原因通过 errno 查看
if (fd < 0)
{perror("open failed");
}

1. flags 参数详解(支持“位或 |”组合使用)

1. 访问方式(必须选一个)

常量说明
O_RDONLY只读打开(Read Only)
O_WRONLY只写打开(Write Only)
O_RDWR读写都打开(Read + Write)

2. 控制行为(可选,多个之间用 | 连接)

常量含义
O_CREAT文件不存在就创建(需要配合 mode 参数)
O_EXCLO_CREAT 同用,文件存在则失败(避免重复创建)
O_TRUNC打开文件时清空原内容(通常配合写)
O_APPEND写入内容追加到文件末尾
O_NONBLOCK非阻塞模式打开文件(常用于设备/管道)
O_CLOEXEC在 exec 调用时关闭该文件描述符
O_SYNC写入时直接同步到硬盘(安全但慢)
2. mode 参数(仅在 O_CREAT 创建新文件时才用)
open("file.txt", O_WRONLY | O_CREAT, 0664);
  • mode_t 用来设置 新文件的权限,与 chmod 类似
  • 常用组合:
权限数字含义说明
0664用户读写,组读写,其他只读
0644用户读写,其他只读

代码示例解读:

// 代码 1:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{int fd = open("temp.txt", O_WRONLY);if (fd < 0){printf("open file error\n");return 1;}return 0;
}// 代码 2:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{int fd = open("temp.txt", O_WRONLY | O_CREAT);if (fd < 0){printf("open file error\n");return 1;}return 0;
}// 代码 3:头文件一样,后面不再重复
int main()
{int fd = open("temp.txt", O_WRONLY | O_CREAT, 0666);if (fd < 0){printf("open file error\n");return 1;}return 0;
}// 代码 4:
int main()
{umask(0);int fd = open("temp.txt", O_WRONLY | O_CREAT, 0666);if (fd < 0){printf("open file error\n");return 1;}return 0;
}
代码编号是否自动创建文件权限设定是否受 umask 影响常见用途
1️⃣❌ 否-仅打开已存在文件
2️⃣⚠️ 可能报错❌ 缺少权限参数(可能是乱码)-不推荐用法
3️⃣✅ 创建0666 - umask(与 0666 不符,原因 umask✅ 是正常用法
4️⃣✅ 创建0666❌ 不受影响特殊场合

3. 战代码示例:打开 + 写入 + 关闭
#include <fcntl.h>                      // 包含文件控制相关的定义和函数声明
#include <unistd.h>                     // 包含 POSIX 操作函数的声明,如 open、close、write 等
#include <stdio.h>                      // 包含标准输入输出函数的声明,如 perror 等
int main()
{// 打开或创建文件,并设置为只写模式、创建文件、截断文件int fd = open("demo.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);if (fd < 0){perror("open failed");          // 打印错误信息,open failed 为自定义的错误前缀return 1;                       // 打开文件失败,返回非零值表示程序异常退出}const char* msg = "实习 / 工作要掌握 open 的实战用法!\n";       // 定义要写入文件的字符串,将字符串写入文件write(fd, msg, strlen(msg));        // 第一个参数是文件描述符,第二个是数据的指针,第三个是数据的长度close(fd);                          // 关闭文件,释放资源return 0;                           // 程序正常退出
}

6. 文件描述符

先出结论:open() 的返回值 就是文件描述符(fd),它是一个 数组下标,指向当前进程的 打开文件表(fd table) 中的一个 struct file * 指针。

int fd = open("a.txt", O_WRONLY);  // 返回 3
write(fd, "Hello", 5);             // 实际就是 write(fd_table [3], ...)

image-20250424150533845

我们通过一段完整的 C 代码 演示:“访问文件的本质,其实是 数组下标访问”。接着再来讲解内核中是如何通过 struct filestruct files_struct 等结构体来描述和管理已打开的文件。

1. C 代码示例:文件描述符本质是数组下标
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>int main()
{// 打开三个不同的文件int fd1 = open("file1.txt", O_CREAT | O_WRONLY, 0666);int fd2 = open("file2.txt", O_CREAT | O_WRONLY, 0666);int fd3 = open("file3.txt", O_CREAT | O_WRONLY, 0666);if (fd1 < 0 || fd2 < 0 || fd3 < 0){perror("open");return 1;}printf("fd1: %d\n", fd1);  // 通常是 3printf("fd2: %d\n", fd2);  // 通常是 4printf("fd3: %d\n", fd3);  // 通常是 5write(fd2, "Hello file2\n", 12);  // 通过 fd2 向 file2.txt 写入内容close(fd1);close(fd2);close(fd3);return 0;
}

输出示例(实际运行):

fd1: 3
fd2: 4
fd3: 5
解释说明:

为什么编号是从 3 开始的?012 去哪了?

答案:Linux 进程默认情况下会有 3 个缺省打开的文件描述符,分别是标准输入 0,标准输出 1,标准错误 2。0,1,2 对应的物理设备一般是:键盘,显示器,显示器。

  • 标准输入(stdin):文件描述符是 0
  • 标准输出(stdout):是 1
  • 标准错误(stderr):是 2

后续打开的文件就是从 下标 3 开始往上分配。所以:fd 本质上是一个 打开文件表的下标(int 类型的索引)

image-20250424151024593


2. 内核层的结构体解析(推荐 linux-2.6.11.1.tar.gz —— 09-Mar-2005 00:59 44M 这个版本查看源码)

用户层调用 open() 后,内核做了什么?

Linux 内核有三层数据结构来描述一个打开的文件:

1. files_struct(表示进程级的打开文件表)
struct files_struct
{struct file *fd_array[NR_OPEN];  // 进程文件描述符数组(最多可打开的文件数)
};
  • 每个进程都有一个 files_struct 实例。
  • fd_array[i] 中存的是指向 struct file 的指针。
  • 这个数组的下标就是我们用户层看到的 fd
2. struct file(表示一个已打开的文件实例)
struct file
{struct inode *f_inode;          // 指向文件的 inode 结构loff_t        f_pos;            // 当前读写位置(文件偏移量)struct file_operations *f_op;   // 操作函数表...
};
  • 表示一次文件打开操作。
  • 不同进程打开同一个文件,会有 不同的 struct file
  • struct file 直接或间接包含的属性:在磁盘的什么位置、基本属性(权限、大小、读写位置、谁打开的……)、文件的内核缓冲区信息、struct file *next 指针、引用计数 count 等。
  • 类似于“文件打开上下文”,记录偏移、标志等状态。
3. inode(文件元数据结构)
struct inode
{// 文件类型、权限、拥有者、指向数据块的指针等等
};
  • 一个 inode 代表文件系统中一个“实际的文件”。
  • 所有打开该文件的 file 都指向同一个 inode
3. 三者之间的联系总结

image-20250424150001978

小结:用户空间的文件描述符(fd)就是内核中的 struct file * 数组的下标!通过这个下标,进程就能访问并操作该文件在内核中对应的资源。

4. 文件描述符的分配规则

先出结论:Linux 内核始终分配当前未被使用的最小下标,作为新的文件描述符。当你调用 open()dup() 等函数时,内核会在 fd_array[] 中从头开始查找:寻找当前未被使用的最小下标,作为新的文件描述符(fd)。

关闭 fd 后: 它会被回收,下一次打开文件就可能重复使用这个编号。

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>int main()
{close(0);                           // 关闭标准输入(fd = 0)int fd1 = open("test1.txt", O_CREAT | O_RDWR, 0666);printf("fd1=%d\n", fd1);            // 输出 fd1 = 0close(1);                           // 关闭标准输出(fd = 1)int fd2 = open("test2.txt", O_CREAT | O_RDWR, 0666);printf("fd2=%d\n", fd2);            // 无法输出到终端(因为 stdout 已关闭),重定向到文件观察close(2);                           // 关闭标准错误(fd = 2)int fd3 = open("test3.txt", O_CREAT | O_RDWR, 0666);printf("fd3=%d\n", fd3);            // 同样无法输出到终端return 0;
}

运行与验证:

  1. 编译并运行程序,将输出重定向到文件:

    gcc test.c -o test && ./test > output.txt 2>&1
    
  2. 查看 output.txt

    fd1=0
    fd2=1
    fd3=2
    

说明:关闭 0/1/2 后,新打开的文件的描述符依次复用这些最小下标。


#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
// dprintf(fd, ...) 就是“向 fd 指定的地方 像用 printf 一样 输出内容”,非常适合标准输出关闭或重定向时使用。
int main()
{printf("原始状态:stdin=0, stdout=1, stderr=2\n");       // 先确认 fd = 0,1,2 都是开启状态close(1);                                               // 关闭标准输出(fd = 1),但保留 stdin(0) 和 stderr(2)int fd1 = open("test1.txt", O_CREAT | O_RDWR, 0666);    // 打开文件 1dprintf(2, "fd1 = %d\n", fd1);                          // 输出到 stderr(fd = 2),应为 1close(0);                                               // 关闭标准输入(fd = 0)int fd2 = open("test2.txt", O_CREAT | O_RDWR, 0666);    // 打开文件 2dprintf(2, "fd2 = %d\n", fd2);                          // 应为 0int fd3 = open("test3.txt", O_CREAT | O_RDWR, 0666);    // 打开文件 3(fd = 2 还在用)dprintf(2, "fd3 = %d\n", fd3);                          // 应为 3,因为 0 和 1 被占用,新开只能用 3return 0;
}

输出内容会写入 stderr(fd = 2),因为 stdout 被关闭。示例输出:

fd1 = 1  ← 因为 fd=1 被关闭,最小空位就是 1
fd2 = 0  ← fd=0 被关闭,最小空位变为 0
fd3 = 3  ← 此时 01 被占用,2 还在用,所以下一空位是 3

注意: 当你只关闭了 fd=1,而 02 保持开启,新文件会被分配到 1不会跳到 2,因为 2 是正在使用中的文件描述符(stderr)。所以:

  • 内核分配新 fd 的顺序是:从低到高,找第一个没用的
  • 如果你关闭了某个 fd,那么它会被下一次 open() 回收复用。
  • 只有当 0、1、2 都被关闭,才会让新文件从 0 开始重新分配。

7. 文件描述符 VS FILE *

一句话总结:文件描述符(fd) 是 Linux 系统内核的低层 I/O 机制,而 FILE \* 是 C 标准库(stdio.h)封装的高级 I/O 结构,它内部依赖文件描述符实现功能。

1. 文件描述符(int fd
  • 是 Linux 内核分配的一个 整数索引
  • 用于标识当前进程打开的某个文件(实际上是指向内核 struct file 的下标)
  • 使用 open(), read(), write(), close() 等系统调用操作
  • 属于 低级 I/O

示例:

int fd = open("a.txt", O_RDWR);			// 打开文件 "a.txt",以读写模式(O_RDWR)打开,返回文件描述符 fd
write(fd, "hello", 5);					// 向文件描述符 fd 指向的文件中写入字符串 "hello",写入长度为 5 字节
close(fd);								// 关闭文件描述符 fd,释放相关资源
2. FILE * 指针
  • stdio.h 定义的 高级抽象结构体
  • 内部其实就是封装了一个 int fd + 缓冲区 + 文件状态等信息
  • 使用 fopen(), fread(), fwrite(), fprintf(), fclose() 等函数操作
  • 属于 高级 I/O

示例:

FILE* fp = fopen("a.txt", "w");			// 以写模式("w")打开文件 "a.txt",返回文件指针 fp,注意:以 "w" 模式打开会清空文件原有内容
fprintf(fp, "hello\n");					// 使用 fprintf 向文件指针 fp 指向的文件中写入字符串 "hello\n"
fclose(fp);								// 关闭文件指针 fp,确保数据被正确写入并释放相关资源
3. 二者之间的关系图
FILE *fp ───► struct __FILE (库层) ───► int fd ───► 内核打开文件表(files_struct)
4. 相互转换方法

FILE * 获取 fd

int fd = fileno(fp);

fd 获取 FILE *

FILE *fp = fdopen(fd, "w");
5. 区别对比总结
特性文件描述符(int fd)FILE * 指针
所属层次内核层(系统调用)C 标准库(用户空间)
使用头文件<fcntl.h>, <unistd.h><stdio.h>
是否有缓冲机制❌ 无缓冲✅ 有缓冲
速度快(系统级)慢(用户态带缓冲)
函数接口open/read/writefopen/fread/fwrite
可否转换✅ 可以互相转换✅ 可以互相转换
控制精细程度更细粒度(如非阻塞、异步)比较抽象、功能丰富

实战建议:

场景建议使用
写系统调用、驱动、IO 重定向等底层功能fd(文件描述符)
做格式化文本输出、文件读写、缓存优化等FILE *(标准库)

共勉

在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/95871.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/95871.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/95871.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MNN LLM Chat iOS 流式输出优化实践

本文介绍了在 iOS 平台上使用 MNN 框架部署大语言模型&#xff08;LLM&#xff09;时&#xff0c;针对聊天应用中文字流式输出卡顿问题的优化实践。通过分析模型输出与 UI 更新不匹配、频繁刷新导致性能瓶颈以及缺乏视觉动画等问题&#xff0c;作者提出了一套包含智能流缓冲、U…

【开发技巧】VS2022+QT5+OpenCV4.10开发环境搭建QT Creator

VS2022编译器支持配置 QT5默认安装以后支持的是VS2015与VS2017&#xff0c;不支持VS2022&#xff0c;所以必须首先在Qt Creator中配置支持VS2022。配置顺序如下&#xff1a; 首先打开【工具】->【选项】 然点击Kits里面的【编译器】选项。点击Manual下面的【C】然后点击【…

【Linux系统】动静态库的制作

前言&#xff1a; 上文我们讲到了文件系统【Linux系统】详解Ext2&#xff0c;文件系统-CSDN博客 本文我们来讲讲动静态库的制作 库 【Linux】编译器gcc/g及其库的详细介绍_linux gcc 有哪些库-CSDN博客 这篇文章的第4大点&#xff0c;简单是介绍了一下库的基本概念。 静态库 静…

链式二叉树的基本操作——遍历

本文笔者将带领读者一起学习链式二叉树的一些基本语法&#xff0c;至于更难一些的插入删除等&#xff0c;笔者将在后续C更新后再次详细带领大家学习。 首先&#xff0c;在进行二叉树之前&#xff0c;我们需要一颗二叉树&#xff0c;而二叉树的初始化现阶段实现不太现实&#x…

Windows运维之以一种访问权限不允许的方式做了一个访问套接字的尝试

一、问题场景 在Windows 上运维服务过程中&#xff0c;经常会遇到运行服务&#xff0c;部署安装时候无任何问题&#xff0c;后续再某个特殊时间点&#xff0c;突然服务无法启动了。再次启动时&#xff0c;提示端口占用与以一种访问权限不允许的方式做了一个访问套接字的尝试。 …

2020/12 JLPT听力原文 问题二 3番

3番&#xff1a;レストランで、女の人と店長が話しています。店長はサラダについて、どんなアドバイスをしていますか。女&#xff1a;店長、この前話してた新しいランチメニューのサラダを作ってみたんですが、どうでしょうか。 男&#xff1a;ああ、サラダだけで満足できるっ…

芯片行业主要厂商

作为一个小白&#xff0c;每次淘宝买芯片时看到相似的命名规则&#xff1a;“OPA、AD、LT、MAX”等等时&#xff0c;我不禁好奇这些芯片行业大厂有哪些&#xff0c;所以查了些资料&#xff1a; 1. 德州仪器&#xff08;Texas Instruments, TI&#xff09; 公司概况&#xff1…

【BLE系列-第四篇】从零剖析L2CAP:信道、Credit流控、指令详解

目录 引言 一、L2CAP主要功能 二、L2CAP帧格式及信道概念 2.1 逻辑链路是什么&#xff1f; 2.2 逻辑信道的作用 2.3 L2CAP帧格式介绍 三、L2CAP信令信道 3.1 信令信道帧格式说明 3.2 信令信道指令介绍 3.2.1 信令信道指令一览表 3.2.2 Credit流控规则 引言 在BLE协…

CSS保持元素宽高比,固定元素宽高比

方法一&#xff1a; <div class"hcp-fixed-aspect-ratio-box">这里是正文内容 </div>.hcp-fixed-aspect-ratio-box {width: 50%;color: #FFFFFF;margin: 100px auto;background: #FF0000;/* 宽高比2:1&#xff0c;兼容性可能不太好 */aspect-ratio: 2 / …

数据分析小白训练营:基于python编程语言的Numpy库介绍(第三方库)(上篇)

&#xff08;一&#xff09;Numpy库的安装安装指定版本的Numpy库&#xff0c;打开命令提示符&#xff0c;输入下图内容&#xff0c;只需要将1.25.5的版本修改成个人需要的版本&#xff0c;然后按下回车键&#xff0c;numpy库就安装在python中&#xff1a;指定版本numpy库安装可…

从 Windows 到 Linux 服务器的全自动部署教程(免密登录 + 压缩 + 上传 + 启动)

一、准备工作 1. 环境说明 本地开发环境&#xff1a;Windows 服务器&#xff08;需执行部署脚本&#xff09;目标服务器&#xff1a;Linux 服务器&#xff08;需安装 node.js、pm2、unzip&#xff09;核心工具&#xff1a;7-Zip&#xff08;压缩&#xff09;、OpenSSH&#x…

智能汽车领域研发,复用云原始开发范式?

汽车电子电气架构演进趋势&#xff1a;分散的功能ECU -> 域控制器 -> 中央计算服务器汽车电子方案与架构在发展与迭代时会使用虚拟化方法几种可行的软硬一体化方案&#xff1a;多ECU&#xff0c;硬件隔离&#xff0c;硬件分区&#xff0c;车规级多核硬件架构 Hypervisor…

数据电台询价的询价要求

技术规格及主要参数 1.电台基本要求&#xff1a; 1.1 电台中的信号处理基于FPGA设计&#xff0c;采用FPGAARM高速AD/DA设计架构&#xff1b; 1.2 具备频谱感知、自主选频、跳频、扩频等功能&#xff1b; 1.3 具备链路质量信息、自组网路由信息、电池电压监测信息、北斗定位信息…

IoT/HCIP实验-5/基于WIFI的智慧农业实验(LwM2M/CoAP+PSK+ESP8266 连接到 IoTDA)

文章目录概述WIFI8266 通信模组WIFI模组也用AT指令&#xff1f;ESP8266 内置协议栈?支持的无线网络模式MCU通过串口与模组交互Wifi模组做客户端PC-AT接入路由器向本地TCP服务发数据用代码接入你家路由器已接入AP&#xff08;你家Wifi&#xff09;平台侧开发工程配置和编译工程…

定时器输出PWM波配置(呼吸灯)

使用定时器 4 通道 3 生成 PWM 波控制 LED1 &#xff0c;实现呼吸灯效果。 频率&#xff1a;2kHz&#xff0c;PSC71&#xff0c;ARR499pwm.c:#include "pwm.h" // 本模块头文件&#xff1a;应声明 pwm_init/pwm_compare_set 等原型、并包含 HAL 头//&#xff08;示…

[ai-agent]环境简介之沙盒e2b vs daytona

所谓的环境的就是agent运行在哪里&#xff0c;或者是agent和那里进行交互。 最常见的环境就是本地开发环境&#xff0c;也就是个人主机&#xff0c;但是存在问题就是没有办法出网和横向扩展。 在沙盒之前也是有其他选择的&#xff1a; 云服务器&#xff0c; 虚拟机&#xff0c;…

【前端面试题】前端面试知识点(第三十一题到第六十一题)

三十一. CSS实现垂直水平居中 实现元素的垂直水平居中是前端开发中的常见需求,主要有以下几种思路: text-align + line-height实现单行文本水平垂直居中 适用于单行文本元素,通过text-align: center实现水平居中,line-height等于容器高度实现垂直居中 text-align + vertic…

嵌入式练习项目——————抓包获取天气信息

一、内容 尝试通过实时天气接口 - 数据接口 - NowAPI此网站获取天气信息&#xff0c;实现可以发送城市查询当前天气和未来天气 二、获取请求报文 可以根据测试示例看到获取内容&#xff0c;此时数据是cJSON格式&#xff0c;我们首先要通过合适的网址抓包获取到请求报文&#x…

Python爬虫实战:研究NewsCrawl ,构建新浪和网易新闻数据采集系统

1. 引言 1.1 研究背景与意义 在信息时代,新闻作为社会动态、公众观点的重要载体,其传播速度与影响力持续扩大。传统的人工筛选与采集方式已无法满足对海量新闻数据的高效处理需求,亟需自动化工具实现大规模、结构化的新闻数据采集。网络爬虫技术作为一种按照预设规则自动抓…

PyTorch神经网络工具箱全解析:nn.Module vs nn.functional

&#x1f50d; 为何需要神经网络工具箱&#xff1f; 在仅用 Autograd 和 Tensor 实现模型时&#xff0c;开发者需手动设置参数梯度&#xff08;requires_gradTrue&#xff09;、反向传播&#xff08;backward()&#xff09;及梯度提取&#xff0c;过程繁琐且易出错。nn 工具箱应…