Почему моя простая программа на С отображает мусор на стандартный вывод?
Рассмотрим следующую простую программу на C, которая считывает файл в буфер и отображает этот буфер на консоли:
#include<stdio.h>
main()
{
FILE *file;
char *buffer;
unsigned long fileLen;
//Open file
file = fopen("HelloWorld.txt", "rb");
if (!file)
{
fprintf(stderr, "Unable to open file %s", "HelloWorld.txt");
return;
}
//Get file length
fseek(file, 0, SEEK_END);
fileLen=ftell(file);
fseek(file, 0, SEEK_SET);
//Allocate memory
buffer=(char *)malloc(fileLen+1);
if (!buffer)
{
fprintf(stderr, "Memory error!");
fclose(file);
return;
}
//Read file contents into buffer
fread(buffer, fileLen, 1, file);
//Send buffer contents to stdout
printf("%s\n",buffer);
fclose(file);
}
Файл, который он будет читать, просто содержит:
Привет, мир!
Выход:
Hello World! ²²²²▌▌▌▌▌▌▌↔☺
Прошло много времени с тех пор, как я сделал что-то существенное в C/C++, но обычно я предполагал, что буфер был выделен больше, чем необходимо, но, похоже, это не так.
В конечном итоге fileLen равен 12, что точно.
Теперь я думаю, что я просто неправильно отображаю буфер, но я не уверен, что делаю неправильно.
Кто-нибудь может подсказать мне, что я делаю неправильно?
5 ответов
Вам нужно NUL-конец вашей строки. добавлять
buffer[fileLen] = 0;
перед печатью.
Подход JesperE будет работать, но вам может быть интересно узнать, что есть альтернативный способ справиться с этим.
Вы всегда можете напечатать строку известной длины, даже если нет NUL-терминатора, указав длину printf
как точность для строкового поля:
printf("%.*s\n", fileLen, buffer);
Это позволяет печатать строку без изменения буфера.
JesperE прав в отношении проблемы nul-termination в вашем примере, я просто добавлю, что если вы обрабатываете текстовые файлы, было бы лучше использовать fgets() или что-то подобное, так как это будет правильно обрабатывать последовательности новой строки на разных платформах и всегда будет нуль-прекратить строку для вас. Если вы действительно работаете с двоичными данными, то вам не нужно использовать printf() для вывода данных, поскольку функции printf ожидают строки, и нулевой байт в данных приведет к усечению вывода.
Ваш подход к определению размера файла путем поиска в конце файла и последующего использования ftell()
неправильно:
- Если это текстовый файл, открытый без
"b"
во втором параметре кfopen()
позвони, тогдаftell()
может не сказать вам количество символов, которые вы можете прочитать из файла. Например, Windows использует два байта для конца строки, но при чтении это одинchar
, На самом деле, возвращаемое значениеftell()
для потоков, открытых в текстовом режиме, полезно только при вызовахfseek()
, а не определять размер файла. - Если это бинарный файл, открывается с
"b"
во втором параметреfopen()
тогда у стандарта C есть это, чтобы сказать:Установка индикатора положения файла в конец файла, как с
fseek(file, 0, SEEK_END)
, имеет неопределенное поведение для двоичного потока (из-за возможных завершающих нулевых символов) или для любого потока с кодированием, зависящим от состояния, которое не обязательно заканчивается в начальном состоянии сдвига.
Итак, то, что вы делаете, не обязательно будет работать в стандартном C. Лучше всего использовать fread()
читать, и если вам нужно больше памяти, используйте realloc()
, Ваша система может предоставить mmap()
или может дать гарантии об установке индикатора положения файла в конец файла для двоичных потоков, но полагаться на них нельзя переносить.
Смотрите также этот C-FAQ: В чем разница между текстовым и двоичным вводом / выводом?,
Ты можешь использовать calloc
вместо malloc
выделить память, которая уже инициализирована. calloc
принимает дополнительные аргументы. Это полезно для размещения массивов; первый параметр calloc
указывает количество элементов в массиве, для которого вы хотите выделить память, а второй аргумент - размер каждого элемента. Поскольку размер char
всегда 1, мы можем просто пройти 1
в качестве второго аргумента:
buffer = calloc (fileLen + 1, 1);
В C нет необходимости приводить возвращаемое значение malloc
или же calloc
, Вышеуказанное гарантирует, что строка будет завершена нулем, даже если чтение файла закончилось преждевременно по какой-либо причине. calloc
занимает больше времени, чем malloc
потому что он должен обнулить всю память, которую вы просили, прежде чем отдать ее вам.