I am in the final stages of testing a text-based game I have been working on for several years but I am running into some major problems with my database access code and some very strange problems at that.

What I am attempting to do is loop through a SQLite3 database to get all of the data from a table and then save that data in a vector of structs for use in other classes. However when the code reaches this particular function the debugger keeps throwing out assert errors claiming there is an invalid null pointer.

What is odd here though is that I took this code and placed it into a stand-alone test program and it works perfectly. Here is that test program:

#include <iostream>
#include "../SQLite/sqlite3.h"
#include <vector>
#include <string>


using namespace std;

struct planetName
{
    string pName;
};

void getPNResults(sqlite3* dBase, string operation, string table, vector<planetName> &pNames, vector<planetName> pNResults);
int getCount(string table, sqlite3* dBase);

int main()
{
    vector<planetName>pNames;
    vector<planetName>pNResults;
    sqlite3 *dBase;

    sqlite3_open("scDatabase.sqlite", &dBase);

    getPNResults(dBase,"Select * From"," Planet_Names",pNames, pNResults);

    cout << pNames.size() << endl << endl;

    if (pNames.size() > 0)
    {
        for (int i = 0; i < pNames.size(); i++)
        {
            cout << pNames.at(i).pName << endl;
        }
    }

    system("Pause");
}


void getPNResults(sqlite3* dBase, string operation, string table, vector<planetName> &pNames, vector<planetName> pNResults)
{
    sqlite3_stmt *statement;
    string sqlStr = operation + table;

    if (sqlite3_prepare_v2(dBase, sqlStr.c_str(), sqlStr.size(), &statement, 0) == SQLITE_OK)
    {
        for (int i = 0; i < getCount("Planet_Names", dBase); i++)
        {           
            int result = sqlite3_step(statement);
            int cols = sqlite3_column_count(statement);

            pNResults.push_back(planetName());

                for (int i2 = 0; i2 <= cols; i2++)
                {
                    switch(i2)
                    {
                    case 1:
                        pNResults.at(i).pName = string((char*)sqlite3_column_text(statement,i2));
                        break;      

                    default:                
                        break;
                    }
                }
            }

    }

    else
    {
        cout << sqlite3_errmsg(dBase) << endl;
    }

    for (int i = 0; i < pNResults.size(); i++)
    {
        pNames.push_back(planetName());
        pNames.at(i).pName = pNResults.at(i).pName;
    }
    //pNResults.clear();
}

int getCount(string table, sqlite3* dBase)
{
    string sqlStr2 = "Select Count(*) From " + table;
    sqlite3_stmt *statement;

    if (sqlite3_prepare_v2(dBase, sqlStr2.c_str(), sqlStr2.size(), &statement, 0) == SQLITE_OK)
    {           
        int result = sqlite3_step(statement);

        if (result == SQLITE_ROW)
        {
            return sqlite3_column_int(statement,0);
        }
    }
}

Now here is the same code but as part of my game and where the two functions are called from:

Database.cpp:

void Database::getPNResults(bool* bErrors)
{   
    if (sqlite3_prepare_v2(dBase, sqlStr.c_str(), sqlStr.size(), &statement, 0) == SQLITE_OK)
    {
        for (i = 0; i < getCount("Planet_Names", bErrors); i++)
        {           
            result = sqlite3_step(statement);
            cols = sqlite3_column_count(statement);

            pNResults.push_back(planetName());

                for (i2 = 0; i2 <= cols; i2++)
                {
                    switch(i2)
                    {
                    case 1:
                        pNResults.at(i).pName = string((char*)sqlite3_column_text(statement,i2));
                        break;

                    default:
                        break;
                    }
                }
            }

        *bErrors = false;
    }

    else 
    {
        *bErrors = true;
        createBInfo();
        d.createBReport("SQL Code 3",sqlite3_errmsg(dBase),bLocale + to_string(__LINE__),bTDate,"./SC_Log.txt");
    }
}

void Database::returnPNResult(vector<planetName>& pNames)
{

    for (i = 0; i < pNResults.size(); i++)
    {
        pNames.push_back(planetName());

        pNames.at(i).pName = pNResults.at(i).pName;
    }

    pNResults.clear();
}

