目錄
一、mystdio.h
代碼思路分析
二、mystdio.c
1. 輔助函數?BuyFile
2. 文件打開函數?MyFopen
3. 文件關閉函數?MyFclose
4. 數據寫入函數?MyFwrite
1、memcpy(file->outbuffer + file->bufferlen, str, len);
2、按位與(&)運算的作用
運算規則
5. 緩沖區刷新函數?MyFFlush
1、緩沖區空檢查
2、數據寫入系統調用
3、數據同步到磁盤
4、重置緩沖區
三、Makefile
四、Makefile-st
一、mystdio.h
#pragma once
// 這是一個預處理指令,確保頭文件在編譯時只被包含一次,防止重復包含#include <stdio.h>
// 包含標準輸入輸出庫,提供基本的文件操作函數原型#define MAX 1024
// 定義輸出緩沖區的大小為1024字節// 定義刷新模式的標志位
#define NONE_FLUSH (1<<0) // 不自動刷新
#define LINE_FLUSH (1<<1) // 行緩沖模式(遇到換行符時刷新)
#define FULL_FLUSH (1<<2) // 全緩沖模式(緩沖區滿時刷新)// 自定義文件結構體,模擬標準庫的FILE結構
typedef struct IO_FILE
{int fileno; // 文件描述符(底層操作系統文件標識)int flag; // 文件打開模式標志char outbuffer[MAX]; // 輸出緩沖區int bufferlen; // 當前緩沖區中的數據長度int flush_method; // 緩沖刷新策略(NONE/LINE/FULL)
}MyFile;// 函數聲明/*** @brief 打開文件并初始化MyFile結構體* @param path 文件路徑* @param mode 打開模式("r"/"w"/"a"等)* @return 成功返回MyFile指針,失敗返回NULL*/
MyFile *MyFopen(const char *path, const char *mode);/*** @brief 關閉文件并釋放資源* @param fp MyFile指針*/
void MyFclose(MyFile *fp);/*** @brief 向文件寫入數據* @param fp MyFile指針* @param str 要寫入的數據指針* @param len 要寫入的數據長度* @return 成功寫入的字節數*/
int MyFwrite(MyFile *fp, void *str, int len);/*** @brief 手動刷新緩沖區* @param fp MyFile指針*/
void MyFFlush(MyFile *fp);
代碼思路分析
這段代碼實現了一個簡單的文件I/O封裝庫,模擬了標準C庫中的文件操作函數。主要特點包括:
-
緩沖機制:
-
使用
outbuffer
作為輸出緩沖區,減少直接系統調用的次數 -
支持三種緩沖策略:無緩沖、行緩沖和全緩沖
-
-
結構設計:
-
MyFile
結構體封裝了文件描述符、緩沖區及狀態信息 -
類似于標準庫的
FILE
結構,但更簡化
-
-
功能模擬:
-
MyFopen
:模擬fopen
,打開文件并初始化結構體 -
MyFwrite
:模擬fwrite
,寫入數據到緩沖區 -
MyFFlush
:模擬fflush
,強制刷新緩沖區 -
MyFclose
:模擬fclose
,關閉文件并釋放資源
-
-
緩沖策略:
-
NONE_FLUSH
:每次寫入都立即刷新(無緩沖) -
LINE_FLUSH
:遇到換行符時刷新(行緩沖) -
FULL_FLUSH
:緩沖區滿時刷新(全緩沖)
-
二、mystdio.c
#include "mystdio.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>static MyFile *BuyFile(int fd, int flag)
{MyFile *f = (MyFile*)malloc(sizeof(MyFile));if(f == NULL) return NULL;f->bufferlen = 0;f->fileno = fd;f->flag = flag;f->flush_method = LINE_FLUSH;memset(f->outbuffer, 0, sizeof(f->outbuffer));return f;
}MyFile *MyFopen(const char *path, const char *mode)
{int fd = -1;int flag = 0;if(strcmp(mode, "w") == 0){flag = O_CREAT | O_WRONLY | O_TRUNC;fd = open(path, flag, 0666);}else if(strcmp(mode, "a") == 0){flag = O_CREAT | O_WRONLY | O_APPEND;fd = open(path, flag, 0666);}else if(strcmp(mode, "r") == 0){flag = O_RDWR;fd = open(path, flag);}else{//TODO}if(fd < 0) return NULL;return BuyFile(fd, flag);
}
void MyFclose(MyFile *file)
{if(file->fileno < 0) return;MyFFlush(file);close(file->fileno);free(file);
}
int MyFwrite(MyFile *file, void *str, int len)
{// 1. 拷貝memcpy(file->outbuffer+file->bufferlen, str, len);file->bufferlen += len;// 2. 嘗試判斷是否滿足刷新條件!if((file->flush_method & LINE_FLUSH) && file->outbuffer[file->bufferlen-1] == '\n'){MyFFlush(file);}return 0;
}
void MyFFlush(MyFile *file)
{if(file->bufferlen <= 0) return;// 把數據從用戶拷貝到內核文件緩沖區中int n = write(file->fileno, file->outbuffer, file->bufferlen);(void)n;fsync(file->fileno);file->bufferlen = 0;}
1. 輔助函數?BuyFile
BuyFile
?函數在這個自定義 I/O 庫中模擬的是標準 C 庫(如 glibc)中?FILE
?結構體的初始化過程。
/*** @brief 分配并初始化MyFile結構體* @param fd 文件描述符* @param flag 文件打開標志* @return 成功返回MyFile指針,失敗返回NULL*/
static MyFile *BuyFile(int fd, int flag)
{// 分配MyFile結構體內存MyFile *f = (MyFile*)malloc(sizeof(MyFile));if(f == NULL) return NULL;// 初始化結構體成員f->bufferlen = 0; // 緩沖區初始為空f->fileno = fd; // 設置文件描述符f->flag = flag; // 設置文件打開標志f->flush_method = LINE_FLUSH; // 默認使用行緩沖模式memset(f->outbuffer, 0, sizeof(f->outbuffer)); // 清空緩沖區return f;
}
功能分析:
-
這是一個靜態輔助函數,用于創建和初始化
MyFile
結構體 -
設置默認緩沖策略為
LINE_FLUSH
(行緩沖) -
清空輸出緩沖區,確保初始狀態干凈
2. 文件打開函數?MyFopen
/*** @brief 打開文件并初始化MyFile對象* @param path 文件路徑* @param mode 打開模式("w"/"a"/"r")* @return 成功返回MyFile指針,失敗返回NULL*/
MyFile *MyFopen(const char *path, const char *mode)
{int fd = -1;int flag = 0;// 根據模式設置打開標志if(strcmp(mode, "w") == 0) // 寫模式{flag = O_CREAT | O_WRONLY | O_TRUNC; // 創建文件/只寫/清空內容fd = open(path, flag, 0666); // 設置文件權限為rw-rw-rw-}else if(strcmp(mode, "a") == 0) // 追加模式{flag = O_CREAT | O_WRONLY | O_APPEND; // 創建文件/只寫/追加fd = open(path, flag, 0666);}else if(strcmp(mode, "r") == 0) // 讀模式{flag = O_RDWR; // 讀寫模式fd = open(path, flag);}else{//TODO: 可以添加其他模式支持}// 檢查文件是否成功打開if(fd < 0) return NULL;// 創建并初始化MyFile對象return BuyFile(fd, flag);
}
功能分析:
-
支持三種基本文件模式:寫("w")、追加("a")和讀("r")
-
使用系統調用
open
打開文件,獲取文件描述符 -
根據模式設置不同的打開標志:
-
"w"模式會清空文件內容
-
"a"模式會在文件末尾追加
-
"r"模式允許讀寫
-
-
最終調用
BuyFile
創建并初始化MyFile
對象
3. 文件關閉函數?MyFclose
/*** @brief 關閉文件并釋放資源* @param file MyFile對象指針*/
void MyFclose(MyFile *file)
{if(file->fileno < 0) return; // 檢查文件描述符是否有效MyFFlush(file); // 確保緩沖區數據寫入文件close(file->fileno); // 關閉文件描述符free(file); // 釋放MyFile結構體內存
}
功能分析:
-
首先刷新緩沖區,確保所有數據寫入文件
-
關閉底層文件描述符
-
釋放
MyFile
結構體內存 -
包含安全檢查,防止無效文件描述符
4. 數據寫入函數?MyFwrite
/*** @brief 向文件寫入數據* @param file MyFile對象指針* @param str 要寫入的數據指針* @param len 要寫入的數據長度* @return 總是返回0(實際實現可能需要改進)*/
int MyFwrite(MyFile *file, void *str, int len)
{// 1. 將數據拷貝到緩沖區memcpy(file->outbuffer + file->bufferlen, str, len);file->bufferlen += len; // 更新緩沖區長度// 2. 檢查是否滿足刷新條件if((file->flush_method & LINE_FLUSH) && // 如果是行緩沖模式file->outbuffer[file->bufferlen-1] == '\n') // 且最后一個字符是換行符{MyFFlush(file); // 刷新緩沖區}return 0; // 注意:實際應該返回寫入的字節數,這里需要改進
}
功能分析:
1、memcpy(file->outbuffer + file->bufferlen, str, len);
memcpy(file->outbuffer + file->bufferlen, str, len);
-
file->outbuffer
:指向文件輸出緩沖區的起始地址。 -
file->bufferlen
:當前緩沖區中已存儲的數據長度(單位:字節)。 -
file->outbuffer + file->bufferlen
:計算緩沖區中下一個空閑位置的地址(即已存數據的末尾)。 -
str
:要寫入的數據源地址(用戶傳入的字符串或二進制數據)。 -
len
:要拷貝的數據長度(單位:字節)。 -
memcpy
:標準內存拷貝函數,將?str
?的?len
?字節數據復制到目標地址。
2、按位與(&
)運算的作用
-
LINE_FLUSH
?的定義(在頭文件中):#define LINE_FLUSH (1 << 1) // 即二進制 0b10(十進制 2)
-
file->flush_method
?是一個整型變量,存儲當前的緩沖模式(可能是?NONE_FLUSH
、LINE_FLUSH
?或?FULL_FLUSH
?的組合)。
運算規則
-
按位與?
&
?會對兩個數的?二進制位?逐位比較:-
如果某一位在兩個數中都是?
1
,結果的該位才是?1
,否則是?0
。
-
-
判斷邏輯:
-
如果?
file->flush_method
?包含?LINE_FLUSH
,則?file->flush_method & LINE_FLUSH
?結果非零(true
)。 -
如果不包含,則結果為?
0
(false
)。
-
-
將數據直接拷貝到輸出緩沖區
-
實現了行緩沖邏輯:當檢測到換行符時自動刷新
-
當前實現總是返回0,實際應該返回寫入的字節數
-
缺少緩沖區滿時的處理邏輯(全緩沖模式)
5. 緩沖區刷新函數?MyFFlush
/*** @brief 刷新緩沖區,將數據寫入文件* @param file MyFile對象指針*/
void MyFFlush(MyFile *file)
{if(file->bufferlen <= 0) return; // 緩沖區為空則直接返回// 將數據從用戶緩沖區寫入內核緩沖區int n = write(file->fileno, file->outbuffer, file->bufferlen);(void)n; // 忽略返回值(實際實現應該檢查)fsync(file->fileno); // 確保數據寫入磁盤file->bufferlen = 0; // 重置緩沖區長度
}
功能分析:
-
將緩沖區內容寫入底層文件描述符
-
使用
fsync
確保數據持久化到磁盤 -
重置緩沖區長度為0
-
當前實現忽略了
write
的返回值,實際應該處理寫入錯誤情況
1、緩沖區空檢查
if(file->bufferlen <= 0) return;
-
作用:如果緩沖區中沒有數據,直接返回
-
優化意義:避免不必要的系統調用
-
潛在問題:沒有檢查?
file
?是否為 NULL(健壯性考慮)
2、數據寫入系統調用
int n = write(file->fileno, file->outbuffer, file->bufferlen);
(void)n; // 忽略返回值
-
write
?系統調用:-
參數1:文件描述符
-
參數2:要寫入的數據緩沖區
-
參數3:要寫入的字節數
-
返回值:實際寫入的字節數(可能小于請求的字節數)
-
-
問題:
-
完全忽略了?
write
?的返回值,可能導致部分寫入未被處理 -
沒有錯誤處理(如?
write
?返回 -1 表示出錯)
-
3、數據同步到磁盤
fsync(file->fileno);
-
fsync
?系統調用:-
強制將內核緩沖區中的數據寫入物理磁盤
-
確保數據持久化,不會因系統崩潰而丟失
-
-
性能影響:
-
這是一個昂貴的操作,會顯著降低 I/O 性能
-
通常只在需要強一致性保證時使用
-
4、重置緩沖區
file->bufferlen = 0;
-
作用:將緩沖區長度重置為0,表示緩沖區已空
-
實現方式:
-
只是簡單地重置長度計數器
-
沒有清空緩沖區內容(可能的安全問題)
-
三、Makefile
# 生成動態鏈接庫 libmyc.so,依賴 mystdio.o 和 mystring.o 兩個目標文件
# $@ 表示目標文件(libmyc.so),$^ 表示所有依賴文件
libmyc.so: mystdio.o mystring.ogcc -shared -o $@ $^# 編譯 mystdio.c 生成位置無關代碼(PIC)的目標文件
# -fPIC 選項是生成動態庫所必需的
# $< 表示第一個依賴文件(mystdio.c)
mystdio.o: mystdio.cgcc -fPIC -c $<# 編譯 mystring.c 生成位置無關代碼(PIC)的目標文件
mystring.o: mystring.cgcc -fPIC -c $<# 偽目標:打包輸出庫文件
.PHONY: output
output:# 創建發布目錄結構mkdir -p lib/include # 存放頭文件的目錄mkdir -p lib/mylib # 存放動態庫的目錄# 拷貝所有頭文件到include目錄cp -f *.h lib/include# 拷貝動態庫文件到mylib目錄cp -f *.so lib/mylib# 將整個lib目錄打包壓縮tar czf lib.tgz lib# 偽目標:清理生成的文件
.PHONY: clean
clean:# 刪除所有目標文件、動態庫文件和生成的目錄rm -rf *.o libmyc.so lib lib.tgz
四、Makefile-st
# 生成靜態鏈接庫 libmyc.a,依賴 mystdio.o 和 mystring.o 兩個目標文件
# 注意:與動態庫(.so)不同,靜態庫(.a)在編譯時會被完整嵌入可執行文件
# $@ 表示目標文件(libmyc.a),$^ 表示所有依賴文件
libmyc.a: mystdio.o mystring.o# 使用 ar 命令打包目標文件生成靜態庫# -r 替換現有文件 -c 創建新庫ar -rc $@ $^# 編譯 mystdio.c 生成目標文件
# 注意:靜態庫不需要 -fPIC 位置無關代碼選項(與動態庫不同)
# $< 表示第一個依賴文件(mystdio.c)
mystdio.o: mystdio.cgcc -c $<# 編譯 mystring.c 生成目標文件
mystring.o: mystring.cgcc -c $<# 偽目標:打包發布靜態庫文件
.PHONY: output
output:# 創建標準的庫發布目錄結構mkdir -p lib/include # 存放頭文件(.h)的目錄mkdir -p lib/mylib # 存放靜態庫文件(.a)的目錄# 拷貝所有頭文件到include目錄cp -f *.h lib/include# 拷貝靜態庫文件到mylib目錄(注意這里是.a文件而非.so)cp -f *.a lib/mylib# 將整個lib目錄打包壓縮,便于分發tar czf lib.tgz lib# 偽目標:清理生成的文件
.PHONY: clean
clean:# 刪除編譯過程中生成的所有中間文件# 包括:目標文件(.o)、靜態庫文件(.a)、打包目錄和壓縮包rm -rf *.o libmyc.a lib lib.tgz