views:

77

answers:

4

Executive summary:

  1. How can I define an arbitrarily-sized 2D array in C?
  2. How can I determine the dimensions of that array at compile-time?

Full disclosure:

I'm writing code for an embedded controller. My application requires several lookup tables with different sizes which will all be used by one lookup function (a binary search). Here is what I have so far:

typedef struct
{
    unsigned char count;        // number of rows in the table
    unsigned char width;        // number of bytes in each row
    const unsigned char * data; // pointer to table data[count][width]
}
LookupTable;

// returns the index of a value from within a table
unsigned char Lookup(unsigned long value, const LookupTable * table);

This part is working. What I would like to do now is define these tables in my source without having to manually enter the count and width constants. Here is what I am doing now:

#define T1_count 100
#define T1_width 3
const unsigned char table1_data[T1_count][T1_width] = 
{
    { 0x12, 0x34, 0x56 },
    { 0x12, 0x38, 0x12 },
    ...
};

const LookupTable table1 = { T1_count, T1_width, table1_data };

Here is what I would like to be able to do (pseudo-code, since this array definition will not actually compile):

const unsigned char table1_data[] = 
{
    { 0x12, 0x34, 0x56 },
    { 0x12, 0x38, 0x12 },
    ...
};

const LookupTable table1 =
{
    get_count_expr(table1_data),
    get_width_expr(table1_data),
    table1_data
};

Obviously, get_count_expr and get_width_expr would have to be constant expressions of some sort, based on the size of the table, and not actual function calls.

To be clear, no part of this design is cast in stone. I'm just posting what I have so far, in the hopes that my intent is clear. Any ideas for improvement would be appreciated.

The "why":

These tables will be changed often, and it would make maintenance easier if entries could be added and removed, or the width of a table changed without having to manually adjust the constants each time. Having to keep track of the sizes manually can be error-prone and violates DRY. I'm looking for a better way.

+3  A: 

Hmmm ... you can leave the leftmost size to the compiler:

#define T1_WIDTH 3
const unsigned char table1_data[][T1_WIDTH] = 
{
    { 0x12, 0x34, 0x56 },
    { 0x12, 0x38, 0x12 },
    /* ... */
};
T1_count = sizeof table1_data / sizeof *table1_data;
T1_width = sizeof *table1_data;
pmg
+1 That is a big improvement already. Using the same logic, I could also set `T1_width = sizeof *table1_data;`, which is almost perfect. The only thing left to enter manually is the width in the array definition, which isn't the end of the world.
e.James
`T1_width` was previously defined as a preprocessor macro. I've changed the code (but now you have both the macro and the variable with the same value).
pmg
+1  A: 

Well, but who fills these tables with data? I think that generated sources are better solution.

ruslik
+1 Great suggestion, and I agree. Unfortunately we are not set up that way right now. I'm making these changes to a legacy system, so I am not completely free to change the way things are done on the build side of things.
e.James
@James if the pattern is simple, maybe you could use recursive macros like here? http://graphics.stanford.edu/~seander/bithacks.html#ParityLookupTable
ruslik
A: 

Define table1_data inside a header. You can auto-generate that header using a script. I do something similar to that for some of my projects. I have a CSV file with data and a Ruby or Python script that generates a header from it.

bta
+2  A: 

Well, it's ugly as hell, but I think the only way to do it within the constraints you've listed is to include the data in a string, and than have initialization code parse the string and generate the table. Ideally you'd do that in a script rather than use C to do it, but if it has to be in C, it has to be in C..

Note that in no way do I claim the following to be production code, but it's just a proof of concept...

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define datatable  "\
    { 0x12, 0x34, 0x56 },\
    { 0x14, 0x36, 0x10 },\
    { 0x13, 0x37, 0x11 },\
    { 0x12, 0x38, 0x12 }"

typedef struct
{
    unsigned char count;        // number of rows in the table
    unsigned char width;        // number of bytes in each row
    unsigned char **data; // pointer to table data[count][width]
}
LookupTable;

int parsedatatable(char *data, LookupTable **table) {
    char *p, *sp, save;
    unsigned char *tabledata;
    int count = 0, width = 0;
    unsigned int tmp;
    int i,j;

    /* find count */
    p = strstr(data,"{");
    while (p) {
        p++;
        p = strstr(p, "{");
        count++;
    }
    /* find width */
    p = strstr(data, "{");
    p++;
    sp = strstr(p, "}");
    if (sp != NULL) {
        save = *sp;
        *sp = '\0';
    }
    while (p) {
        p = strstr(p, ",");
        width++;
        if (p != NULL) p++;
    }
    if (sp != NULL) {
        *sp = save;
    }

    printf("Count = %d, width = %d\n",count, width);
    tabledata = (unsigned char *)malloc(width*count*sizeof(unsigned char));
    *table = (LookupTable *)malloc(sizeof(LookupTable));
    (*table)->data = (unsigned char **)malloc(count*sizeof(unsigned char*));
    for (i=0; i<count; i++) {
        (*table)->data[i] = &(tabledata[i*width]);
    }
    (*table)->count = count;
    (*table)->width = width;

   p = data;
    for (i=0; i<count; i++) {
        p = strstr(p,"{");
        if (!p) {
            fprintf(stderr,"Fail (a) reading in data!: %s\n",data);
            free((*table)->data);
            free(tabledata);
            free(*table);
            return -1;
        }
        p++;
        for (j=0; j<width; j++) {
            printf("Scanning <%s>, ",p);
            sscanf(p,"%x",&tmp);
            printf("got %d\n",tmp);
            (*table)->data[i][j] = tmp;
            p = strstr(p,",");
            if (!p && j<width-1) {
                fprintf(stderr,"Fail (b) reading in data!: %d, %d, %s\n",i,j,data);
                free((*table)->data);
                free(tabledata);
                free(*table);
                return -1;
            }
            p++;
        }
    } 
    return 0;
}

void printtable(LookupTable *table) {
    unsigned char i,j;
    for (i=0; i<table->count; i++) {
        printf("{");
        for (j=0; j<table->width; j++) {
            printf("%x ",table->data[i][j]);
        }
        printf("}\n");
    } 
    return;
}

int main(int argc, char **argv) {
    char *data; 
    LookupTable *table;

    data = (char *)malloc(strlen(datatable)+1);
    strcpy(data,datatable);

    parsedatatable(data,&table);
    printtable(table);

    return 0;
}
Jonathan Dursi
+1 just for the effort that you put into this. Wow.
e.James