Я пытаюсь выполнить несколько упражнений, чтобы понять разницу между записью текстовых и двоичных файлов на C, и при просмотре результатов с помощью утилиты hexdump я нахожу неожиданные результаты. Не могли бы вы помочь мне понять причину?
В частности, я пытаюсь использовать следующий код для записи текстового файла:
#include <stdio.h>
int main() {
FILE *ptr_myfile;
char c = 'a';
int numero = 12345;
ptr_myfile = fopen("test.txt","w");
if (!ptr_myfile){
printf("Unable to open file!");
return 1;
}
fwrite(&c, sizeof(char), 1, ptr_myfile);
fwrite(&numero, sizeof(int), 1, ptr_myfile);
fclose(ptr_myfile);
return 0;
}
При выполнении «cat test.txt» я обнаружил, что содержимое файла:
кот тест.txt
а90
Не могу понять, как 12345 преобразовали в 90.
Более того, если я сделаю
hexdump test.txt
0000000 3961 0030 0000
0000005
В этом случае я нахожу первый байт, записанный со значением 39. Почему? Второе значение (61) уже соответствует ascii-значению fo 'a'' (61 hex = 97 dec = 'a' ascii-код), но не может найти логическое объяснение остальным битам.
Если я изменю режим записи на двоичный файл, изменив строку
ptr_myfile=fopen("test.txt","w") by ptr_myfile=fopen("test.txt","wb")
Я не вижу никаких изменений в поведении записанного содержимого файла.
Подсказка: 12345 в шестнадцатеричном формате — это 3039.
Используйте hexdump -C
, это должно быть менее запутанным
@Someprogrammerdude, так что с fwrite я всегда пишу в двоичном формате, независимо от того, в каком режиме я открывал файл? Вы говорите, что я рассматриваю файл как двоичный вместо текста? Почему игнорируется режим «fopen»?
Да, это правильно. Десятичное значение 12345
будет записано как четыре байта 0x00003039
. Если вы хотите написать текст, используйте, например. fprintf
лайк fprintf(ptr_myfile, "%c%d", c, numero)
"Почему игнорируется режим "fopen"?" Прочитайте (не гадайте, а прочитайте), что на самом деле означает режим fopen.
@н.м. спасибо, с stackoverflow.com/questions/43777913/… Я понимаю, что единственная разница заключается в том, как преобразуются несколько символов (т.е. конец строки), либо в \r\n в текстовом режиме, либо просто \n в двоичный), но это не влияет на запись необработанных данных или текста... (я неправильно это понял).
Содержимое файла test.txt
:
$ hexdump -C test.txt
00000000 61 39 30 00 00 |a90..|
00000005
Первый байт 61
— это 'a'
, а байты после него — это прямое представление 12345
.
39 30 00 00
— это 4 байта, что является типичным размером для int
.
Обратите внимание, что это число не 0x39300000
, а 0x00003039
.
Порядок байтов записанного числа зависит от порядка байтов вашей системы.
Вы можете наблюдать это сами, используя htonl для преобразования порядка следования байтов хоста в порядок следования байтов (сетевой порядок байтов):
#include <stdio.h>
int main() {
FILE *ptr_myfile;
char c = 'a';
int numero = 12345;
ptr_myfile = fopen("test.txt","w");
if (!ptr_myfile) {
printf("Unable to open file!");
return 1;
}
// convert from host endianness to network byte order
int numero_big_endian = htonl(numero);
fwrite(&c, sizeof(char), 1, ptr_myfile);
fwrite(&numero_big_endian, sizeof(int), 1, ptr_myfile);
fclose(ptr_myfile);
return 0;
}
Это даст:
$ hexdump -C test.txt
00000000 61 00 00 30 39 |a..09|
00000005
Как видите, порядок байтов теперь обратный.
Это одна из причин, по которой вы можете не захотеть записывать двоичные данные непосредственно на диск из-за различий в порядке следования байтов.
Система с прямым порядком байтов распознает 0x00003039
как 0x39300000
, что будет 959447040
, а не 1234
.
Как уже упоминалось, fwrite
не записывает данные в строковое представление.
Если вы хотите, вы можете использовать snprintf
(или использовать fprintf
), чтобы сначала преобразовать число в строку, а затем записать его в файл:
#include <stdio.h>
#include <string.h>
int main() {
FILE *ptr_myfile;
char c = 'a';
int numero = 12345;
ptr_myfile = fopen("test.txt","w");
if (!ptr_myfile) {
printf("Unable to open file!");
return 1;
}
// convert numero to a string
char numero_str[64];
// check result of snprintf, omitted for readability
snprintf(numero_str, sizeof(numero_str), "%d", numero);
fwrite(&c, sizeof(char), 1, ptr_myfile);
fwrite(numero_str, strlen(numero_str), 1, ptr_myfile);
fclose(ptr_myfile);
return 0;
}
$ cat test.txt
a12345
Когда вы используете fwrite
, функция записи обрабатывает данные, как если бы они были двоичными определенной длины. Это никак не связано с выбранным вами ранее режимом открытия файла.
Рассмотрим следующий пример:
/** A character buffer. */
char *ascii_buf = "ABCD";
/** A buffer which contains binary representation of A, B, C, D letters in ASCII. */
uint8_t binary_buf[4] = { 65, 66, 67, 68 };
written = fwrite(ascii_buf, 1, strlen(ascii_buf), fout);
written = fwrite(binary_buf, 1, sizeof(binary_buf), fout);
Вышеупомянутые два вызова fwrite
приводят к одному и тому же выводу "ABCD"
в целевой выходной файл.
Единственная разница заключается в том, как интерпретируются данные. В первом случае ascii_buf
данные интерпретируются как символ. В то время как во втором случае binary_buf
данные интерпретируются как целые числа без знака. Там содержание одинаковое, но их представление разное.
Обычно вы хотите использовать:
fprintf
для вывода отформатированных строк в файл.fwrite
для вывода необработанных данных в файл.
С помощью
fwrite
вы записываете необработанные двоичные данные значений, а не их текстовые представления. А дляint
это обычно четыре байта данных.