Чтение файла в строковый буфер и обнаружение EOF

Я открываю файл и помещаю его содержимое в строковый буфер, чтобы провести лексический анализ для каждого символа. Делая это таким образом, синтаксический анализ завершается быстрее, чем использование последующего числа вызовов fread(), и, поскольку исходный файл всегда будет не больше пары МБ, я могу быть уверен, что все содержимое файла будет всегда читаться,

Тем не менее, кажется, что возникают проблемы с обнаружением, когда больше нет данных для анализа, потому что ftell() часто дает мне целочисленное значение, превышающее фактическое количество символов в файле. Это не будет проблемой при использовании макроса EOF (-1), если конечные символы всегда были -1... Но это не всегда так...


Вот как я открываю файл и считываю его в строковый буфер:

FILE *fp = NULL;
errno_t err = _wfopen_s(&fp, m_sourceFile, L"rb, ccs=UNICODE");
if(fp == NULL || err != 0) return FALSE;
if(fseek(fp, 0, SEEK_END) != 0) {
    fclose(fp);
    fp = NULL;
    return FALSE;
}

LONG fileSize = ftell(fp);
if(fileSize == -1L) {
    fclose(fp);
    fp = NULL;
    return FALSE;
}
rewind(fp);

LPSTR s = new char[fileSize];
RtlZeroMemory(s, sizeof(char) * fileSize);
DWORD dwBytesRead = 0;
if(fread(s, sizeof(char), fileSize, fp) != fileSize) {
    fclose(fp);
    fp = NULL;
    return FALSE;
}

Кажется, это всегда работает отлично. Далее следует простой цикл, который проверяет содержимое буфера строки по одному символу за раз, например, так:

char c = 0;
LONG nPos = 0;
while(c != EOF && nPos <= fileSize)
{
    c = s[nPos];
    // do something with 'c' here...
    nPos++;
}

Конечные байты файла обычно представляют собой последовательность символов (-3) и " (-85), поэтому EOF никогда не обнаруживается. Вместо этого цикл просто продолжается до тех пор, пока nPos не будет иметь более высокое значение, чем fileSize - что нежелательно для правильного лексического анализа, потому что вы часто заканчиваете тем, что пропускаете последний токен в потоке, который пропускает символ новой строки в конце.


Можно ли предположить, что в наборе символов Basic Latin предполагается, что EOF char - это любой символ с отрицательным значением? Или, может быть, есть лучший способ сделать это?


#EDIT: Я только что попытался внедрить функцию feof() в мой цикл, и, тем не менее, она также не обнаруживает EOF.

1 ответ

Решение

Сбор комментариев в ответ...

  • Вы теряете память (потенциально много памяти), когда не можете прочитать.

  • Вы не допустили нулевого терминатора в конце прочитанной строки.

  • Нет смысла обнулять память, когда она собирается быть перезаписана данными из файла.

  • Ваш тестовый цикл обращается к памяти вне границ; nPos == fileSize это один за пределами памяти, которую вы выделили.

    char c = 0;
    LONG nPos = 0;
    while(c != EOF && nPos <= fileSize)
    {
        c = s[nPos];
        // do something with 'c' here...
        nPos++;
    }
    
  • Есть другие проблемы, не упомянутые ранее, с этим. Вы спросили, "безопасно ли предположить, что символ EOF - это любой символ с отрицательным значением", на что я ответил " Нет". Здесь есть несколько проблем, которые затрагивают как C, так и C++ код. Во-первых, это равнина char может быть подписанным типом или неподписанным типом. Если тип является беззнаковым, то вы никогда не сможете сохранить в нем отрицательное значение (или, точнее, если вы попытаетесь сохранить отрицательное целое число в беззнаковом знаке, оно будет усечено до младших значащих 8 * битов и будет обработано как положительный.

  • В приведенном выше цикле может возникнуть одна из двух проблем. Если char является типом со знаком, то есть символ (ÿ, y-umlaut, U+00FF, LATIN SMALL LETTER Y WITH DIAERESIS, 0xFF в наборе кодов Latin-1), который имеет то же значение, что и EOF (который всегда является отрицательным и обычно -1). Таким образом, вы можете обнаружить EOF преждевременно. Если char это тип без знака, тогда никогда не будет символа, равного EOF. Но проверка EOF на символьной строке в корне неверна; EOF - это индикатор состояния операций ввода-вывода, а не символ.

  • Во время операций ввода / вывода вы обнаружите EOF только тогда, когда попытаетесь прочитать данные, которых там нет. fread() не будет сообщать EOF; Вы попросили прочитать, что было в файле. Если вы пытались getc(fp) после fread(), вы получите EOF, если файл не вырос, так как вы измерили, как долго это будет. поскольку _wfopen_s() это нестандартная функция, это может повлиять на то, как ftell() ведет себя и значение, которое он сообщает. (Но вы позже установили, что это не так.)

  • Обратите внимание, что такие функции, как fgetc() или же getchar() определены для возврата символов в виде положительных целых чисел и EOF в качестве отдельного отрицательного значения.

    Если указатель конца файла для входного потока указывает на stream не установлен и присутствует следующий символ, fgetc функция получает этот символ как unsigned char преобразован в int,

    Если индикатор конца файла для потока установлен, или если поток находится в конце файла, индикатор конца файла для потока установлен и fgetc функция возвращает EOF. В противном случае fgetc функция возвращает следующий символ из входного потока, на который указывает stream, Если происходит ошибка чтения, устанавливается индикатор ошибки для потока и fgetc функция возвращает EOF. 289)

    289) Конец файла и ошибка чтения можно различить с помощью feof а также ferror функции.

    Это указывает на то, что EOF отделен от любого допустимого символа в контексте операций ввода-вывода.

Вы комментируете:

Что касается любой потенциальной утечки памяти... На данном этапе в моем проекте утечки памяти являются одной из многих проблем с моим кодом, которые на данный момент меня не беспокоят. Даже если он не пропустил память, с самого начала он даже не работает, так какой в ​​этом смысл? Функциональность на первом месте.

На начальном этапе кодирования легче предотвратить утечки памяти в путях ошибок, чем вернуться назад и исправить их - потому что вы можете их не обнаружить, потому что вы не можете вызвать состояние ошибки. Однако степень, в которой это имеет значение, зависит от целевой аудитории программы. Если это одноразовый курс по кодированию, у вас все может быть хорошо. Если вы единственный человек, который будет использовать его, вы можете быть в порядке. Но если он будет установлен миллионами, у вас будут проблемы с модернизацией чеков повсюду.

Я поменял местами _wfopen_s() с fopen(), и результат от ftell() такой же. Однако после изменения соответствующих строк на LPSTR s = new char[fileSize + 1], RtlZeroMemory(s, sizeof(char) * fileSize + 1); (который также должен завершаться нулем, btw), и, добавив if(nPos == fileSize) в начало цикла, он теперь выходит чисто.

ХОРОШО. Вы могли бы использовать только s[fileSize] = '\0'; обнулить данные тоже, но используя RtlZeroMemory() достигается тот же эффект (но будет медленнее, если размер файла много мегабайт). Но я рад, что различные комментарии и предложения помогли вам вернуться на правильный путь.


* Теоретически, CHAR_BITS может быть больше 8; на практике это почти всегда 8, и для простоты я предполагаю, что здесь 8 бит. Обсуждение должно быть более нюансированным, если CHAR_BITS равно 9 или более, но общий эффект почти такой же.

Другие вопросы по тегам