Почему моя простая программа на C отображает мусор на стандартный вывод? - PullRequest
9 голосов
/ 04 ноября 2008

Рассмотрим следующую простую программу на 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!

Вывод:

Hello World! ²²²²▌▌▌▌▌▌▌↔☺

Прошло много времени с тех пор, как я сделал что-то существенное в C / C ++, но обычно я предполагал, что буфер был выделен больше, чем необходимо, но, похоже, это не так.

fileLen заканчивается 12, что точно.

Теперь я думаю, что я просто неправильно отображаю буфер, но я не уверен, что делаю неправильно.

Может кто-нибудь подсказать мне, что я делаю неправильно?

Ответы [ 5 ]

39 голосов
/ 04 ноября 2008

Вам нужно NUL-конец вашей строки. Добавить

buffer[fileLen] = 0;

перед печатью.

28 голосов
/ 04 ноября 2008

Подход JesperE будет работать, но вам может быть интересно узнать, что есть альтернативный способ справиться с этим.

Вы всегда можете напечатать строку известной длины, даже когда NUL-терминатор отсутствует, указав длину printf в качестве точности для строкового поля:

printf("%.*s\n", fileLen, buffer);

Это позволяет печатать строку без изменения буфера.

8 голосов
/ 04 ноября 2008

JesperE прав в отношении проблемы nul-termination в вашем примере, я просто добавлю, что если вы обрабатываете текстовые файлы, было бы лучше использовать fgets () или что-то подобное, поскольку это будет правильно обрабатывать последовательности новой строки на разных платформах и всегда обнуляет строку для вас. Если вы действительно работаете с двоичными данными, то не хотите использовать printf () для вывода данных, поскольку функции printf ожидают строки, и нулевой байт в данных приведет к усечению вывода.

3 голосов
/ 16 января 2010

Ваш подход к определению размера файла путем поиска в конце файла и последующего использования ftell() неверен:

  • Если это текстовый файл, открытый без "b" во втором параметре вызова fopen(), то ftell() может не указывать количество символов, которое вы можете прочитать из файла. Например, окна используют два байта для конца строки, но при чтении это один char. Фактически, возвращаемое значение ftell() для потоков, открытых в текстовом режиме, полезно только при вызовах fseek(), а не для определения размера файла.
  • Если это бинарный файл, открытый с "b" во втором параметре в fopen(), то в стандарте C есть следующее:

    Установка индикатора положения файла в конец файла, как и в fseek(file, 0, SEEK_END), имеет неопределенное поведение для двоичного потока (из-за возможных завершающих нулевых символов) или для любого потока с кодированием, зависящим от состояния, которое не гарантированно заканчивается в начальном состоянии смены.

Итак, то, что вы делаете, не обязательно будет работать в стандартном C. Лучше всего будет использовать fread() для чтения, а если вам понадобится больше памяти, используйте realloc(). Ваша система может предоставить mmap() или может предоставить гарантии для установки индикатора положения файла в конец файла для двоичных потоков & mdash; но полагаться на них непереносимо.

См. Также этот C-FAQ: В чем разница между текстовым и двоичным вводом / выводом? .

0 голосов
/ 16 января 2010

Вы можете использовать calloc вместо malloc для выделения памяти, которая уже инициализирована. calloc принимает дополнительные аргументы. Это полезно для размещения массивов; первый параметр calloc указывает количество элементов в массиве, для которого вы хотите выделить память, а второй аргумент - размер каждого элемента. Поскольку размер char всегда равен 1, мы можем просто передать 1 в качестве второго аргумента:

 buffer = calloc (fileLen + 1, 1);

В C нет необходимости приводить возвращаемое значение malloc или calloc. Вышеуказанное гарантирует, что строка будет завершена нулем, даже если чтение файла закончилось преждевременно по какой-либо причине. calloc занимает больше времени, чем malloc, потому что он должен обнулить всю запрошенную вами память, прежде чем отдать ее вам.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...