簡體   English   中英

使用標准 C 庫將文件讀取到 memory - Windows 過早識別 EOF,但適用於 Mac,ZEDC9F10A35A5D56747733

[英]Reading a file to memory using standard C library - Windows prematurely identifies EOF but works on Mac,Linux

這感覺像是最愚蠢的問題,但希望有人能提供幫助。 抱歉這篇文章太長了,但我想提供足夠的細節,以免人們建議我已經嘗試過的東西。

我已經隔離了我編寫的 C 程序的問題,該程序應該在 Mac、Linux 和 Windows 上發布。 該程序無法在 Windows 上運行,但在 Mac 和 Linux 上正常運行,並且在最近的更改之前曾經在 Windows 上運行。

失敗的直接原因與將文件讀入 memory 塊有關 - 所以我將該代碼隔離到一個獨立的程序中,並使用一些示例數據對其進行了測試,這些數據在 Windows 上可靠地失敗,並且在 Mac 和 ZEDC9F0A5A37364773647 上正常工作。

應該注意的是,在 Windows 上,我使用的是 Visual Studio 2019(版本 16.5.5)。 我正在 64 位戴爾筆記本電腦上使用 Windows 10 Enterprise 對其進行測試。 在 Linux 上,我正在使用 gcc(我正在使用 Ubuntu 20.04 對其進行測試)。 在 mac 上,我使用 clang 編譯它。 該程序旨在可移植(至少在這三個平台之間)。

加載文件的基本策略是使用 fopen() 打開它,然后使用 fseek() 測量文件,將文件標記移動到文件末尾,使用 ftell() 獲取文件內的位置,然后fseek() 回到開頭,然后使用 ftell() 獲取文件開頭的位置(實際上通常為零,但不能保證),然后我從結束位置減去開始位置確定文件大小。 這種“測量文件”代碼在實踐中似乎可以可靠地測量我關心的三個平台上的文件。

然后,我調用 malloc() 分配一個足夠大的 memory 塊來保存文件。 這總是很好。 我使用的文件大約是 200K,它們是二進制文件——但出於隔離目的,我能夠通過 271 字節的文件使其可靠地失敗。 原始代碼只是使用了一個從 0 到文件大小的 for 循環,並重復調用 getc(fileptr),然后將每個字節分配到 memory 緩沖區中。 然后它關閉了文件。 此代碼在 Mac 和 Linux 上運行良好,但在 Windows 上運行良好。 我觀察到的是我會得到文件的第一部分——在某些情況下是文件的大部分——然后我會開始從 getc(fileptr) 調用中讀回“ff”,這將填滿 memory 的 rest ——顯然錯了。

所以我研究了 getc() 和 fgetc() 之間的區別,顯然 getc() 有時可能是一個不止一次評估事物的宏。 這似乎不是一個明顯的罪魁禍首,但我還是改成了 fgetc() 並沒有改變任何東西。 我還將 malloc() 調用更改為 calloc(),以便我可以從全零開始,並更容易看到使用調試器讀取的文件(即查看 memory 緩沖區並看到它被寫入)。

我使用 Hex 編輯器創建了一個包含以下數據的文件,以便我可以使用它進行更系統的測試。 該文件包含 271 個字節。 前 256 個字節是所有可能的字節值:00 01 02 03... fc fd fe ff。 最后 16 個字節是 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f。 這樣,我可以查看問題是否是由嘗試讀取某些特定字節值引起的,並且我可以讓它繼續通過所有可能的字節值並以相同的模式再執行 16 個字節,只是為了更好地衡量,我可以輕松查看是否最后一個字節是 0f。

接下來我使用預處理器#if 0/#if 1 切換在使用 fgetc() 的文件讀取版本和使用 fread() 的版本之間切換。 這是我得到關於可能發生的事情的第一個有趣線索的地方。

在 Mac/Linux 上,該程序的兩個版本都可以正確打印我期望的值。 但是,在 Windows 上,fread() 版本讀取前 26 個字節,之后所有字節為 00(因為 calloc 將整個塊的值設置為 00,而 fread() 僅設置前 26 個字節)。 文件讀取的 getc() 版本正確讀取前 26 個字節,然后所有后續字節為 ff。

前 26 個字節是:0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f 0x10 0x11 0x12 0x13 0x14 0x15 0x16 198 0x。

Mac上程序的完整(正確)output是:

szFile: 271 Read 271 bytes load_ggx_file: 0 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f 0x10 0x11 0x12 0x13 0x14 0x15 0x16 0x17 0x18 0x19 0x1a 0x1b 0x1c 0x1d 0x1e 0x1f 0x20 0x21 0x22 0x23 0x24 0x25 0x26 0x27 0x28 0x29 0x2a 0x2b 0x2c 0x2d 0x2e 0x2f 0x30 0x31 0x32 0x33 0x34 0x35 0x36 0x37 0x38 0x39 0x3a 0x3b 0x3c 0x3d 0x3e 0x3f 0x40 0x41 0x42 0x43 0x44 0x45 0x46 0x47 0x48 0x49 0x4a 0x4b 0x4c 0x4d 0x4e 0x4f 0x50 0x51 0x52 0x53 0x54 0x55 0x56 0x57 0x58 0x59 0x5a 0x5b 0x5c 0x5d 0x5e 0x5f 0x60 0x61 0x62 0x63 0x64 0x65 0x66 0x67 0x68 0x69 0x6a 0x6b 0x6c 0x6d 0x6f 0x70 0x71 0x72 0x73 0x74 0x75 0x76 0x77 0x78 0x79 0x7a 0x7b 0x7c 0x7d 0x7e 0x7f 0x80 0x81 0x82 0x83 0x84 0x85 0x86 0x87 0x88 0x89 0x8a 0x8b 0x8c 0x8d 0x8e 0x8f 0x90 0x91 0x92 0x93 0x94 0x95 0x96 0x97 0x98 0x99 0x9a 0x9b 0x9c 0x9d 0x9e 0x9f 0xa0 0xa1 0xa2 0xa3 0xa4 0xa5 0xa6 0xa7 0xa8 0xa9 0xaa 0xab 0xac 0xad 0xae 0xaf 0xb0 0xb1 0xb2 0xb3 0xb4 0xb5 0xb6 0xb7 0xb8 0xb9 0xba 0xbb 0xbc 0xbd 0xbe 0xbf 0 xc0 0xc1 0xc2 0xc3 0xc4 0xc5 0xc6 0xc7 0xc8 0xc9 0xca 0xcb 0xcc 0xcd 0xce 0xcf 0xd0 0xd1 0xd2 0xd3 0xd4 0xd5 0xd6 0xd7 0xd8 0xd9 0xda 0xdb 0xdc 0xdd 0xde 0xdf 0xe0 0xe1 0xe2 0xe3 0xe4 0xe5 0xe6 0xe7 0xe8 0xe9 0xea 0xeb 0xec 0xed 0xee 0xef 0xf0 0xf1 0xf2 0xf3 0xf4 0xf5 0xf6 0xf7 0xf8 0xf9 0xfa 0xfb 0xfc 0xfd 0xfe 0xff 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x009 0x0a 0xb 0x00

在 Window 上使用 fread() 版本打印:

szFile: 271 ferror: 0 feof: 1 Read 26 bytes load_ggx_file: 0 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f 0x10 0x11 0x12 0x13 0x14 0x15 0x16 0x17 0x18 0x19 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00

在 Windows 上,當 fread 返回的值低於您要求的值(即我的情況下的第三個參數)時,您應該檢查 ferror() 和 feof()。 我發現ferror()返回0,feof()返回1。所以問題似乎是Windows認為它已經到了文件的末尾。 問題是它為什么會這樣認為,考慮到我的限制,什么是合理的替代方案? (即我想只使用標准庫編寫可移植的 C 代碼 - 而不是一堆特定於平台的代碼)。

我確實檢查了問題是否僅僅是由於 0x20 字符造成的。 我嘗試使用十六進制編輯器在我的測試文件中的 0x01 之后插入一個 0x20,發生的事情是它讀取並打印了該字符只是文件的表示,並且仍然在 0x19 字符之后停止。 似乎沒有任何特定的角色總是導致它窒息。

這是完整的測試程序:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

typedef struct {
    long long szFile;
    unsigned char* ggx_file;
} ggx_t;

int load_ggx_file(const char* ggx_file_path, ggx_t* outGGX)
{
    int rc;
    FILE* ggx_file;
    unsigned char c;
    long long szFile;
    long fend_offset;
    long fstart_offset;

    ggx_file = fopen(ggx_file_path, "r");
    if (!ggx_file || NULL == outGGX) {
        return -1;
    }
    rc = fseek(ggx_file, 0, SEEK_END);
    assert(0 == rc);
    fend_offset = ftell(ggx_file);

    rc = fseek(ggx_file, 0, SEEK_SET);
    assert(0 == rc);

    fstart_offset = ftell(ggx_file);
    szFile = fend_offset - fstart_offset;

    printf("szFile: %lld\r\n", szFile);

    outGGX->szFile = szFile;
    outGGX->ggx_file = (unsigned char*)calloc(szFile, 1);

    int i = 0;
#if 0
    for (; i < szFile; ++i) {
        c = fgetc(ggx_file);
        outGGX->ggx_file[i] = c;
    }
#else
    i = fread(outGGX->ggx_file, 1, szFile, ggx_file);
    if (i < szFile) {
        int rc2;
        rc2 = ferror(ggx_file);
        printf("ferror: %d\r\n", rc2);
        rc2 = feof(ggx_file);
        printf("feof: %d\r\n", rc2);
    }
#endif

    printf("Read %d bytes\r\n", i);

    fclose(ggx_file);

    return 0;
}

int main(int argc, const char* argv[]) {

    const char * ggx_file_path = argv[argc - 1];

    ggx_t ggx_file;
    int rc = load_ggx_file(ggx_file_path, &ggx_file);

    printf("load_ggx_file: %d\r\n", rc);

    for (int i = 0; i < ggx_file.szFile; ++i) {
        printf("0x%02x ", ggx_file.ggx_file[i]);
        if (0 == ((i+1) % 20)) {
            printf("\r\n");
        }
    }
    printf("\r\n");
    return 0;
}

您想以二進制模式(而不是文本模式)打開文件。
在 Un*x 下是一樣的,在 Windows 下它會阻止庫將磁盤中的某些數據替換為 memory 中的不同數據,例如"\r\n"變為"\n" "\x1B"信號EOF , ...

fopen(..., "rb") // same as "r" in Un*x

暫無
暫無

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

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