Lion King
Lion King

Reputation: 33813

What is the correct way to assign an const unsigned char* data into an array

I have the following code :

sqlite *sql = new sqlite; // create a new sqlite object.
const char *dbFile = "database.db"; // sqlite database file.

sql->open(dbFile);  // open the connection.
sql->query("SELECT * FROM categories"); // make a query.
int numRows = sql->numRows(); // get the number of rows.
const unsigned char *result[numRows]; // an array to store data that will be brought in from the database.
int index = 0; // counter

while(sql->fetch() != SQLITE_DONE){
    // store the data into an array
    result[index] = sql->getValue("name");
    index++;

    // print the data directly without storing in an array
    cout << sql->getValue("name") << endl;
}
sql->close();

// print the content of the `result` array.
for(int i=0;i<numRows;i++){
    cout << result[i] << endl;
}

The result:

Notice:

The method sql->getValue("name"); return const unsigned char* data.

As you seen in the result image, why when print the data directly, appear without problems, while when print the same data stored in the array does not appear ?


The solution

Firstly: I thank everyone who contributed(by comment or answer) to helping me to reach to a solution for my problem. their answers really deserve the best answer.

sqlite *sql = new sqlite;
const char *dbFile = "database.db";
sql->open(dbFile);
sql->query("SELECT * FROM categories");
int numRows = sql->numRows();
char **result = (char**)malloc(sizeof(char*) * numRows);
int index = 0;

while(sql->fetch() != SQLITE_DONE){
    result[index] = (char*)malloc(sizeof(char*) * CHAR_MAX);
    strcpy(result[index], reinterpret_cast<const char*> (sql->getValue("name")));
    index++;

    cout << sql->getValue("name") << endl;
}
cout << "\n\n" << endl;
sql->close();

for(int i=0;i<numRows;i++){
    cout << *(result+i) << endl;
}

The result:

Upvotes: 0

Views: 2239

Answers (3)

Cheers and hth. - Alf
Cheers and hth. - Alf

Reputation: 145299

Okay, replace this original code:

const unsigned char *result[numRows]; // an array to store data that will be brought in from the database.
int index = 0; // counter

while(sql->fetch() != SQLITE_DONE){
    // store the data into an array
    result[index] = sql->getValue("name");
    index++;
}

with this:

#include <string>
#include <vector>
using std::string; using std::vector;
    ⋮

vector<string> result;
while( sql->fetch() != SQLITE_DONE )
{
    result.emplace_back( reinterpret_cast<const char*>(
        sql->getValue("name")
        ) );
}

And also replace the printf with cout <<, or else, in the printf call replace result[i] with result[i].c_str().

All assuming that the original code works.

Disclaimer: code not touched by compiler.


Addendum:

You most recent code-that-doesn't-compile,

const unsigned char **result = (const unsigned char**)malloc(sizeof(const unsigned char*) * numRows);
int index = 0;
while(sql->fetch() != SQLITE_DONE){
    result[index] = (const unsigned char*)malloc(sizeof(const unsigned char*) * CHAR_MAX);
    strcpy(result[index], sql->getValue("name"));
    index++;

    cout << sql->getValue("name") << endl;
}

… can be expressed in C-style C++ as

char** result = new char*[numRows]();
int index = 0;
while( sql->fetch() != SQLITE_DONE )
{
    assert( index < numRows );
    char const* const s = reinterpret_cast<char*>( sql->getValue( "name" ) );
    result[index] = new char[1 + strlen( s )];
    strcpy( result[index], s );
    cout << s << endl;
    ++index;
}

Again assuming that the original code worked.

Disclaimer: Again, the code has not been touched by a compiler. Also, readers should note that using new directly, while that improves on using malloc, is ungood practice. Good practice would be to use std::vector and std::string, as I advised first of all.

Upvotes: 1

Bill Weinman
Bill Weinman

Reputation: 2059

Your problem is that you're assigning a pointer rather than copying the string. This is an important distinction.

Assigning the pointer doesn't "store the data" as your comment suggests, it simply copies the pointer. SQLite discards the data after it's done with the record, so its buffers are free to use with another record. By the time you go to print out the string, the pointer is no longer valid.

In order to retain the string for later printing you'll need to copy the string to your own buffer.

This means you'll need to allocate buffer space for the string, copy the string, and then free the space when you're done with it.

It sounds like you want to avoid using C++ features, so you'll want to look at malloc(), free(), and strncpy() to accomplish your task. Otherwise, if you wanted to use the C++ STL String class, that would solve your problem nicely.

Upvotes: 1

Galik
Galik

Reputation: 48635

I have recently started looking at Sqlite myself so I happen to have some code I knocked up that I have extracted to give as an exampe. This is not supposed to be particularly performant just to get the job done.

It also includes code to get the SQL REGEXP function working.

The idea is to add elements to a data structure during the select callback. The data structure is a std::vector of std::map. The std::map being the table's colum data mapped from the column names and the std::vector containing the rows of columns:

#include <map>
#include <regex>
#include <string>
#include <vector>
#include <iterator>
#include <iostream>

#include <sqlite3.h>

typedef std::map<std::string, std::string> result;
typedef std::vector<result> result_vec;

#define log(msg) std::cout << msg << std::endl

static int select_callback(void* user, int argc, char** argv, char** azColName)
{
    result_vec& v = *static_cast<result_vec*>(user);
    result r;
    for(int i = 0; i < argc; ++i)
        if(argv[i])
            r.emplace(azColName[i], argv[i]);
    v.emplace_back(std::move(r));
    return 0;
}

static void regexp_callback(sqlite3_context* context, int argc,
    sqlite3_value** argv)
{
    unsigned count = 0;
    if(argc == 2)
    {
        const char* pattern = (const char*) sqlite3_value_text(argv[0]);
        const char* value = (const char*) sqlite3_value_text(argv[1]);

        if(pattern && value)
        {
            std::string s = value;
            std::regex r(pattern, std::regex::icase);
            std::smatch m;
            if(std::regex_search(s, m, r))
                count = m.size();
        }
    }
    sqlite3_result_int(context, (int) count);
}

sqlite3* open(const std::string& name)
{
    sqlite3* db;
    if(sqlite3_open(name.c_str(), &db) != SQLITE_OK)
    {
        log("ERROR: opening db: " << name);
        log("ERROR: " << sqlite3_errmsg(db));
        return nullptr;
    }
    sqlite3_create_function_v2(db, "REGEXP", 2, SQLITE_ANY, 0, regexp_callback, NULL, NULL, NULL);
    return db;
}

bool select(sqlite3* db, const std::string& sql, result_vec& v)
{
    char* error = 0;
    if(sqlite3_exec(db, sql.c_str(), select_callback, &v, &error) != SQLITE_OK)
    {
        log("ERROR: " << error);
        log("ERROR: " << sqlite3_errmsg(db));
        log("  SQL: " << sql);
        sqlite3_free(error);
        return false;
    }
    return true;
}

int main()
{
    sqlite3* db = open("mydatabase.db");

    if(db)
    {
        result_vec results;

        if(!select(db, "select * from mytable", results))
            return 1;

        for(auto&& row: results)
            for(auto&& col: row)
                std::cout << col.first << ": " << col.second << '\n';
    }
}

NOTE: I just read that you are not looking to use std::vector or C++11 features so this answer is not what you are looking for (although it may still be of use to you). However I will leave it for other people who may google this question.

Upvotes: 1

Related Questions