dataSystem.cpp:

else if (table == "Planet_Names")
    {
        if (pNames.size() > 0)
        {
            pNames.clear();
        }

        pNames.push_back(planetName());

        db_ds.getPNResults(&bErrors);
        db_ds.returnPNResult(pNames);
    }

Here is the error:

---------------------------

Microsoft Visual C++ Runtime Library

Debug Assertion Failed!

Program: C:\Windows\system32\MSVCP110D.dll
File: c:\program files (x86)\microsoft visual studio 11.0\vc\include\xstring
Line: 1143

Expression: invalid null pointer

For information on how your program can cause an assertion
failure, see the Visual C++ documentation on asserts.

(Press Retry to debug the application)

---------------------------

Abort Retry Ignore

The last line of my code the call stack is traced to is the sqlite3_column_text line where the data is saved to the vector in case 1. I have tried about a dozen different things to deal with this. pNResults is already initialized byt he push back but I added a second one to be sure. I tried using pNames instead of pNResults but that still caused the assert. When I tried putting it in the stand alone program it worked and that really threw me off because nothing had really changed in how it was implemented.

There has to be something that I am missing. If you need any other info let me know and I will provide it.

void Database::getPNResults(bool* bErrors)

can you show how you called this fuction in your code?
Also understand the basic use of a pointer.
Pointers on creation are initialized to point to a specific memmory or points to a 0/NULL.

I stand to be corrected but If you dont follow this basic rules, you will fall into a segfault/null pointer errors just like what you have now.

This

pNResults.push_back(planetName());

for (i2 = 0; i2 <= cols; i2++)
{
    switch(i2)
    {
    case 1:
        pNResults.at(i).pName = string((char*)sqlite3_column_text(statement,i2));
        break;

    default:
        break;
    }
}

can be written more efficiently as

planetName planet;

if (cols >= 1)
{
    planet.pName = (char*)sqlite3_column_text(statement,1);
}

pNResults.push_back(planet);

it seems to me that the most likely problem is sqlite3_column_text returning NULL and you could modify this to check that

planetName planet;

if (cols >= 1)
{
    const char *data = (char*)sqlite3_column_text(statement,1);

    if (data != NULL)
    {
        planet.pName = data;
    }
    else
    {
        // report error
    }
}

pNResults.push_back(planet);

You should never just assume pointers are good.

If you implemented proper constructors, copy constructors and assignment operators for planetName then there are various other things you can do to simplfy your code.

This

for (i = 0; i < getCount("Planet_Names", bErrors); i++)
{
    ...
}

is very inefficient, every loop iteration you are querying the database store the result of getCount in a variable.

Use the debugger, it will show you where the crash is and by traversing the call stack you should be able to work out where the NULL pointer is.

void Database::getPNResults(bool* bErrors)

can you show how you called this fuction in your code?
Also understand the basic use of a pointer.
Pointers on creation are initialized to point to a specific memmory or points to a 0/NULL.

I stand to be corrected but If you dont follow this basic rules, you will fall into a segfault/null pointer errors just like what you have now.

If you look at the second block of code I posted it is called from there. That function, rData, is called from another function dealing with getting the names for planets:

void Game::getPNames()
{
    pNStorage.push_back(string());
    ds_g.getPNames(pNStorage);
}

This is called from Main.cpp. It calls getPNames which then calls the function in question (rData):

void dataSystem::getPNames(vector<string>& names)
{
    rData("Planet_Names","select from",0);

    for (i = 0; i < pNames.size(); i++)
    {
        names.push_back(string());

        names.at(i) = pNames.at(i).pName;
    }
}

And here is the code from rData again, this time with the entire function:

