问题
I am working on a sqlite-.dll for educational purpose. I am trying to dynamically add a row in my 2 dimensional array for each time the callback function is called with a new row from the database. (e.g. SELECT * FROM CUSTOMER). The data stored in this array should then be returned as a C-Interface.
SQLCONTROL_API char** sql_execQuery(char *dbName, char *sqlStatement)
{
char **a = 0;
/*Some sqlite stuff*/
int rc = sqlite3_exec(db, sqlStatement, callback, &a, &zErrMsg);
return a;
}
With the callback function:
static int callback(void *data, int argc, char **argv, char **azColName)
{
char **old = (char **)data;
int num_rows = sizeof(old) / sizeof(old[0]);
int num_cols = sizeof(old[0]) / sizeof(old[0][0]);
old = (char **)realloc(old, (num_rows + 1) * sizeof(char *));
for (int i = 0; i < (num_rows + 1); i++)
old[i] = (char *)realloc(old[i], argc * sizeof(char *));
/*I am trying to create a 2 dim array that looks like a table,
so the column names are in the first row,
then the data from the table is stored in each row*/
for (int i = 0; i < argc; i++)
{
if (num_rows == 1)
old[0][i] = *azColName[i];
old[num_rows][i] = *argv[i];
}
data = old;
return 0;
}
When inserting data to the database, everything works fine. But when I try to retrieve data, I get read access violation. Now my question, am I on the right way with my approach or do I miss some important requirements for my intention?
回答1:
In your sql_execQuery()
, you declare a
as a char **
, and you pass its address, &a
, as the fourth argument of sqlite3_exec()
. That argument therefore has type char ***
, and it points to a location somewhere in the program's stack. There's nothing inherently wrong with that.
But then we get to callback()
, which has serious problems, principal among them:
- It treats the
data
pointer as if it were of typechar **
, instead of the correct type,char ***
. If that were your only problem, you could fix it like this:
char **old = *(char ***)data;
// ...
*(char ***)data = old;
It tries to compute the dimensions of the allocated space via the
sizeof
operator, as would be reasonable ifold
were, in fact, a 2D array, but it is not an array at all. It is a pointer to pointer tochar
, sosizeof(old)
is the size of a pointer (to pointer tochar
),sizeof(old[0])
is the size of a pointer (tochar
) andsizeof(old[0][0])
is the size of achar
. This does not tell you anything about how much space has been allocated.After allocating memory for
old
, it dereferences the parts of the allocated memory without initializing them, by passing them torealloc()
. Generally, all but one of these will have been initialized, but the one uninitialized one causesrealloc()
to exhibit undefined behavior.You fail to check for allocation errors.
It looks like you need a more complex data structure to be passed through to your callback, so that you can track the allocated dimensions. Something like this, for example:
struct mytable {
char **data;
size_t dim;
};
SQLCONTROL_API char** sql_execQuery(char *dbName, char *sqlStatement)
{
struct mytable a = { NULL, 0 };
// ...
int rc = sqlite3_exec(db, sqlStatement, callback, &a, &zErrMsg);
return a.data;
}
static int callback(void *data, int argc, char **argv, char **azColName)
{
struct mytable *old = data;
char **temp;
old->dim++;
temp = realloc(old->data, old->dim * sizeof(*old->data));
if (temp) {
old->data = temp;
old->data[old->dim - 1] = NULL;
} else {
// handle allocation error ...
}
for (int i = 0; i < old->dim; i++) {
char *temp2 = realloc(old->data[i], argc * sizeof(*old->data[i]));
if (temp2) {
old->data[i] = temp2;
old->data[i][argc - 1] = NULL;
} else {
// handle allocation error ...
}
}
// ... other stuff ...
// no need for anything like data = old
return 0;
}
来源:https://stackoverflow.com/questions/40096266/dynamically-realloc-2-dim-array-in-callback-function-of-sqlite