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.
You can dynamically allocate the array:
char **aList;
int i;
aList = malloc(sizeof(char *) * numStrings);
for (i = 0; i < numStrings; i++)
{
aList[i] = malloc(maxLength);
}
If, by any chance, you can use C++ instead of C, you could always use a C++ vector:
std::vector<std::vector<char> > aList;
You could use a dynamically allocated array. Use malloc()
to make one, realloc()
to change the size of one, and free()
when you're done with it. But this has already been covered by another answer.
Another alternative is to use a linked list. That way you don't have to realloc()
every time you want to extend your array - realloc()
can be rather expensive if it has to copy the entire array to a new location.
If you really want to be able to remove items from the middle of the grid (your questions isn't clear on this), you'll need some kind of multiply linked structure. These are often used to implement sparse arrays, so you can probably find one pre-made.
I'm talking about something like this:
+---+
| A |
+-|\+
| \
| \
| \
| \
| +----+----+----+
| | C0 | C1 | C2 | ...
| +--|-+----+--|-+
| | |
| | |
+-V--+ +--V-+ | +----+
| R0 |->|a0,0|-------+>|a0,3|--> ...
+----+ +--|-+ +--V-+----+
| R1 |-----+----->|a1,2|--> ...
+----+ | +--|-+
... V |
... V
...
Where A is the root node of the object, C is an array of column pointers, R is an array of row pointers, and each cell points to it next neighbor along both its row and column. All cells not explicitly represented are assumed to have some default value (usually NULL or 0).
It is a simple idea, but a fairly picky implementation, with lots of chances to mess up, so use a debugged library if you can.
If your plan is to populate while reading in the file you could do one of two things.
Either store the number of strings as the first element in the file, then the suggestion by jgottula would work well.
Or, must you use an array? You could read them directly into a linked list, and then when finished reading, move them into an array, and free up the linked list.
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.
The situation you've described is precisely what malloc is for -- allocating a variable-length block of memory.