简体   繁体   中英

Read a file with only char pointers in c++

I need to read a file content into certain objects, unfortunately i'm not at liberty to use std::string and thereby have to use char pointers. However when i do this i get weird signs coming directly from the memory, the solution to that is however not working. So i rebuilded by using getline directly from the istream instead of the std library, but the same happens. How can i read the file correctly without making use of std::string.

PortsContainer game::ParsePort(std::istream& stream)
{
    PortsContainer ports;

    bool passFirstRow = false;

    char* portLine = new char[1000000];
    int i = 0;
    while (!stream.eof())
    {
        if (!stream)
            throw std::system_error(Error::STREAM_ERROR);

        if (portLine[0] == '\0' || portLine == nullptr || portLine[0] == '#')
            continue;

        std::stringstream ss(portLine);

        if (!passFirstRow) {
            char* name = new char[100];
            while (!ss.eof()) {
                ss.getline(name, sizeof name, ';');
                Port* port = new Port();
                //port->name = const_cast<char*>(name);
                port->name = const_cast<char*>(name);
                ports.addItem(port);
            }

            passFirstRow = true;
        } else {
            i++;
        }

        if (!stream)
            throw std::system_error(Error::STREAM_ERROR);
    }

    return ports;
}

PortsContainer game::ParsePort(std::istream& stream, std::error_code& errorBuffer)
{
    try
    {
        return ParsePort(stream);
    }
    catch (std::system_error exception)
    {
        errorBuffer = exception.code();
    }
}

PortsContainer game::GetAvailablePorts()
{
    PortsContainer ports;

    std::ifstream stream("./ports.csv");

    std::error_code errorBuffer;
    ports = ParsePort(stream, errorBuffer);
    if (errorBuffer)
        return PortsContainer();

    return ports;
}

You are not populating portLine with any data. In fact, you are not reading any data from stream at all.

You are misusing eof() . The eofbit flag is not updated until AFTER a read operation is attempted first. So you must read before elavulating eof() .

You are leaking your portLine and name buffers. And worse, since you are not allowed to use std::string , that implies that the Port::name member is a char* pointer, which means you have (potentially) multiple Port objects pointing at the same physical buffer in memory. If Port tries to free that buffer later on, such as in its destructor, you are going to have memory errors.

Try something more like this instead:

PortsContainer game::ParsePort(std::istream& stream)
{
    if (!stream)
        throw std::system_error(Error::STREAM_ERROR);

    PortsContainer ports;

    bool passFirstRow = false;

    // better would be to use std::unique_ptr<char[]>, std::vector<char>,
    // or std::string instead so the memory is freed automatically ...
    char *portLine = new char[1000000];

    int i = 0;

    do
    {
        if (!stream.getline(portLine, 1000000))
        {
            delete[] portLine; // <-- free the buffer for line data...
            throw std::system_error(Error::STREAM_ERROR);
        }

        if ((stream.gcount() == 0) || (portLine[0] == '#'))
            continue;

        if (!passFirstRow)
        {
            std::istringstream iss(portLine);

            // better would be to use std::unique_ptr<char[]>, std::vector<char>,
            // or std::string instead so the memory is freed automatically ...
            char* name = new char[100];

            while (iss.getline(name, 100, ';'))
            {
                if (iss.gcount() == 0) continue;

                Port *port = new Port();
                port->name = name; // <-- assumes ownership is transferred!
                ports.addItem(port);
                name = new char[100]; // <-- have to reallocate a new buffer each time!
            }
            delete[] name; // <-- free the last buffer not used...
            passFirstRow = true;
        } else {
            ++i;
        }
    }
    while (!stream.eof());

    delete[] portLine; // <-- free the buffer for line data...

    return ports;
}

PortsContainer game::ParsePort(std::istream& stream, std::error_code& errorBuffer)
{
    try
    {
        return ParsePort(stream);
    }
    catch (const std::system_error &exception)
    {
        errorBuffer = exception.code();
        return PortsContainer(); // <-- don't forget to return something!
    }
}

PortsContainer game::GetAvailablePorts()
{
    std::ifstream stream("./ports.csv");
    std::error_code errorBuffer;
    return ParsePort(stream, errorBuffer); // <-- no need to check errorBuffer before returning!
}

however, I strongly suggest you using the STL smart pointers to help ensure safer memory management:

PortsContainer game::ParsePort(std::istream& stream)
{
    if (!stream)
        throw std::system_error(Error::STREAM_ERROR);

    PortsContainer ports;

    bool passFirstRow = false;

    // since you are using std::error_code, that means you are
    // using C++11 or later, so use std::unique_ptr to ensure
    // safe memory management...
    std::unique_ptr<char[]> portLine(new char[1000000]);

    int i = 0;

    do
    {
        if (!stream.getline(portLine.get(), 1000000))
            throw std::system_error(Error::STREAM_ERROR);

        if ((stream.gcount() == 0) || (portLine[0] == '#'))
            continue;

        if (!passFirstRow)
        {
            std::istringstream iss(portLine.get());

            // use std::unique_ptr here, too...
            std::unique_ptr<char[]> name(new char[100]);

            while (iss.getline(name.get(), 100, ';'))
            {
                if (iss.gcount() == 0) continue;

                // use std::unique_ptr here, too...
                std::unique_ptr<Port> port(new Port);
                port->name = name.release(); // <-- assumes ownership is transferred!
                                             // better to make Port::name use std::unique_ptr<char[]> and then std::move() ownership of name to it...
                ports.addItem(port.get());
                port.release();

                name.reset(new char[100]); // <-- have to reallocate a new buffer each time!
            }
            passFirstRow = true;
        } else {
            ++i;
        }
    }
    while (!stream.eof());

    return ports;
}

Though, using std::string would be the best option:

PortsContainer game::ParsePort(std::istream& stream)
{
    if (!stream)
        throw std::system_error(Error::STREAM_ERROR);

    PortsContainer ports;

    bool passFirstRow = false;
    std::string portLine;
    int i = 0;

    while (std::getline(stream, portLine))
    {
        if (portLine.empty() || (portLine[0] == '#'))
            continue;

        if (!passFirstRow)
        {
            std::istringstream iss(portLine);
            std::string name;

            while (std::getline(iss, name, ';'))
            {
                if (name.empty()) continue;

                std::unique_ptr<Port> port(new Port);
                port->name = name; // <-- make Port::name be std::string as well!
                ports.addItem(port.get());
                port.release();
            }
            passFirstRow = true;
        } else {
            ++i;
        }
    }

    if (!stream)
        throw std::system_error(Error::STREAM_ERROR);

    return ports;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM