Чтение файла в строковый буфер и обнаружение 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 - что нежелательно для правильного лексического анализа, потому что вы часто пропускаете последний токен в потоке, в конце которого опускается символ новой строки.


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


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


person RectangleEquals    schedule 11.03.2013    source источник
comment
Вы теряете много памяти (потенциально), когда не умеете читать. Вы не разрешили использовать нулевой терминатор в конце прочитанной строки. Нет смысла обнулять память, когда все данные будут перезаписаны данными из файла. Ваш тестовый цикл обращается к памяти вне пределов; nPos == fileSize находится за пределами выделенной вами памяти.   -  person Jonathan Leffler    schedule 11.03.2013
comment
Есть ли причина, по которой вы пометили этот C ++, но используете то, что мне кажется чем-то более похожим на чистый C? вы пробовали потоки файлов C ++? Какая кодировка у вашего файла?   -  person Warren P    schedule 11.03.2013
comment
Вы обнаружите EOF только тогда, когда попытаетесь прочитать данные, которых нет. fread() не будет сообщать о EOF; вы просили прочитать, что было в файле. Если вы попробуете getc(fp) после fread(), вы получите EOF, если только файл не увеличился с тех пор, как вы измерили его длину. Поскольку _wfopen_s() - нестандартная функция, она может влиять на поведение ftell() и значение, которое он сообщает. Нет; небезопасно предполагать, что любое отрицательное значение char является EOF. Тип plain char может быть подписанным или беззнаковым.   -  person Jonathan Leffler    schedule 11.03.2013
comment
@WarrenP: он правильно помечен тегом C ++ из-за new[fileSize]. Вероятно, это не идиоматический C ++, но это определенно не C.   -  person Jonathan Leffler    schedule 11.03.2013
comment
Он находится в VC ++ 11, завернутый в класс C ++. Показанные здесь файловые операции выполняются на C, но в целом это C ++. И нет, я не собираюсь использовать файловые потоки C ++, и это было протестировано на нескольких исходных кодировках, начиная от ANSI, UTF-8 и UTF-8 без спецификации.   -  person RectangleEquals    schedule 11.03.2013
comment
Что касается любой потенциальной утечки памяти ... На данном этапе моего проекта утечки памяти - одна из многих проблем с моим кодом, которые пока меня не беспокоят. Даже если это не утечка памяти, это даже не работает, так в чем смысл? Функциональность на первом месте.   -  person RectangleEquals    schedule 11.03.2013
comment
@JonathanLeffler Я поменял местами _wfopen_s() на fopen(), и результат ftell() такой же. Однако после изменения соответствующих строк на LPSTR s = new char[fileSize + 1], RtlZeroMemory(s, sizeof(char) * fileSize + 1); (что также должно завершать его нулем, кстати) и добавления if(nPos == fileSize) в начало цикла, теперь он выходит чисто. Если вам нужны очки репутации, дайте соответствующий ответ.   -  person RectangleEquals    schedule 11.03.2013
comment
EOF не кодируется в буфере. Он возвращается из fgetc() или fgetwc(), в зависимости от того, как вы » re обрабатывает сам файл и не имеет отношения к вашему режиму. Но вы открываете файл в двоичном режиме, о котором я, честно говоря, даже не знал, поддерживается в режиме кодирования ccs. Размер буфера должен быть правильным в байтах, если вы использовали вычисленную длину файла + 1 (+1 для терминатора). Если открытие в двоичном режиме и указание подсказки кодирования для запроса анализа спецификации работает, тем лучше.   -  person WhozCraig    schedule 11.03.2013


Ответы (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 может быть типом со знаком или без знака. Если тип беззнаковый, то вы никогда не сможете сохранить в нем отрицательное значение (или, точнее, если вы попытаетесь сохранить отрицательное целое число в беззнаковый char, оно будет усечено до наименее значимого 8 * бит и будет считаться положительным.

  • В приведенном выше цикле может возникнуть одна из двух проблем. Если char является типом со знаком, то есть символ (ÿ, y-умлаут, U ​​+ 00FF, СТРОЧНАЯ ЛАТИНСКАЯ БУКВА Y С ДИАРЕЗОМ, 0xFF в кодовом наборе Latin-1), который имеет то же значение, что и EOF (который всегда отрицательный и обычно -1). Таким образом, вы можете обнаружить EOF преждевременно. Если char - беззнаковый тип, то никогда не будет символа, равного EOF. Но проверка EOF на символьной строке в корне ошибочна; EOF - это индикатор состояния операций ввода-вывода, а не символ.

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

  • # P9 #
    # P10 # # P11 # # P12 #
    # P13 #

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

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

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

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

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


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

person Jonathan Leffler    schedule 11.03.2013