views:

583

answers:

2

What I am trying to do is create an 2-d array of character strings The following seg faults instantly, what is wrong?

void add2(char***b, char *i)
{
    if (!i) {
       b[0][0] = (char*) malloc(120);
       sprintf(b[0][0], "%s", "hithere");
       b[0][1] = (char*) malloc(120);
       sprintf(b[0][1], "%s", "bithere");
    } else {
       strcat(b[0][0], "\\\\");
       strcat(b[0][0], i);
       strcat(b[0][1], "\\\\");
       strcat(b[0][1], i);
    }

}
void add1(char ***b)
{
 add2(b,NULL);
 add2(b,"one");
 add2(b,"two");
 add2(b,"three");
}

int main()
{
 char **keys[2] = {0};
 int i,j;

 add1(keys);

 for (i = 0; keys[i]; i++)
     for (j = 0; keys[j]; j++)
     {
         fprintf(stderr, "%s\n", keys[i][j]);
         free(keys[i][j]);
     }

}
+2  A: 

When you declare the array keys you are saying to the compiler that you want to work with an array of 2 pointers to pointers to chars and ask it to initialize those pointers to pointer to char to NULL. All good.

Then you call add1(). All good.

Then you call add2() and try to put into the first element of b[0] the return value from malloc(). But the value of b[0] is NULL. The NULL was put there in the main() function. b[0] has no elements!

When you have arrays of arrays (of arrays ...) in the form of pointers you need to malloc() (and free()) every level individually.


Edit

#include <stdlib.h>

int main()
{
    char **keys[2] = {0};
    keys[0] = malloc(20 * sizeof *keys[0]); /* 20 strings */
    keys[1] = malloc(20 * sizeof *keys[1]); /* 20 strings */

    for (int k=0; k<20; k++) {
        keys[0][k] = malloc(120); /* string with 119 chars + '\0' */
        keys[1][k] = malloc(120); /* string with 119 chars + '\0' */
    }

    /* use k[0][0] through k[0][19] */
    /* use k[1][0] through k[1][19] */

    for (int k=0; k<20; k++) {
        free(keys[0][k]);
        free(keys[1][k]);
    }
    free(keys[0]);
    free(keys[1]);

    return 0;
}

I put it all in the main() function, but it's the same idea if the malloc()s and free()s are in their own function.

pmg
so how do i malloc b[0] through b[n]?
malloc(sizeof(char*)*n)? that's my guess, i might be wrong.
pxl
@pxl: actually, to be precise, it should be `malloc(sizeof(char**) * n);`, though in practice `sizeof(char**)` is the same as `sizeof(char*)`. That's the main reason I like to use `sizeof` with the thing I'm allocation memory for: `pointer = malloc(n * sizeof *pointer);`. I don't care if pointer points to an array of structs or a struct with arrays or whatever; the `malloc()` always gets the correct size :)
pmg
well, u can never be too sure... sizeof(char**) would be the right way to do it.
pxl
A: 

The general procedure for dynamically allocating a 2D array of char * is something like this (for any other type T, replace char * with the desired type):

char ***new2DArr(size_t rows, size_t cols)
{
  char ***newArr = NULL;
  size_t i;
  newArr = malloc(sizeof *newArr * rows);
  if (newArr)
  {
    for (i = 0; i < rows; i++)
    {
      newArr[i] = malloc(sizeof *newArr[i] * cols);
      if (newArr[i])
      {
        /* initialize or assign newArr[i][0] - newArr[i][cols-1] here */
      }
    }
  }
  return newArr;
}

This assumes you know how many rows and columns you want ahead of time. Note that you have allocated a table of pointers; you will still have to allocate memory for each string entry, like so:

char **myArr = new2DArr(10, 10);
myArr[0][0] = malloc(strlen("Hello, World") + 1);
if (myArr[0][0])
{
    strcpy(myArr[0][0], "Hello, World");
}

If you want to add new rows or new entries to existing rows, you're going to have to do some extra bookkeeping.

Here's one example that extends a single row:

char **extendRow(char **row, char *newEntry, size_t *numEntries)
{
  char **tmp = realloc(row, *numEntries + 1);
  if (tmp)
  {
    row = tmp;
    row[*numEntries] = malloc(strlen(newEntry) + 1);
    if (row[*numEntries])
    {
      strcpy(row[*numEntries], newEntry);
      (*numEntries)++;
    }
  }
  return row;
}

And you'd call it like

table[i] = extendRow(table[i], "This is a test", &entryCount);

The result is assigned back to table[i] in the event that the pointer value is changed by realloc(). I'm doing it this way instead of passing a pointer to table[i] just to keep the pointer gymnastics to a minimum.

John Bode