[英]C - binary reading, fread is inverting the order
fread(cur, 2, 1, 鰭)
我相信當我得到這個答案時我會覺得自己很愚蠢,但是發生了什么?
cur 是一個指向 code_cur 的指針,一個短的(2 個字節),fin 是一個為二進制讀取打開的流。
如果我的文件是00101000 01000000
我最終得到的是
code_cur = 01000000 00101000
這是為什么? 我還沒有舉辦任何比賽,因為問題確實歸結為這種(至少對我而言)意外的行為。
而且,如果這是常態,我怎樣才能獲得預期的效果?
聚苯乙烯
我應該補充一點,為了“查看”字節,我正在打印它們的整數值。
printf("%d\n",code_cur)
我試了幾次,看起來很可靠。
正如其他人指出的那樣,您需要了解更多關於字節序的知識。
您不知道,但您的文件(幸運的是)采用網絡字節順序(Big Endian)。 你的機器是小端的,所以需要更正。 無論是否需要,始終建議進行此更正,因為這將保證您的程序在任何地方運行。
做類似這樣的事情:
{
uint16_t tmp;
if (1 == fread(&tmp, 2, 1, fin)) { /* Check fread finished well */
code_cur = ntohs(tmp);
} else {
/* Treat error however you see fit */
perror("Error reading file");
exit(EXIT_FAILURE); // requires #include <stdlib.h>
}
}
ntohs()
會將您的值從文件順序轉換為您機器的順序,無論是大端還是小端。
這就是 htonl 和 htons(和朋友)存在的原因。 它們不是 C 標准庫的一部分,但幾乎可以在每個進行網絡連接的平台上使用。
“htonl”的意思是“主機到網絡,長”; “htons”的意思是“主機到網絡,簡稱”。 在這種情況下,“長”表示 32 位,“短”表示 16 位(即使平台聲明“長”為 64 位)。 基本上,每當您從“網絡”(或在您的情況下,您正在閱讀的流)中讀取某些內容時,您都會通過“ntoh*”傳遞它。 當你寫出來時,你通過“hton*”傳遞它
你可以用任何你想要的方式排列這些函數名,除了那些愚蠢的(不,沒有 nton,也沒有 stonl)
正如其他人指出的那樣,這是一個字節序問題。
最高有效字節在您的文件和您的機器中有所不同。 您的文件具有大端(MSB 在前),而您的機器是小端(MSB 在后或 LSB 在前)。
要了解發生了什么,讓我們創建一個包含一些二進制數據的文件:
uint8_t buffer[2] = {0x28, 0x40}; // hexadecimal for 00101000 01000000
FILE * fp = fopen("file.bin", "wb"); // opens or creates file as binary
fwrite(buffer, 1, 2, fp); // write two bytes to file
fclose(fp);
file.bin
已創建並保存二進制值 00101000 01000000,讓我們閱讀它:
uint8_t buffer[2] = {0, 0};
FILE * fp = fopen("file.bin", "rb");
fread(buffer, 1, 2, fp); // read two bytes from file
fclose(fp);
printf("0x%02x, 0x%02x\n", buffer[0], buffer[1]);
// The above prints 0x28, 0x40, as expected and in the order we wrote previously
所以一切正常,因為我們正在逐字節讀取並且字節沒有字節序(從技術上講,它們總是最重要的位,無論您的機器如何,但您可能會認為它們好像沒有簡化理解)。
無論如何,正如您所注意到的,當您嘗試直接閱讀短片時會發生以下情況:
FILE * fp_alt = fopen("file.bin", "rb");
short incorrect_short = 0;
fread(&incorrect_short, 1, 2, fp_alt);
fclose(fp_alt);
printf("Read short as machine endianess: %hu\n", incorrect_short);
printf("In hex, that is 0x%04x\n", incorrect_short);
// We get the incorrect decimal of 16424 and hex of 0x4028!
// The machine inverted our short because of the way the endianess works internally
最糟糕的是,如果您使用的是大端機器,上述結果不會返回不正確的數字,讓您不知道您的代碼是特定於端的,並且不能在處理器之間移植!
使用arpa/inet.h
ntohs
來轉換字節序很好,但我覺得很奇怪,因為它是一個完整的(非標准)庫,用於網絡通信以解決來自讀取文件的問題,它通過以下方式解決從文件中錯誤地讀取它,然后“翻譯”不正確的值,而不是正確地讀取它。
在高級語言中,我們經常看到處理從文件中讀取字節序而不是轉換值的函數,因為我們(通常)知道文件結構及其字節序,只需查看 Javascript Buffer 的readInt16BE
方法,直截了當且易於使用。
受這種簡單性的啟發,我創建了一個函數,它讀取下面的 16 位整數(但如果需要,可以很容易地更改為 8、32 或 64 位):
#include <stdint.h> // necessary for specific int types
// Advances and reads a single signed 16-bit integer from the file descriptor as Big Endian
// Writes the value to 'result' pointer
// Returns 1 if succeeds or 0 if it fails
int8_t freadInt16BE(int16_t * result, FILE * f) {
uint8_t buffer[sizeof(int16_t)];
if (!result || !f || sizeof(int16_t) != fread((void *) buffer, 1, sizeof(int16_t), f))
return 0;
*result = buffer[0] << 8 + buffer[1];
return 1;
}
用法很簡單(為簡潔起見省略了錯誤處理):
FILE * fp = fopen("file.bin", "rb"); // Open file as binary
short code_cur = 0;
freadInt16BE(&code_cur, fp);
fclose(fp);
printf("Read Big-Endian (MSB first) short: %hu\n", code_cur);
printf("In hex, that is 0x%04x\n", code_cur);
// The above code prints 0x2840 correctly (decimal: 10304)
如果文件不存在、無法打開或不包含在當前位置讀取的 2 個字節,則該函數將失敗(返回 0)。
作為獎勵,如果您碰巧找到小端格式的文件,則可以使用此函數:
// Advances and reads a single signed 16-bit integer from the file descriptor as Little Endian
// Writes the value to 'result' pointer
// Returns 1 if succeeds or 0 if it fails
int8_t freadInt16LE(int16_t * result, FILE * f) {
uint8_t buffer[sizeof(int16_t)];
if (!result || !f || sizeof(int16_t) != fread((void *) buffer, 1, sizeof(int16_t), f))
return 0;
*result = buffer[1] << 8 + buffer[0];
return 1;
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.