0

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.

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;
}

Here is an updated solution using structs which as Groo pointed out is the only way to keep track of the row and columns sizes.

typedef struct{
    char ***data;
    int row_size;
    int *col_size;
}Table;

static int db_select_cb(void *p_table ,int argc, char **argv, char **azColName){
    Table **table = (Table **)p_table;

    (*table)->data = realloc((*table)->data,sizeof(char **)*((*table)->row_size+1));
    (*table)->data[(*table)->row_size] = malloc(sizeof(char *)*(argc));
    (*table)->col_size = realloc((*table)->col_size,sizeof(int)*((*table)->row_size+1));

    int col_index;
    for(col_index = 0;col_index < argc;col_index++){
        (*table)->data[(*table)->row_size][col_index] = malloc(sizeof(char)*(strlen(argv[col_index])+1));
        strcpy((*table)->data[(*table)->row_size][col_index],argv[col_index]);
    }
    (*table)->col_size[(*table)->row_size] = col_index;
    (*table)->row_size++;
    return 0;
}

Table *db_select(sqlite3 *conn,unsigned char *zSQL){
    Table *table = malloc(sizeof(Table));
    table->row_size = 0;
    table->data = NULL;
    table->col_size = NULL;

    char *err = 0;
    int cerr = sqlite3_exec(conn,zSQL,db_select_cb,(void*)&table,&err);
    if(cerr){
        printf(":: SQL ERROR IN \"db_select\" || %s ||\n", err);
        sqlite3_free(err);
        return 0;
    }
    return table;
}
TheAschr
  • 899
  • 2
  • 6
  • 19
  • I cannot remember seeing so many stars in one question before:( – Martin James Aug 21 '17 at 18:23
  • 7
    A three-star programmer should know how to use a debugger. – Cody Gray - on strike Aug 21 '17 at 18:25
  • XD I know how to use valgrind but all the debuggers on windows are behind a paywall or don't really tell me much. – TheAschr Aug 21 '17 at 18:27
  • You realloc 'data' - a local in 'db_select_cb()'. That may modify 'data', but that pointer is lost upon return. I can't see that ending well. – Martin James Aug 21 '17 at 18:30
  • 2
    @TheAschr Your code is a huge multilevel UB. My advice - reconsider and write it again – 0___________ Aug 21 '17 at 18:30
  • Are you working with 3D matrixes? If not you should not have to allocate `char ***` anywhere... – Felix Guo Aug 21 '17 at 18:31
  • @Cody Gray he should use even the 4 stars here so it is a 4 star code :) – 0___________ Aug 21 '17 at 18:32
  • Its a 2d matrix of char arrays so I think it would techically be considered a 3d matrix. Would it be better to pass the data arround using structures. I always thought those were discouraged in C because of the ambiguity. – TheAschr Aug 21 '17 at 18:36
  • 1
    `I always thought those were discouraged in C` where did you get this information from? – 0___________ Aug 21 '17 at 18:40
  • The `malloc` in `db_select` doesn't make any sense. 1) If you are not using `data` outside of `db_select_cb`, why are you allocating it outside? 2) If you *are* using `data` outside of `db_select_cb`, then you need to pass `&data` and it *still* doesn't have to be allocated needlessly. – vgru Aug 21 '17 at 18:43
  • @Groo I allocated it in db_select because I didn't think I could use realloc without first mallocing it so it has a starting point. I'll try your suggestion in a bit. Thanks! – TheAschr Aug 21 '17 at 18:55
  • `(char ***)malloc(sizeof(char ***)*(row_index+1));` Your asterisk counter is broken. You get lucky because `sizeof(char***)` is probably the same as `sizeof(char**)`. – n. m. could be an AI Aug 21 '17 at 19:26
  • Which begs the obligatory: There is no need to cast the return of `malloc`, it is unnecessary. See: [**Do I cast the result of malloc?**](http://stackoverflow.com/q/605845/995714) – David C. Rankin Aug 21 '17 at 20:07

1 Answers1

0

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. :)

vgru
  • 49,838
  • 16
  • 120
  • 201