簡體   English   中英

fseek只用fread調用而不是讀取?

[英]fseek only working with fread call after rather than read?

我打開一個文件:

FILE *fp = fopen("hello_world.txt", "rb");

只有內容Hello World!

然后我得到大小並重置到開頭:

fseek(fp, 0L, SEEK_END);
size_t sz = ftell(fp);
fseek(fp, 0L, SEEK_SET);

當我去執行read ,它似乎不起作用。 read(fileno(fp), buffer, 100)返回0

但是,如果我改為做;

fread(buffer, 100, 1, fp)

這確實正確地讀入了緩沖區。

更奇怪的是,如果我將第一個fseek調用的偏移量更改為1 ,它的工作完全正常(盡管已超過文件末尾)。 我想知道為什么會這樣。 我最初的想法是它與清除EOF標志有關,但我認為至少應該在fseek回到開始時重置。 不知道為什么fread工作。 看起來我正在調用某種未定義的行為,因為在不同的機器上運行時有些東西是變化的,但我不知道為什么。

這是一個MCVE:

#include <stdio.h>
#include <unistd.h>

int main() {
     FILE *fp = fopen("hello_world.txt", "rb");
     fseek(fp, 0L, SEEK_END); // works fine if offset is 1, but read doesn't get any bytes if offset is 0
     size_t sz = ftell(fp);
     fseek(fp, 0L, SEEK_SET);
     char buffer[100];
     size_t chars_read = read(fileno(fp), buffer, 100);
     printf("Buffer: %s, chars: %lu", buffer, chars_read);
     fclose(fp);
     return 0;
 }

這個問題很棘手,但歸結為:

不要將流級別輸入/輸出和定位調用與底層系統句柄上的低級系統調用混合。

以下是對實際問題的可能解釋:

  • fseek(fp, 0L, SEEK_END); 使用系統調用lseek(fileno(fp), 0L, 2); 確定與系統句柄關聯的文件的長度。 系統返回的長度為12 ,小於流緩沖區大小, fseek()重置系統句柄位置並將12個字節讀入緩沖區,將系統句柄位置保留為12 ,將流的內部文件位置設置為12。
  • ftell(fp); 返回流的內部文件位置12.這樣做是因為流以二進制模式打開,不推薦用於文本文件,因為行結束序列不會在舊系統上轉換為換行符'\\n'
  • fseek(fp, 0L, SEEK_SET); 將流的內部文件位置設置為0 ,它位於當前緩沖的內容中,不會發出lseek()系統調用。
  • read(fileno(fp), buffer, 100); 無法讀取任何內容,因為系統句柄的當前位置是12,文件結束。
  • fread(buffer, 100, 1, fp)將從緩沖區中讀取文件內容,12個字節,嘗試從文件中讀取更多內容,沒有可用的內容,並返回讀取的字符數,12。

相反,如果你將1傳遞給fseek()會發生以下情況:

  • fseek(fp, 1L, SEEK_END); 使用系統調用lseek(fileno(fp), 0L, 2); 確定與系統句柄關聯的文件的長度。 系統返回的長度為12 ,因此請求的位置為13,小於流緩沖區大小, fseek()重置系統句柄位置並嘗試從文件中讀取13個字節到流緩沖區,但只有12個字節是可從文件中獲得。 fseek清除緩沖區並發出系統調用lseek(fileno(fp), 1L, 2); 並跟蹤流內部文件位置為13。
  • ftell(fp); 返回流內部文件位置,即13
  • fseek(fp, 0L, SEEK_SET); 將內部文件位置重置為0 ,並發出系統調用lseek(fileno(fp), 0L, 0); 因為該位置在當前流緩沖區之外。
  • read(fileno(fp), buffer, 100); 從系統句柄當前位置讀取文件內容,該位置也為0 ,因此表現如預期。

筆記:

  • 此行為無法保證,因為C標准未指定流函數的實現,但它與觀察到的行為一致。
  • 您應該檢查fseek()ftell()的返回值是否失敗。
  • 同時使用%zu作為size_t參數。
  • buffer不一定是null終止的,不要使用%sprintf打印其內容,使用%.*s並傳遞(int)chars_read作為精度值。

這是一個檢測版本:

#include <stdio.h>
#include <unistd.h>

#ifndef fileno
extern int fileno(FILE *fp); // in case fileno is not declared
#endif

int main() {
    FILE *fp = fopen("hello_world.txt", "rb");
    if (fp) {
        fseek(fp, 0L, SEEK_END);
        long sz = ftell(fp);
        fseek(fp, 0L, SEEK_SET);
        char buffer[100];
        ssize_t chars_read = read(fileno(fp), buffer, 100);
        printf("\nread(fileno(fp), buffer, 100) = %zd, Buffer: '%.*s', sz = %zu\n",
               chars_read, (int)chars_read, buffer, sz);
        fclose(fp);
    }
    fp = fopen("hello_world.txt", "rb");
    if (fp) {
        fseek(fp, 1L, SEEK_END);
        long sz = ftell(fp);
        fseek(fp, 0L, SEEK_SET);
        char buffer[100];
        ssize_t chars_read = read(fileno(fp), buffer, 100);
        printf("\nread(fileno(fp), buffer, 100) = %zd, Buffer: '%.*s', sz = %zu\n",
               chars_read, (int)chars_read, buffer, sz);
        fclose(fp);
    }
    return 0;
}

這是一個關於linux的系統調用的跟蹤符合我的暫定解釋:文件hello_world.txt包含Hello world! 沒有換行符,總共12個字節:

chqrlie$ strace ./rb612-1
...
<removed system calls related to program startup>
...
open("hello_world.txt", O_RDONLY)       = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=12, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5e356ed000
fstat(3, {st_mode=S_IFREG|0644, st_size=12, ...}) = 0
lseek(3, 0, SEEK_SET)                   = 0
read(3, "Hello world!", 12)             = 12
lseek(3, 12, SEEK_SET)                  = 12
read(3, "", 100)                        = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 1), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5e356ec000
write(1, "\n", 1
)                       = 1
write(1, "read(fileno(fp), buffer, 100) = "..., 55read(fileno(fp), buffer, 100) = 0, Buffer: '', sz = 12
) = 55
close(3)                                = 0
munmap(0x7f5e356ed000, 4096)            = 0
open("hello_world.txt", O_RDONLY)       = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=12, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5e356ed000
fstat(3, {st_mode=S_IFREG|0644, st_size=12, ...}) = 0
lseek(3, 0, SEEK_SET)                   = 0
read(3, "Hello world!", 13)             = 12
lseek(3, 1, SEEK_CUR)                   = 13
lseek(3, 0, SEEK_SET)                   = 0
read(3, "Hello world!", 100)            = 12
write(1, "\n", 1
)                       = 1
write(1, "read(fileno(fp), buffer, 100) = "..., 68read(fileno(fp), buffer, 100) = 12, Buffer: 'Hello world!', sz =
) = 68
close(3)                                = 0
munmap(0x7f5e356ed000, 4096)            = 0

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM