022 基礎 IO —— 文件

🦄 個人主頁: 小米里的大麥-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 工具箱應…