С++ Необработанное исключение при чтении файла

Попытка написать парсер .ply для использования моделей .ply в OpenGL.

Попытка начать читать файл .ply и записать все его строки. Моя программа делает это, но когда она распечатывает последнюю строку, я получаю необработанное исключение:

Необработанное исключение по адресу 0x62aad540 (msvcr100d.dll) в PLY parser.exe: 0xC0000005: место чтения нарушения прав доступа 0x00000000.

Это мой код:

#include <iostream>
#include <fstream>
#include <string>
#include <stdio.h>


using namespace std;


int main ()
{
    char buffer[10000];
    FILE * myFile;
    myFile = fopen("walkman.ply", "r");
    if(myFile != NULL)
    {
        while (!feof(myFile))
        {

               cout<<fgets(buffer, 10000, myFile);

        }
        fclose(myFile);
    }
    else
    {
        cout<<"file not found"<<endl;
    }

    system("pause");
    return 0;
}

Это может быть глупой ошибкой в ​​моем коде, но было бы здорово, если бы кто-нибудь заметил ошибку, вызывающую это.


person Community    schedule 01.03.2012    source источник
comment
Но вы вообще не используете <fstream>.   -  person Seagull    schedule 02.03.2012


Ответы (3)


Прежде чем мы перейдем к описанию ошибки, вы должны знать, что сообщение "Необработанное исключение... Нарушение доступа к местоположению чтения 0x00000000", которое вы получаете, не вызвано исключением C++; это эквивалент Windows «Ошибка сегментации». Ваш код пытался разыменовать указатель NULL.

Теперь вы сделали одну из классических ошибок при работе с FILE объектами. feof(fp) не становится истинным, когда вы достигаете конца файла. Это становится истинным только после того, как вы попытались прочитать за конец файла хотя бы один раз. Таким образом, ваш цикл чтения будет выполняться до тех пор, пока после fgets не будет предпринята попытка чтения после конца файла. И когда fgets пытается прочитать дальше конца файла, он терпит неудачу и возвращает указатель NULL, который вы вслепую передали cout. Кабум.

(Кстати, так же работает и istream::eof().)

Правильный способ написать этот цикл

while (fgets(buffer, 10000, myFile))
    cout << buffer;

(Или, что еще лучше, один из этих:

while (fgets(buffer, 10000, myFile))
    fputs(buffer, stdout));

while(myFile.get(buffer, 10000))
    cout << buffer;

немного странно смешивать stdio.h ФАЙЛОВ и iostream, как вы это делаете.)

person zwol    schedule 01.03.2012

feof() сообщает вам, что вы пытались прочитать за конец файла, а не о том, что вы достигли конца файла. fgets() возвращает NULL, когда вы находитесь в конце файла и больше нет данных для чтения. Вот откуда исходит исключение. В конце файла feof() вернет false, а fgets() вернет NULL, что вызовет исключение, когда ваша программа попытается выполнить cout << NULL;.

Это идиоматический способ написать это в стиле C:

char buffer[10000];
FILE* myFile = fopen("walkman.ply", "r");
if (myFile != NULL) {
    while (fgets(buffer, sizeof(buffer), myFiles) {
        fputs(buffer, stdout);
    }
    fclose(myFile);
}

или в стиле С++:

std::string buffer;
std::ifstream myFile("walkman.ply");
if (myFile.is_open()) {
    while (std::getline(myFile, buffer)) {
        std::cout << buffer << '\n';
    }
}
person Ferruccio    schedule 01.03.2012
comment
fgets не обрезает завершающий символ новой строки, поэтому вам нужно соединить его с fputs, а не puts. - person zwol; 02.03.2012
comment
@Зак: Спасибо. Прошло много лет с тех пор, как я писал производственный код C. - person Ferruccio; 02.03.2012

РЕДАКТИРОВАТЬ: мой прогноз был неверным, но все же прочитайте следующее, если вы должны использовать потоки.

Также рассмотрите возможность использования потоков (в ) и векторов, эти методы гораздо менее подвержены ошибкам и соответствуют стилю и духу C++.

std::ifstream in("walkman.ply", std::ios::binary);
std::vector<char> v(istream_iterator<char>(in),
                    istream_iterator<char>( ));

или если это предполагается строковым значением.

std::ifstream in("walkman.ply");
std::string str(istream_iterator<char>(in),
                istream_iterator<char>( ));
std::cout << str << std::endl;
person 111111    schedule 01.03.2012
comment
Это может случиться, но это даст другой адрес для нарушения доступа. Теперь он ссылается на 0x0000000, что означает, что где-то программа разыменовывает указатель NULL. - person Matteo Italia; 02.03.2012
comment
@MatteoItalia, если говорить прямо, я считаю это проблемой C, а не C++, если ошибка сохраняется с реализацией потока, которую я дал выше, пожалуйста, дайте мне знать. - person 111111; 02.03.2012
comment
Я не говорю, что ваше решение неверно, но что ваш диагноз неверен. - person Matteo Italia; 02.03.2012
comment
Проголосовали против, потому что char[] не будет заканчиваться нулем, это неверно: fgets гарантирует, что в случае успеха он возвращает строку с нулевым завершением. - person zwol; 02.03.2012
comment
@MatteoItalia Извините, что раньше я грубо сталкивался с этим. Вы правы, Ферруччо дал хороший технический ответ, я лично все равно буду использовать потоки, и, судя по тегам и формулировке вопроса, я думаю, что ОП имел в виду использование потоков, но по какой-то причине этого не сделал. - person 111111; 02.03.2012
comment
Кроме того, если вам нужен стиль и дух С++, ifstream("walkman.ply") >> cout.rdbuf() - это оптимальный способ выполнения этой операции IIUC. - person zwol; 02.03.2012
comment
@Zack, это зависит от того, где цель заключалась в том, чтобы напечатать файл или нет, если это было, то да, это хороший способ сделать, я предположил, что буфер в конечном итоге (даже если не в примере) будет использоваться для чего-то. - person 111111; 02.03.2012