福州營銷網(wǎng)站建設(shè)模板如何制作網(wǎng)站和網(wǎng)頁
博客主頁:【夜泉_ly】
本文專欄:【C語言】
歡迎點贊👍收藏?關(guān)注??
C語言-文件操作-一些我想到的、見到的奇怪的問題
- 前言
- 1.在不關(guān)閉文件的情況下,連續(xù)多次調(diào)用 fopen() 打開同一個文件,會發(fā)生什么?
- 1.1過程
- 1.2結(jié)論
- 1.3意義
- 2.fseek如果設(shè)置到文件前的位置會發(fā)生什么?
- 2.1過程
- 1.2結(jié)論
- 3.fseek設(shè)置到單個漢字的中間會發(fā)生什么?
- 3.1過程
- 3.2結(jié)論
- 3.3意義
- 4.同時讀、寫同一個文件會發(fā)生什么?
- 4.1過程
- 4.2結(jié)論
- 5.在追加模式下使用fseek會不會覆蓋原文件?
- 5.1過程
- 5.2結(jié)論
- 6.在只讀模式下寫入會發(fā)生什么?
- 6.1過程
- 6.2結(jié)論
- 7.FILE類型的結(jié)構(gòu)體是開在堆上的嗎?如果是,free它一下會發(fā)生什么?
- 7.1過程
- 7.2結(jié)論
- 7.3意義
前言
關(guān)于C語言文件操作的文章在CSND上很多很多,我自身才疏學(xué)淺,補(bǔ)充不了什么內(nèi)容,因此,我決定換一個角度,分享一下我想到的、見到的奇怪的問題。
這個奇怪當(dāng)然也是我單方面認(rèn)為的
注:本篇只討論數(shù)據(jù)文件,且只討論純文本文件(.txt
)。
1.在不關(guān)閉文件的情況下,連續(xù)多次調(diào)用 fopen() 打開同一個文件,會發(fā)生什么?
1.1過程
為了驗證這個問題,我當(dāng)然不想創(chuàng)建很多的文件指針來一對一,因此,我嘗試使用同名指針兩次打開同一個文件:
#include <stdio.h>
int main()
{FILE* pf = fopen("test.txt", "w");printf("time 1:%p\n", pf);pf = fopen("test.txt", "w");printf("time 2:%p\n", pf);return 0;
}
運行結(jié)果如下圖:
這說明,即便是同一個文件指針,同一個文件,只要使用fopen
,系統(tǒng)都會再用一個新的文件描述符。
那么要驗證這個問題就很簡單了,代碼如下:
#include <stdio.h>
int main()
{FILE* pf;int count = 1;while (1){pf = fopen("test.txt", "w");if (pf == NULL){printf("time %d::", count);perror("");break;}printf("time %d:%p\n", count++, pf);}return 0;
}
運行結(jié)果如下圖:
可以看見當(dāng)打開第510次時,以及沒有更多的文件描述符了。
當(dāng)然,我用的是VS2022,而在其他環(huán)境下具體次數(shù)可能會改變,但應(yīng)該還是會有個限制的。
在現(xiàn)代操作系統(tǒng)中,每個進(jìn)程可以同時打開的文件數(shù)是有限的(通??梢酝ㄟ^
ulimit -n
查看限制)。每次調(diào)用fopen()
,操作系統(tǒng)都會為文件分配一個文件描述符,文件描述符是操作系統(tǒng)為進(jìn)程管理文件資源的句柄。當(dāng)打開的文件數(shù)量超過系統(tǒng)允許的最大數(shù)量時,fopen()
將返回NULL
。
1.2結(jié)論
對文件只開不關(guān),會使得系統(tǒng)提供的文件描述符被耗盡,最后fopen
會返回一個空指針。
1.3意義
如果有人只會開文件,不會關(guān)文件,那么那個人多半在開文件時,也不會檢查文件是否打開成功🤣。
那么這樣的錯誤,就有可能出現(xiàn)了:
fputs("HaHa", NULL);
具體場景如下:
#include <stdio.h>
int main()
{FILE* pf;int count = 1;while (1){pf = fopen("test.txt", "w");if (pf == NULL){printf("time %d::", count);perror("");break;}printf("time %d:%p\n", count++, pf);}fputs("HaHa", pf);// pf 此時已經(jīng)為 NULL了return 0;
}
如果運行,程序最終會崩掉:
因此,在使用完文件后,一定要fclose
!
2.fseek如果設(shè)置到文件前的位置會發(fā)生什么?
2.1過程
有這樣的問題是因為有一天我寫了這樣一個代碼:
#include <stdio.h>
int main()
{FILE* pf = fopen("test.txt", "w");if (!pf)return 1;int ch = 'a';fputc(ch,pf);fseek(pf, -1000000, SEEK_SET);ch = 'a';fputc(ch, pf);fclose(pf);pf = NULL;return 0;
}
我在fgetc
之后用了fseek
,并把位置設(shè)置到了開始位置的-10000,之后再次使用了fgetc
。
然后程序運行成功了,并且在文件中輸入了兩個a
:
aa
為什么不是一個a
或者報錯?
其實非常簡單,因為剛進(jìn)fseek
就被彈出來了:
#include <stdio.h>
int main()
{FILE* pf = fopen("test.txt", "w");if (!pf)return 1;int ch = 'a';fputc(ch, pf);printf("fseek前的偏移量:%d\n", ftell(pf));if (fseek(pf, -1000000, SEEK_SET)) {perror("fseek failed");}printf("fseek后的偏移量:%d\n", ftell(pf));ch = 'a';fputc(ch, pf);fclose(pf);pf = NULL;return 0;
}
運行結(jié)果如下圖:
1.2結(jié)論
什么都不會發(fā)生,fseek會返回一個非零值,并設(shè)置錯誤碼(對應(yīng)的錯誤信息就是 Invalid argument
),但偏移量不會改變。
3.fseek設(shè)置到單個漢字的中間會發(fā)生什么?
3.1過程
眾所周知,一個漢字占多個字節(jié),那我用fseek
設(shè)置到這多個字節(jié)的中間會發(fā)生什么呢?
代碼如下:
#include <stdio.h>
int main()
{FILE* pf = fopen("test.txt", "w");if (!pf)return 1;fprintf(pf, "今天的日期:20240921");fclose(pf);pf = fopen("test.txt", "r");if (!pf)return 1;fseek(pf, 1, SEEK_SET);char ch[100];fscanf(pf, "%s", ch);printf("%s\n", ch);return 0;
}
運行結(jié)果如下:
3.2結(jié)論
可能會輸出亂碼。
3.3意義
在C語言中,有很多地方使用漢字會導(dǎo)致未定義行為,因此盡量避免使用漢字。
4.同時讀、寫同一個文件會發(fā)生什么?
4.1過程
代碼如下:
#include <stdio.h>int main() {FILE* write = fopen("test.txt", "w");FILE* read = fopen("test.txt", "r");if (!read || !write)return 1;fputs("Hello World!", write);char ch[100];fgets(ch, 100, read);printf("%s\n", ch);fclose(write);write = NULL;fclose(read);read = NULL;return 0;
}
這里,我在對文件寫入之后立刻讀取,最終得到下圖的結(jié)果:
但如果我在fputs
語句之后,刷新緩沖區(qū),則會正常輸出:
fputs("Hello World!", write);fflush(write);
如果我將fclose
提前,也能正常輸出:
fputs("Hello World!", write);fclose(write);write = NULL;
4.2結(jié)論
可能緩沖區(qū)沒被刷新,導(dǎo)致讀到亂碼或不完整的信息。
5.在追加模式下使用fseek會不會覆蓋原文件?
5.1過程
先寫,再追加,最后讀:
#include <stdio.h>int main()
{FILE* write = fopen("test.txt", "w");if (!write)return 1;fputs("Hello World!", write);fclose(write);write = NULL;FILE* add = fopen("test.txt", "a");if (!add)return 1;fseek(add, -10, SEEK_SET);perror("");fputs("xxxxxxxxxx", add);fclose(add);add = NULL;FILE* read = fopen("test.txt", "r");char ch[100];fgets(ch, 100, read);printf("%s\n", ch);fclose(read);read = NULL;return 0;
}
結(jié)果如下圖:
5.2結(jié)論
并不會覆蓋原文件,fseek
又是一進(jìn)去就被彈出來了(rewind
也不行,會設(shè)置在追加的起始位置)。
6.在只讀模式下寫入會發(fā)生什么?
6.1過程
我先寫入"Hello World!",然后在只讀模式下嘗試寫入信息(還加了一個perror打印錯誤信息),最后讀取文件信息。
代碼如下:
#include <stdio.h>int main() {FILE* write = fopen("test.txt", "w");if (!write)return 1;fputs("Hello World!", write);fclose(write);write = NULL;FILE* pf = fopen("test.txt", "r");if (!pf)return 1;fprintf(pf, "HaHa");perror("");fclose(pf);FILE* read = fopen("test.txt", "r");char ch[100];fgets(ch, 100, read);printf("%s\n", ch);fclose(read);read = NULL;return 0;
}
運行結(jié)果如下圖:
錯誤信息是壞的文件描述符,可能是指我的文件指針用錯了吧。
6.2結(jié)論
會拒絕寫入,原文件信息不會改變。
7.FILE類型的結(jié)構(gòu)體是開在堆上的嗎?如果是,free它一下會發(fā)生什么?
7.1過程
先簡單驗證一下VS2022中是不是開在堆上的:
#include <stdio.h>
#include <stdlib.h>
int main()
{int* a = (int*)malloc(sizeof(int));int* b = (int*)malloc(sizeof(int));FILE* pf = fopen("test.txt", "w");printf("%p\n%p\n%p", a, b, pf);return 0;
}
運行結(jié)果如下圖:
可以發(fā)現(xiàn),這三個變量的地址相近,因此,可以認(rèn)為FILE類型的結(jié)構(gòu)體是開在堆上的。
既然如此,那么free(文件指針)應(yīng)該是可以運行的:
#include <stdio.h>
#include <string.h>
int main()
{FILE* write = fopen("test.txt", "w");if (!write)return 1;fputs("Hello World!", write);fflush(write);free(write);FILE* read = fopen("test.txt", "r");char ch[100];fgets(ch, 100, read);printf("%s\n", ch);fclose(read);read = NULL;return 0;
}
運行結(jié)果如下圖:
7.2結(jié)論
在VS2022中,FILE類型的結(jié)構(gòu)體是開在堆上的,因此,free(結(jié)構(gòu)體指針)
時不會報錯。
但是,FILE類型的結(jié)構(gòu)體并不是通過 malloc
或 calloc
分配的內(nèi)存,所以使用 free
會導(dǎo)致未定義行為,如,在下一次讀取時輸出一堆亂碼。
7.3意義
雖然 FILE*
在堆上分配,但由于它是由C標(biāo)準(zhǔn)庫通過 fopen()
處理的,不應(yīng)直接使用 free()
,而應(yīng)該使用 fclose()
來正確釋放資源!!!
希望本篇文章對你有所幫助!并激發(fā)你進(jìn)一步探索編程的興趣!
本人僅是個C語言初學(xué)者,如果你有任何疑問或建議,歡迎隨時留言討論!讓我們一起學(xué)習(xí),共同進(jìn)步!