C: Низкоуровневое форматирование символов: (enter+newline) с использованием fgetc

Я работаю над проектом на C, который читает текстовый файл и преобразует его в массив логических значений. Сначала я читаю файл в строку размера n (это массив символов без знака), затем я использую функцию для преобразования этой строки в логический массив размера n * 8. Функция работает отлично, вопросов по этому поводу нет.

Я получаю строку из файла, используя этот код:

unsigned char *Data_in; // define pointer to string
int i;

FILE* sp = fopen("file.txt", "r"); //open file

fseek(sp, 0, SEEK_END);            // points sp to the end of file
int data_dim = ftell(sp);          // Returns the position of the pointer (amount of bytes from beginning to end)
rewind(sp);                        // points sp to the beginning of file

Data_in = (unsigned char *) malloc ( data_dim * sizeof(unsigned char) ); //allocate memory for string
unsigned char carac; //define auxiliary variable 

for(i=0; feof(sp) == 0; i++)       // while end of file is not reached (0)
{
   carac = fgetc(sp);              //read character from file to char
   Data_in[i] = carac;             // put char in its corresponding position
}
//

fclose(sp);                        //close file

Дело в том, что есть текстовый файл, сделанный Блокнотом в Windows XP. Внутри него у меня есть эта строка из 4 символов ":\n\nC" (двоеточие, клавиша ввода, клавиша ввода, заглавная C).

Вот как это выглядит в HxD (шестнадцатеричный редактор): 3A 0D 0A 0D 0A 43.

В этой таблице становится понятнее:

character             hex      decimal    binary
 :                    3A       58         0011 1010
 \n (enter+newline)   0D 0A    13 10      0000 1101 0000 1010    
 \n (enter+newline)   0D 0A    13 10      0000 1101 0000 1010
 C                    43       67         0100 0011

Теперь я запускаю программу, которая печатает эту часть в двоичном виде, поэтому я получаю:

character      hex      decimal      binary
 :             3A         58         0011 1010
 (newline)     0A         10         0000 1010    
 (newline)     0A         10         0000 1010
 C             43         67         0100 0011

Ну а теперь, когда это показано, я задаю вопросы:

  • Чтение правильное?
  • Если да, то почему он удаляет 0D?
  • Как это работает?

person Machine-Code Reader    schedule 29.05.2012    source источник
comment
Ваш английский очень понятен. Это тоже довольно очаровательно!   -  person wallyk    schedule 29.05.2012


Ответы (4)


Создайте двоичный файл fopen:

fopen("file.txt", "rb");
                    ^

В противном случае ваша стандартная библиотека просто съест \r (0x0D).


В качестве примечания, открытие файла в двоичном режиме также устраняет другую проблему, когда определенная последовательность в середине файла выглядит как EOF в DOS.

person cnicutar    schedule 29.05.2012
comment
Вот интересно, теперь работает отлично. Кроме того, ваше примечание, похоже, ответило на другой вопрос о другой проблеме, которая, как мне кажется, у меня возникла, спасибо! - person Machine-Code Reader; 29.05.2012

Это потому, что вы обрабатываете файл как файл ASCII. Если вы рассматриваете его как двоичный файл, вы сможете увидеть оба символа. Для этого используйте режим "rb" при открытии файла. Также используйте fread для чтения содержимого файла.

person Superman    schedule 29.05.2012

В дополнение к проблеме с "rb" есть еще одна ошибка: вы читаете лишний символ в конце, потому что feof(sp) остается 0 после чтения последнего символа. Он устанавливается в 1 только после того, как вы попытались прочитать последний EOF. Это распространенная ошибка новичков. Идиоматический код C для перебора входных символов:

int c;   /* int, not char due to EOF. */

while ((c = fgetc(sp)) != EOF) {
   /* Work with c. */
}
person Jens    schedule 29.05.2012

В других ответах обсуждался ввод в двоичном и текстовом режимах.

В вашем коде на самом деле есть отдельная проблема. Эта идиома для Pascal, а не C:

for (i = 0; feof(sp) == 0; i++)
{
   carac = fgetc(sp);
   Data_in[i] = carac;
}

Проблема в том, что когда fgetc() получает EOF, вы обрабатываете его как символ (вероятно, сопоставляя его с ÿ, y-umlaut, U+00FF, СТРОЧНОЙ ЛАТИНСКОЙ БУКВОЙ Y С ДИЭРЕЗИСОМ). Тест feof() неуместен; он не обнаруживает EOF перед попыткой чтения следующего символа. Кроме того, функция fgetc() и ее родственники getc() и getchar() возвращают int, а не char. Вы должны научиться использовать стандартную идиому C:

int c;
for (i = 0; (c = fgetc(sp)) != EOF; i++)
   Data_in[i] = c;

Идиома представляет собой сочетание задания и теста. Подсчет вокруг него менее стандартный; на самом деле, это, вероятно, довольно редко. Но это не так; это применимо к вашей программе.

Нет необходимости использовать feof() в большинстве кодов C; практически каждый раз, когда вы его используете, это ошибка. Не всегда; он существует для какой-то цели. Но эта цель состоит в том, чтобы различать EOF и ошибку после того, как функция, такая как fgetc(), вернула EOF, а не проверять, достигли ли вы EOF еще до того, как функция чтения скажет, что она достигла EOF. (Я не думаю, что во всех сотнях моих программ есть очень мало ссылок на feof(): 2884 исходных файла, 18 ссылок на feof(), и большинство из них в коде, изначально написанном другими людьми.)

person Jonathan Leffler    schedule 29.05.2012
comment
Я не знаю, почему вы говорите, что это код на паскале, когда я не знаю ни единого бита паскаля, и я использую mingw32 для компиляции всего кода ansi-c, но вы правы с feof(), его действительно проще использовать EOF. И разве это не то же самое, что использовать char в этом случае? Я имею в виду, что fgetc возвращает int, но его можно интерпретировать как char, не считая того, что мы/компилятор называем, это всего 8 бит, верно? Или может ли функция fgetc вернуть значение больше 255 и меньше 2^32? В любом случае спасибо за ответ, очень познавательно! - person Machine-Code Reader; 29.05.2012
comment
fgetc не может вернуть char, потому что в дополнение к 256 возможным значениям char он должен вернуть 257th: EOF, который обычно #определяется как -1. Таким образом, вам нужен тип шириной не менее 9 бит. Использование int было выбором разработчиков языка. - person Jens; 29.05.2012
comment
@Machine-CodeReader Причина, по которой предлагается Pascal, заключается в том, что в (стандартном) Pascal есть ошибка при попытке чтения из файла, который достиг EOF, поэтому вы должны проверить EOF, прежде чем пытаться выполнить ввод-вывод (который гарантированно не не удается из-за EOF). Йенс прекрасно резюмировал причину, по которой fgetc() возвращает int. Это одна из ловушек, в которые попадают люди при изучении C (чаще с getchar(), чем fgetc(), но логика та же). - person Jonathan Leffler; 29.05.2012