I have a 2-D array of characters e.g. char aList[numStrings][maxLength]
. ideally, during program execution I want to be able to modify the contents of aList i.
2D C-style arrays in general are, sorry the term, kindof wacky ... they look simple and useful on paper but implementing dynamic memory management - handling of allocation failures and cleanups/resizes - is often quite difficult in detail.
What you can do is something like:
/*
* Start with an array that can hold INITIAL_NUM elements of (char*).
*/
char **aList = (char**)malloc(INITIAL_NUM, sizeof(*aList));
int curIdx = 0, curListSz = INITIAL_NUM;
while (more_stuff_to_append) {
/*
* Still space in the existing list ? If not - resize
*/
if (curIdx >= INITIAL_NUM) {
curListSz += ALLOC_INCREMENT_FOR_ALIST;
if ((aList = realloc(aList, curListSz * sizeof(*aList))) == NULL)
error_and_yucky_cleanup("can't resize list, out of memory");
}
/*
* Allocate a new element.
* Note that if it's _known_ in advance that all elements
* are the same size, then malloc'ing a big block and slicing
* that into pieces is more efficient.
*/
if ((aList[curIdx] = malloc(new_elem_size, sizeof(char)) == NULL)
error_and_yucky_cleanup("out of memory");
/*
* put the contents into the new buffer, however that's done.
*/
populate_new_entry(aList[curIdx]);
curIdx++;
}
The big problem with these approaches is usually that cleanup is messy. One needs to go through the array and call free() on every element, plus the additional final one to clean up aList
itself.
If you know all sizes in advance, one can allocate a single memory block that holds both aList
and all the elements. This works via something like:
#define LISTSZ(lst) (NUMSTRINGS_MAX * sizeof(*(lst)))
#define ELEMSZ(lst) (STRINGSIZE_MAX * sizeof(**(lst)))
char **aList = malloc(LISTSZ(aList) + NUMSTRINGS * ELEMSZ(aList));
char *curElem = ((char*)aList) + LISTSZ(aList));
int i;
for (i = 0; i < NUMSTRINGS_MAX; i++) {
aList[i] = curElem;
curElem += ELEMSZ(aList);
}
The advantage of this is that cleanup is trivial - just call free((char*)aList);
and the whole thing is gone. But you can't realloc()
it anymore as that wouldn't insert new space at the beginning of the memory block (where aList[]
is stored).
These things make up really good reasons for using C++ vectors; at least C++ does the cleanup (e.g. on out of memory exceptions) automatically.