//For Database Interaction
void dataSystem::rData(string table, string operation, int ID)
{
    db_ds.openDB(&bErrors);
    db_ds.tableTAccess(table);

    db_ds.createStatement(ID,operation);

    if (table == "Ship_Data")
    {
        if (ships.size() > 0)
        {
            ships.clear();
            ships.push_back(ship());
        }

        ships.push_back(ship());

        db_ds.getSResults(&bErrors);
        db_ds.returnSResult(ships);
    }

    else if (table == "Weapon_Data")
    {
        if (weapons.size() > 0)
        {
            weapons.clear();
            weapons.push_back(weapon());
        }

        weapons.push_back(weapon());

        db_ds.getWResults(&bErrors);
        db_ds.returnWResult(weapons);
    }

    else if (table == "Mission_Data")
    {
        if (missions.size() > 0)
        {
            missions.clear();
        }

        missions.push_back(mission());

        db_ds.getMResults(&bErrors);
        db_ds.returnMResult(missions);
    }

    else if (table == "Msg_Data")
    {
        if (messages.size() > 0)
        {
            messages.clear();
        }

        messages.push_back(message());

        db_ds.getMesResults(&bErrors);
        db_ds.returnMesResult(messages);
    }

    else if (table == "Rank_Data")
    {
        if (rank.size() > 0)
        {
            rank.clear();
        }

        rank.push_back(ranks());

        db_ds.getRResults(&bErrors);
        db_ds.returnRResult(rank);
    }

    else if (table == "Race_Data")
    {
        if (races.size() > 0)
        {
            races.clear();
        }

        races.push_back(race());

        db_ds.getRcResults(&bErrors);
        db_ds.returnRcResult(races);
    }

    else if (table == "Dispo_Data")
    {
        if (dispositions.size() > 0)
        {
            dispositions.clear();
        }

        dispositions.push_back(diplomacy());

        db_ds.getDResults(&bErrors);
        db_ds.returnDResult(dispositions);
    }

    else if (table == "Planet_Names")
    {
        if (pNames.size() > 0)
        {
            pNames.clear();
        }

        pNames.push_back(planetName());

        db_ds.getPNResults(&bErrors);
        db_ds.returnPNResult(pNames);
    }

    else if (table == "Def_Data")
    {
        if (defenses.size() > 0)
        {
            defenses.clear();
        }

        defenses.push_back(defense());

        db_ds.getDefResults(&bErrors);
        db_ds.returnDefResult(defenses);
    }

    else if (table == "Module_Data")
    {
        if (modules.size() > 0)
        {
            modules.clear();
        }

        modules.push_back(module());

        db_ds.getModResults(&bErrors);
        db_ds.returnModResult(modules);
    }

    else if (table == "Item_Data")
    {
        if (items.size() > 0)
        {
            items.clear();
        }

        items.push_back(item());

        db_ds.getIResults(&bErrors);
        db_ds.returnIResults(items);
    }

    db_ds.closeDB(&bErrors);
}

___________________

Banfa, you were corrent though. For some reason my database is returning null values and I am in the process of figuring out why. The reason I did not check for this possibility is that I used the exact same file for the test program and it worked without problems.

I have a logging system set up in my game that will report and bugs either generate by SQLite or that non-uniform behaviors that I have chosen to catch. One problem with how it is is used is that it does not notify the user of errors unless it is called through the save/load data functions in my Game class at the moment.

Decided to check those and the database is being opened properly. There were some errors but these were just errors on trying to close the database beause I had not finalized my statements. I do not believe that has anything to do here.

And I figured it out thanks to your suggestions. I had already been thinking about why it would be saying there was a null pointer. I had attempted to call another table using ints and it worked but another table using strings also failed and gave the assert error.

So today when I noticed the statement finalize errors in my log I began to think of what you mentioned about how querying the database on each loop was inefficient and had a "doh!" moment What was going on was that, I believe anyway since this is what fixed the problem, the query to get the number of rows was causing the problem (that was the getCount call you mentioned).

I think what was going on was the statement was being changed from the original query to the one used to get the rows. For some reason this did not effect queries that were calling ints and the reason the code worked in the test program was that I was no longer calling that function at all.

I added another statement member to hold that particular query (statement2) and tried another debugging session and the assert error is gone and the values are being properly loaded even with the code I had been using before.

I will take under advisement your suggestions for simplifying these queries, it is much appriciated since calling SQLite can be pretty heady if done too often. Right now it does not matter too much since the game is only a text-based game. Once I move it to the full graphics version though it could impact preformance greatly.

Thanks for the help.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.