TheAschr TheAschr - 1 month ago 6
C Question

Dynamically allocated string matrix in C

I'm trying to create a string matrix in C to stores the results of a sql callback. For some reason, it always crashes on the 12th reallocation of "data" even though the memory address of data is the same.
Thanks.

int row_index;

static int db_select_cb(void *p_data ,int argc, char **argv, char **azColName){
char ***data = (char ***)p_data;
data = (char ***)realloc(data,sizeof(char **)*(row_index+1));
data[row_index] = (char **)malloc(sizeof(char *)*(argc));
for(int col_index = 0;col_index < argc;col_index++){
data[row_index][col_index] = (char *)malloc(sizeof(char)*(strlen(argv[col_index])+1));
strcpy(data[row_index][col_index],argv[col_index]);
}
row_index++;
return 0;
}

char ***db_select(sqlite3 *conn,unsigned char *zSQL){
row_index = 0;
char ***data = (char ***)malloc(sizeof(char ***)*(row_index+1));
char *err = 0;
int cerr = sqlite3_exec(conn,zSQL,db_select_cb,(void*)data,&err);
if(cerr){
printf(":: SQL ERROR IN \"db_select\" || %s ||\n", err);
sqlite3_free(err);
return 0;
}
return data;
}


Thanks for your help guys. The problem was that I needed to pass a reference to the matrix to the callback as realloc was modifying data. Here's what ended up working. I decided not to use the structs because I can get the column and row sizes by using sizeofs.

int row_index;

static int db_select_cb(void *p_data ,int argc, char **argv, char **azColName){
char ****data = (char ****)p_data;
*data = realloc(*data,sizeof(char **)*(row_index+1));
(*data)[row_index] = malloc(sizeof(char *)*(argc));
for(int col_index = 0;col_index < argc;col_index++){
(*data)[row_index][col_index] = malloc(sizeof(char)*(strlen(argv[col_index])+1));
strcpy((*data)[row_index][col_index],argv[col_index]);
}
row_index++;
return 0;
}

char ***db_select(sqlite3 *conn,unsigned char *zSQL){
row_index = 0;
char ***data = malloc(sizeof(char **)*(row_index+1));
char *err = 0;
int cerr = sqlite3_exec(conn,zSQL,db_select_cb,(void*)&data,&err);
if(cerr){
printf(":: SQL ERROR IN \"db_select\" || %s ||\n", err);
sqlite3_free(err);
return 0;
}
return data;
}

Answer Source

Your life would be much easier if you would create a couple of sanely named structs and some tiny helper functions.

First of all, your db_select function returns an allocated data, but sets a global variable row_index. Number of columns is lost forever. But this already indicates that you need a struct - you want to pack all information that this function needs to give you into a single "coherent" block.

So, you might say a row is a bunch of columns:

typedef struct {
    char *cols;
    int cols_count;
} Row;

And a table is a bunch of rows:

typedef struct {
    Row * rows;
    int rows_count;
} Table;

And now you handle allocation and housekeeping separately (note: I am writing this in the browser, haven't even checked if it will compile):

// allocates a new table
Table * Table_create(void) {
    Table * table = calloc(1, sizeof *table);
    return table;
}

// creates a new child row in the table, with the specified number of cols
Row * Row_create(Table *table, int numCols) {

    table = realloc(table, table->rows_count * sizeof *table);
    table->rows_count++;

    Row * newRow = &table->rows[table->rows_count - 1];
    newRow->cols = calloc(numCols * sizeof *newRow->cols);
    newRow->cols_count = numCols;
    return newRow;
}

Sqlite functionality now looks quite simpler:

// this obviously allocates a new table, so somebody will have to 
// free it at some point
Table * table_fetch_from_db(sqlite3 * conn, unsigned char * sql) {

    Table * table = Table_create();

    if (sqlite3_exec(conn, sql, load_single_row, table, NULL)) {
        // handle error
    }

    return table;
}

int load_single_row(void *args, int numCols, char **cols, char **colNames) {

    // we passed a Table* as args
    Table * table = (Table*)args;

    // allocate a new row inside table
    Row * row = Row_create(table, numCols);

    for (int i = 0; i < numCols; i++) {
        int single_col_len = strlen(cols[col_index]);
        row->cols[i] = malloc(single_col_len * sizeof *row->cols[i]);
        strcpy(row->cols[i], cols[i]);            
    }

    return 0;
}

If you're using C99, this code might be slightly simplified using flexible array members, because you don't need to allocate the struct and the inner array separately.

Note that I haven't tested any of this, it lacks functions for freeing tables, and possibly won't fix your actual issue. :)