This question already has answers here:
C dynamically growing array
(10 answers)
Closed 6 years ago.
Can anyone explain to me the easiest way to create dynamically a 2D string array with stable second dimension? I've got a txt file with some strings in it and I want to transfer this txt to an array. So I want to associate the txt line number with the first dimension and the string itself with the second dimension. The second dimension is the number of characters in every row (which is stable because every line in the txt has a certain syntax) So if I have in my txt:
hello how (newline)
are youuu
*(I wrote youuu because as I said, every line has the same number of characters).
I want something like:
array[0]["hello how"],
array[1]["are youuu"]
Non numerical keys are not allowed in C. You're trying to do some PHP and JavaScript nonsense in a language that only works with numbers.
But, with C there is always 2 roads to hell.
char *lookup_key(int index, char *key) { ... }
printf(lookup_key(0, "hello how"));
If you know the length of the strings and how many you have, you can configure the array like this
char strings[numLines][strLen+1];
you can then access the array like this
strcpy(strings[1], "test2");
If you don't know anything beforehand, you need a pointer to pointer array and then use malloc to allocate space as the array grow, free when you are done.
dynamic in C implies you will need to use one of [c][m]alloc to create memory for your strings. And 2D implies an array of char arrays. Assuming you know the number of strings and the longest string needed, the following will create memory to contain them:
char ** Create2DStr(ssize_t numStrings, ssize_t maxStrLen)
{
int i;
char **a = {0};
a = calloc(numStrings, sizeof(char *));
for(i=0;i<numStrings; i++)
{
a[i] = calloc(maxStrLen + 1, 1);
}
return a;
}
The following will free the memory created above:
void free2DStr(char ** a, ssize_t numStrings)
{
int i;
for(i=0;i<numStrings; i++)
{
if(a[i]) free(a[i]);
}
free(a);
}
These can be called like this:
...
char **strArray = {0};
strArray = Create2DStr(10, 20);
//Use strArray...
free2DStr(10);
Giving 10 arrays, each able to contain 20 char, plus a NULL. (The + 1 after maxStrLen provides the extra space for the NULL).
If you want to save each line of the file as a row in the array, use a 2D array of char:
char fileContents[NUM_LINES][LINE_LENGTH + 1]; // +1 for zero terminator
If you don't know how many lines you have up front, you'll need to do some memory management. First, you'll need to allocate an initial extent:
#define INITIAL_EXTENT 20 // or some good starting point
char (*fileContents)[LINE_LENGTH + 1] = malloc( sizeof *fileContents * INITIAL_EXTENT );
if ( !fileContents )
{
// malloc failed; fatal error
fprintf( stderr, "FATAL: could not allocate memory for array\n" );
exit( EXIT_FAILURE );
}
size_t numRows = INITIAL_EXTENT; // number of rows in array
size_t rowsRead = 0; // number of rows containing data
As you read from the file, you'll check to make sure you have room in the array; if you don't, you'll need to extend the array with a realloc call, which is a potentially expensive operation. A common technique is to double the size of the array each time you extend it - that minimizes the total number of realloc calls. The risk is some internal fragmentation if you double the array size because you need just one more row, but that's probably something you can analyze around:
char tmpBuf[LINE_LENGTH + 2]; // account for newline in input buffer
while ( fgets( tmpBuf, sizeof tmpBuf, inputFile ) )
{
/**
* Check to see if you have any room left in your array; if not,
* you'll need to extend it. You'll probably want to factor this
* into its own function.
*/
if ( rowsRead == numRows )
{
/**
* Use a temporary variable for the result of realloc in case of failure
*/
char (*tmp)[LINE_LENGTH + 1] =
realloc( fileContents, sizeof *fileContents * ( 2 * numRows ) );
if ( !tmp )
{
/**
* realloc failed - we couldn't extend the array any more.
* Break out of the loop.
*/
fprintf( stderr, "ERROR: could not extend fileContents array - breaking out of loop\n" );
break;
}
/**
* Otherwise, set fileContents to point to the new, extended buffer
* and update the number of rows.
*/
fileContents = tmp;
numRows *= 2;
}
// strip the newline from the input buffer
char *newline = strchr( tmpBuf, '\n' );
if ( newline )
*newline = 0;
strcpy( fileContents[rowsRead++], tmpBuf );
}
Related
i am new to coding and am having a problem with the following.
I am required to read from a text file, each row will contain:
command arg1 arg2 arg3...
command arg1 arg2
command
command arg1 arg2 ... arg9
etc
What i am trying to do is read this entire file into a 2D string array called array using malloc. This way if i were to do:
array[0][0] i would access command arg1 arg2 arg3
array[1][0] i would access command arg1 arg2
and so on.
I also know there is a max of 100 rows and 256 characters per line. Below is how i attempted to declare my malloc however when trying to allocate strings to the 2d array, it only allocated single characters.
I dont quite understand how to do this, detailed explanation would be greatly appreciated
int row = 100;
int col = 256;
int **array;
array = (int**)malloc(row*sizeof(array));
if(!array){
perror("Error occured allocating memory");
exit(-1);
}
for(int i = 0; i<row;i++){
array[i] = (int*)malloc(col*sizeof(array));
}
If I got it right, you need to set up a two dimensional array of char * instead of int.
That is, you address the correct row by dereferencing once (array[the_ith_row]), and then address the correct element(command, arg1, arg2, ...) by another dereference (array[the_ith_row][the_jth_col]).
Notice: strings like "arg1" and "command" are treated as "array of chars" therefore you need to store a char * in order to access them. int could only store one char(with some extra space consumption), therefore won't work here.
So, the correct one should look like:
#include <string.h>
int row = 100;
int col = 256;
char ***array;
array = (char ***)malloc(row * sizeof(char **));
if (!array) {
perror("Error occured allocating memory");
exit(-1);
}
for (int i = 0; i < row; i++) {
array[i] = (char **)malloc(col * sizeof(char *));
}
// Do some example assignments
for (int j = 0; j < col; j++) {
array[i][j] = strcpy((char *)malloc(100), "test_string");
}
//therefore printf("%s", array[0][0]); will print test_string"
UPDATE: I missed some * here..
You are allocating using sizeof(array) which is not the correct unit of allocation that you want.
It looks like what you want are two different kinds of memory allocations or objects.
The first is an array of pointers to character strings since the file data is a series of character strings.
The second kind of memory allocation is for the memory to hold the actual character string.
The first kind of memory allocation, to an array of pointers to character strings would be:
char **pArray = malloc (100 * sizeof(char *)); // allocate the array of character string pointers
The second kind of memory allocation, to a character string which is an array of characters would be:
char *pString = malloc ((256 + 1) * sizeof(char)); // allocate a character array for up to 256 characters
The 256 + 1 is needed in order to allocate space for 256 characters plus one more for the end of string character.
So to allocate the entire needed space, you would do the following:
int iIndex;
int nMax = 100;
char **pArray = malloc (nMax, sizeof(char *)); // allocate array of rows
for (iIndex = 0; iIndex < nMax; iIndex++) {
pArray[iIndex] = malloc ((256 + 1) * sizeof (char)); // allocate a row
}
// now use the pArray to read in the lines of text from the file.
// for the first line, pArray[0], second pArray[1], etc.
Using realloc()
A question posed is using the realloc() function to adjust the size of the allocated memory.
For the second kind of memory, memory for the actual character string, the main thing is to use realloc() as normal to expand or shrink the amount of memory. However if memory is reduced, you need to consider if the text string was truncated and a new end of string terminator is provided to ensure the text string is properly terminated with and end of string indicator.
// modify size of a text string memory area for text string in pArray[i]
// memory area. use a temporary and test that realloc() worked before
// changing the pointer value in pArray[] element.
char *p = realloc (pArray[i], (nSize + 1) * sizeof (char));
if (p != NULL) {
pArray[i] = p; // valid realloc() so replace our pointer.
pArray[i][nSize] = 0; // ensure zero terminator for string
}
If you ensure that when the memory area for pArray] is set to NULL after allocating the array, you can just use the realloc() function as above without first using malloc() since if the pointer in the argument to realloc() is NULL then realloc() will just do a malloc() for the memory.
For the first kind of memory, you will need to consider freeing any memory whose pointers may be destroyed when the allocated array is shortened. This means that you will need to do a bit more management and keeping management data about the allocated memory area. If you can guarantee that you will only be increasing the size of the array and never shortening it then you don't need to do any management and you can just use the same approach as provided for the second kind of memory above.
However if the memory allocated for the first kind of memory will need to be smaller as well as larger, you need to have some idea as to the size of the memory area allocated. Probably the easiest would be to have a simple struct that would provide both a pointer to the array allocated as well as the max count of items the array can hold.
typedef struct {
size_t nCount;
char **pArray;
} ArrayObj;
Warning: the following code has not been tested or even compiled. Also note that this only works for if the memory allocation will be increased.
Then you would wrap the realloc() function within a management function. This version of the function only handles if realloc() is always to expand the array. If making it smaller you will need to handle that case in this function.
ArrayObj ArrayObjRealloc (ArrayObj obj, size_t nNewCount)
{
// make the management a bit easier by just adding one to the count
// to determine how much memory to allocate.
char **pNew = realloc (obj.pArray, (nNewCount + 1) * sizeof (char *));
if (pNew != NULL) {
size_t ix;
// realloc() worked and provided a valid pointer to the new area.
// update the management information we are going to return.
// set the new space to NULL to have it in an initial and known state.
// initializing the new space to NULL will allow for knowing which array
// elements have a valid pointer and which don't.
obj.pArray = pNew;
for (ix = nNewCount; ix >= obj.nCount; ix--) {
obj.pArray[ix] = NULL;
}
obj.nCount = nNewCount;
}
return obj;
}
and use this function something like
AnyObj obj = {0, NULL};
// allocate for the first time
obj = ArrayObjRealloc (obj, 100);
// do stuff with the array allocated
strcpy (obj.pArray[i], "some text");
// make the array larger
obj = ArrayObjRealloc (obj, 150);
I want to know how to create a dynamic array of 2 strings per array. And I'm not sure how. I know how to create an array of strings, but not one of "n" dimensions, and i want to know how.
Here are some parts of my code, which is just currently an array of strings:
First I declared my array inside an struct:
typedef struct{
char bloqueCedula[7];
short bloqueDia;
char **bloqueLibrosPrestados;//This is the array i want to modify
}RegistroControl;
then I initialize it on my first registration:
void primerRegistro(RegistroArchivo regArch, RegistroControl *regControl) {
strcpy(regControl->bloqueCedula, regArch.cedula);
regControl->bloqueDia = regArch.dia;
regControl->bloqueLibrosPrestados = malloc(cont * sizeof(regControl->bloqueLibrosPrestados));
regControl->bloqueLibrosPrestados[0] = malloc(7 * sizeof(char));
strcpy(regControl->bloqueLibrosPrestados[0], regArch.codigoLibro);
imprimirCabecera();
//printf("%s\n", regControl->bloqueLibrosPrestados[cont-1]);
}
In the code above I made each string of a length 7, because I already know the length of what I need to copy on it, which is 6.
This is how I kept making it grow:
void procesarRegistro(RegistroArchivo regArch, RegistroControl *regControl) {
cont++;
regControl->bloqueLibrosPrestados = realloc(regControl->bloqueLibrosPrestados,cont * sizeof(regControl->bloqueLibrosPrestados));
regControl->bloqueLibrosPrestados[cont-1] = malloc(7 * sizeof(char));
strcpy(regControl->bloqueLibrosPrestados[cont-1], regArch.codigoLibro);
printf("%s\n", regControl->bloqueLibrosPrestados[cont-2]);
}
I want to know how to add another string of 2 length next to the one that is 7 length. This codes are parts of a code that reads from a file until it reaches EOF, that's why I used a counter which is "cont".
You have a char**.
Suppose a. Allocate some memory to a.
a= malloc(sizeof(char*)*10);
if( a == NULL)
{
fprintf(stderr,"%s","error in malloc");
exit(1);
}
Now for each of the position of a you do the same thing only this time it is for char.
a[i]= malloc(sizeof(char)*100);
if( a[i] == NULL)
{
//...
}
Free them when done.
free(a[i]); //for each i.
free(a);
a=NULL;
Now this is 2 dimension
Now I don't know when you need more than this. This can always be extended.
char *** aa; But it is not needed. {read this}
If you need it then you should think over your data representation.
Theoretical idea for N dimensional strings
For n dimensional you can do this:-
char **...n...** complicated= malloc(sizeof(char**...n...>**)*N1);
// malloc check
for(int i=0;i<N1;i++)
complicated[i]= malloc(sizeof(char**...(n-1)...**)*N2);
...
I would like to have a dynamic 2D array of chars, max 200 and scan to it.
I am not really sure how to do this, all I came up with is to define the dynamic arrays (and I don't even know if it is right):
char **arr = (char **)malloc( (len+1) * sizeof(char*) );
for (i=0; (len+1)>0; i++)
char *arr1 = (char *) malloc ( (len+1) * sizeof(char) );
But I am not sure what to put in the (len) - let's say I would like an array of 51x150, depending on the scanned input.
How to allocate the array based on the scanned value and print it?
An example of input:
####.#...##
##.##.#.###
##.###.#.##
#.##.##.#..
You probably want to use a real 2D array and not a segmented look-up table.
size_t x = 51; // user input
size_t y = 150; // user input
char (*arr)[y] = malloc( sizeof(char[x][y]) );
...
arr[i][j] = something;
...
free(arr);
This is much faster and safer than a segmented look-up table. This also allows you to memcpy data in and out of the array, even if that data is larger than one line.
I needed a character array containing a dynamic number of character arrays based on the number of files in a specific folder. I was able to accomplish this by initializing char (*FullPathNames)[MAX_FILENAME_AND_PATHNAME_LENGTH] and then using FullPathNames = malloc( sizeof(*FullPathNames) * NumOfFiles * MAX_FILENAME_AND_PATHNAME_LENGTH ) ) after I know how many files another function discovered( which I have not provided). This process works flawlessly.
I can only use ANSI C; I am specifically using LabWindows CVI 8.1, to compile my code. I cannot use any other compiler. The below code is doing what I want. I can fill this array easily enough with the following code:
Strcpy(FullPathNames[0],”Test Word”);
char (*FullPathNames)[MAX_FILENAME_AND_PATHNAME_LENGTH];
size_t Size;
NumOfFiles = NumberOfUserFiles(“*.txt”, “C:\\ProgramData” );
FullPathNames = malloc( sizeof(*FullPathNames) * NumOfFiles * MAX_FILENAME_AND_PATHNAME_LENGTH ) );
Size = sizeof(*FullPathNames) * NumOfFiles;
Memset(FullPathNames,0,Size);
However, I would like to be able to pass FullPathNames which is an array of pointers to a variable amount of character arrays into a method. I want this method to be able to remove a single character array at a given index.
I am calling the method with the following code.
Remove_Element(FullPathNames,1, NumOfFiles);
The code for Remove_Element:
void Remove_Element( char (*Array)[MAX_FILENAME_AND_PATHNAME_LEN], int Index, int Array_Length )
{
int i;
char String[MAX_FILENAME_AND_PATHNAME_LEN];
char (*NewArray)[MAX_FILENAME_AND_PATHNAME_LEN];
int NewLength = Array_Length - 1;
size_t Size;
NewArray = malloc( sizeof( *NewArray) * NewLength * ( MAX_FILENAME_AND_PATHNAME_LEN ) );
Size = sizeof( *NewArray ) * NewLength;
memset(NewArray, 0, Size);
for ( i = Index; i < Array_Length - 1; i++ )
{
memcpy(String,Array[i+1],MAX_FILENAME_AND_PATHNAME_LEN); // Remove last index to avoid duplication
strcpy( Array[Index], String );
}
Array = NewArray;
}
My expectation of what I have currently is that the original data of FullPathNames remains except for the index that I removed, by copying data from index + 1, and the original pointers contained within FullPathNames is of course updated. Since I also wanted to shrink the array I attempted to set the array equal to the new array. The following information explains my attempts at debugging this behavior.
The watch variables present the following information as I enter the method.
FullPathNames = XXXXXX
NewArray = Unallocated
Array = XXXXXX
After I fill the new temporary Array the following happens:
FullPathNames = XXXXXX
NewArray = YYYYY
Array = XXXXXX
As I exit the method the following happens:
FullPathNames = XXXXXX
NewArray = YYYYY
Array = YYYYY
I was attempting to modify FullPathNames by passing it in as a pointer. I originally tried this task by using realloc but that just resulted in a free pointer exception.
Notes:
MAX_FILENAME_AND_PATHNAME_LENGTH = 516;
If I understand correctly, what you want to do is to modify the FullPathNames Pointer in the code part where you initialize your original array.
With your declartion of FullPatchNames
char (*FullPathNames)[MAX_FILENAME_AND_PATHNAME_LENGTH]
you basically declare a pointer to an array of MAX_FILENAME_AND_PATHNAME_LENGTH char elements. With your call to void Remove_Element(...) you just give a copy of this pointer to the local variable Array valid inside your function. Because of this Array = NewArray;, only changes the local copy of your pointer inside the function, not FullPathNames.
If you want to change the value of FullPathNames you must give a pointer to this pointer to your function. The Prototype of Remove_Element must look like this:
void Remove_Element( char (**Array)[MAX_FILENAME_AND_PATHNAME_LEN],
int Index, int Array_Length )
Now Array is a Pointer to an Pointer to an (one dimansional) array of char. By dereferencing this Pointer, you can change your original Pointer FullPathNames to point to your new object you created inside your function. You must modify the call to this function to Remove_Element(&FullPathNames,1, NumOfFiles);. To read from Array, you must dereference it using the * operator:
memcpy(String,*Array[i+1],MAX_FILENAME_AND_PATHNAME_LEN);
...
Array = NewArray;
Warning: This code will now produce a memory leak, since you are loosing your reference to your orignal object. You should remove this using the free() function somewhere in your code!
There seems to exist a certain lack of knowledge about the syntax in C language first and foremost.
char (*FullPathNames)[MAX_FILENAME_AND_PATHNAME_LENGTH]
This is one example. The syntax shown here would be read by a c- programmer as:
Semicolon is missing - maybe #define voodoo somewhere!
char (*FullPathNames)... - a function pointer! oh wait why square brackets next?!
Maybe he wanted to say char *FullPathNames; or he wanted char FullPathNames[MAX_FILENAME_AND_PATH_NAME_LENGTH]; Hm...
So here the first 101:
char foo[50]; // A fixed size array with capacity 50 (49 chars + '\0' max).
char *foo = NULL; // a uninitialized pointer to some char.
char (*foo)(); // a pointer to a function of signature: char(void).
char *foobar[50]; // This is an array of 50 pointers to char.
Depending on where your char foo[50]; is located (in the code file, in a function, in a structure definition), the storage used for it varies.
char foo1[50]; // zerovars section.
char foo2[50] = { 0 }; // initvars section
char foo3[50] = "Hello World!"; // also initvars section
void FooTheFoo( const char *foo )
{
if(NULL != foo )
{
printf("foo = %s\n", foo);
}
}
int main(int argc, const char *argv[])
{
char bar[50] = "Message from the past."; // bar is located on the stack (automatic variable).
FooTheFoo(bar); // fixed size array or dynamic array - passed as a (const pointer) in C.
return 0;
}
Now we got the basics down, lets look at 2-dimensional dynamic array.
char **matrix = NULL;
A pointer to a pointer of char. Or a pointer to an array of pointers to chars or an array of pointers to pointers to arrays of chars.
As lined out, there is no "meta" information regarding to what a char* or a char ** point to beyond that finally the dereferenced item will be of type char. And that it is a pointer to a pointer.
If you want to make a 2-dimensional array out of it, you have to initialize accordingly:
const size_t ROW_COUNT = 5;
const size_T COL_COUNT = 10;
char **myMatrix = malloc(sizeof(char *) * ROW_COUNT);
// check if malloc returned NULL of course!
if( NULL != myMatrix )
{
for(size_t row = 0; row < ROW_COUNT; row++ )
{
myMatrix[row] = malloc(sizeof(char) * COL_COUNT);
if( NULL == myMatrix[row] ) PanicAndCryOutLoudInDespair();
for(size_t col = 0; col < COL_COUNT; col++ )
{
myMatrix[row][col] = 0;
}
// of course you could also write instead of inner for - loop:
// memset(myMatrix[row], 0, sizeof(char) * COL_COUNT);
}
}
Last not least, how to pass such a 2-dimensional array to a function? As the char** construct does not contain the meta information regarding sizes, in the general (inner not a 0 terminated string) case, you would do it like that:
void FooIt( const char **matrix, size_t rowCount, size_t colCount )
{ // Note: standard checks omitted! (NULL != matrix, ...)
putchar(matrix[0][0]);
}
Last, if you want to get rid of your 2D dynamic array again, you need to properly free it.
void Cleanup2DArray( char **matrix, size_t rowCount )
{
for(size_t row = 0; row < rowCount; row++ )
{
free(matrix[row];
}
free(matrix);
}
The only thing more to say about it I leave to other gentle contributors. One thing coming to mind is how to express const-ness correctly for those multi-dimensional things.
const char **
const char const * const *
etc.
With this, you should be able to spot the places where you went wrong in your code and fix it.
The pointer you're passing is just a value. That it holds an address means you can dereference it to modify what it points to, but it doesn't mean changing its value directly (your assignment statement) will affect the caller-parameter. Like everything else in C, if you want to modify something by-address, then an address is exactly what you need to do it. If the thing you're modifying is a pointer, then the address of the pointer (through a pointer-to-pointer parameter) is the generally prescribed solution.
However, I can tell you the syntax and housekeeping to do that is... uninviting in your case. A simple pointer is easy enough, but a pointer-to-array-of-N isn't so simply. Were I you his would simply use the return result of the function itself, which is otherwise currently being unused and void. Declare your function like this:
char (*Remove_Element( char (*Array)[MAX_FILENAME_AND_PATHNAME_LEN],
int Index, int Array_Length ))[MAX_FILENAME_AND_PATHNAME_LEN]
{
....
return Array; // or whatever else you want to return so
// long as the type is correct.
}
and simply have the caller do this:
Array = RemoveElement(Array, Index, Array_Length);
A working variation of my solution appears below. The reason I had to do it this way is because while I was able to dereference (**Array)[MAX_FILENAME_AND_PATHNAME_LEN] I was only able to modify the first string array in the array.
The string array was initialized and filled several strings. While I could reference a string contained within *Array[0] but was unable to reference any of the other strings. The resulting array will replace the original array. This method will only work in the initial code block where the array to be replaced is initialized.
#define MAX_FILENAME_AND_PATHNAME_LEN MAX_FILENAME_LEN + MAX_PATHNAME_LEN
/*
This method was designed to free the memory allocated to an array.
*/
void FreeFileAndPathArrays( char (*Array)[MAX_FILENAME_AND_PATHNAME_LEN] )
{
free( Array );
}
/*
This method was designed to remove an index from an array. The result of this method will shrink the array by one.
*/
void Remove_Element( char (**ArrayPointer)[MAX_FILENAME_AND_PATHNAME_LEN],int Index, int *Array_Length, char (*Array)[MAX_FILENAME_AND_PATHNAME_LEN] )
{
int i = 0;
int j = 0;
char String[MAX_FILENAME_AND_PATHNAME_LEN];
char (*NewArray)[MAX_FILENAME_AND_PATHNAME_LEN];
char (*GC)[MAX_FILENAME_AND_PATHNAME_LEN];
int Length = *Array_Length;
int NewLength = Length - 1;
size_t Size;
NewArray = malloc( sizeof( *NewArray) * NewLength * ( MAX_FILENAME_AND_PATHNAME_LEN ) );
Size = sizeof( *NewArray ) * NewLength;
memset(NewArray, 0, Size);
UI_Display("Test Block:");
for ( j = 0; j < NewLength; j++ )
{
if ( j != Index )
{
memcpy(String,Array[j],MAX_FILENAME_AND_PATHNAME_LEN);
strcpy( Array[Index], String );
Fill(NewArray,String,j);
UI_Display(String);
}
}
GC = Array;
*ArrayPointer = NewArray;
free(GC);
*Array_Length = *Array_Length - 1;
}
/*
This method was designed to place a string into an index.
*/
void Fill( char (*Array)[MAX_FILENAME_AND_PATHNAME_LEN], const char * String, int Index)
{
strcpy( Array[Index], String );
}
/*
This method was designed to place fill each string array contained within the array of string arrays with 0's.
*/
void PrepareFileAndPathArrays( char (*FullPathNames)[MAX_FILENAME_AND_PATHNAME_LEN], int ROWS )
{
size_t Size;
Size = sizeof( *FullPathNames ) * ROWS;
memset(FullPathNames, 0, Size);
}
For an assignment, part of what I have to do involves the use of malloc and realloc. I first create a 2D array of chars, the dimensions being the number of lines and the number of characters. I then use malloc to allocate enough memory to store input from some file. Using fgets I read one line in at a time, and store it in the array. This part works fine (or so I think). The problem comes in when I try to reallocate memory for more lines if need be. The program flow is supposed to be like this:
Create a character array of 50 lines, with 80 characters per line (working)
Use fgets to read one line at a time and save it to the array (working)
When 50 lines have been read, reallocate the array to allow for 100 lines (not working)
Keep reallocating as need be (not working)
This is what I have so far (the core of it at least, I omitted irrelevant code):
#define NUMBER_OF_LINES 50
#define CHARACTERS_PER_LINE 80
FILE *inputFile = fopen("some.text", "r");
char **lines;
lines = malloc(NUMBER_OF_LINES * sizeof(*lines));
int i;
for (i = 0; i < NUMBER_OF_LINES; i++)
*(lines+i) = malloc(CHARACTERS_PER_LINE * sizeof(char));
int linesRemaining = NUMBER_OF_LINES;
int reallocCount = 1;
i = 0;
while (!feof(inputFile)) {
if (!linesRemaining) {
reallocCount++;
lines = realloc(lines, (NUM_OF_LINES * reallocCount) * sizeof(*lines));
linesRemaining = NUM_OF_LINES;
}
fgets(*(lines+i), CHARS_PER_LINE, inputFile);
i++;
linesRemaining--;
}
My gut tells me the problem is with the realloc, so I'll explain what I think it's doing.
realloc(lines, (NUM_OF_LINES * reallocCount) * sizeof(*lines));
The first argument, lines, is the pointer I would like to reallocate a certain amount of memory. NUM_OF_LINES is the amount I would like to increase the size by. I multiply this by reallocLinesCount, which is a counter that keeps track of how many sets of 50 lines I ought to have. The sizeof(*lines) part is the size of a pointer to a char.
Thank you for reading and any help is greatly appreciated :)
EDIT: thank you all for the responses; I do not have time right now to read all of the answers right now, but all of your answers will be more thoroughly read and understood once this imminent deadline has passed :D
My motto is: "say what you mean". In your case, you MEAN to enlarge your array when it's not big enough to hold your data.
FILE *in; // you fill this in
int nlines=50; // initial value
char **buffer=malloc(nlines * sizeof *buffer);
int i=0;
for(int i=0; !feof(in); ++i)
{
if(i>=nlines)
buffer=realloc(buffer, (nlines+=50)*sizeof *buffer);
buffer[i]=malloc(80);
fgets(buffer[i], 80, in);
}
realloc() will often find out that there is not enough available room to expand the existing array in-place; in that case, it will create an entirely new array of the specified size, copy the contents of the old array to the new one, deallocate the old array, and return a pointer to the new one. So you should write
char **oldLines = lines;
lines = realloc(...);
(the purpose of oldLines is to keep the original pointer in case realloc() runs out of memory and returns NULL, as per #Brian L's tip).
This is how you should realloc:
char **new_lines = realloc(lines, (NUM_OF_LINES * ++reallocLinesCount) * sizeof(*lines));
if (new_lines)
{
lines = new_lines;
}
else
{
// Memory allocation fails. Do some error handling.
}
Read realloc reference for details.
EDIT
You need more allocation for each new lines.
You are allocating more pointers to lines but not the lines themselves. It is in your code at the beginning:
for (i = 0; i < NUMBER_OF_LINES; i++)
*(lines+i) = malloc(CHARACTERS_PER_LINE * sizeof(char));
So after you allocated your number of lines for each line you allocate the space for the line itself. You forgot to do this for the new lines when you reallocate.
Let's first see how realloc() works. It returns a pointer to new
memory on success, and NULL on failure. On failure, it doesn't
touch the old memory, and on success, it free()'s it, after copying
your data to the new place.
So, the way to use realloc() safely is:
/* allocate memory using malloc() */
ptr = malloc(N * sizeof *ptr);
/* make sure malloc succeeded */
...
/* call realloc() */
new_ptr = realloc(ptr, M * sizeof *new_ptr);
/* see if it succeeded */
if (new_ptr) {
/* okay, we can set ptr */
ptr = new_ptr;
} else {
/* realloc failed, old pointer still valid */
}
So, the first thing is that you are using realloc() incorrectly.
You should never say x = realloc(x, ...);, because if realloc()
fails, you assign x to NULL, and the old memory is lost. This is
a memory leak.
Now, on to your problem. Let's say you have successfully read
NUMBER_OF_LINES lines. Now you want to make room for an additional
NUMBER_OF_LINES lines. You would do:
char **new_lines = realloc(lines, NUMBER_OF_LINES*reallocCount*sizeof *new_lines);
if (new_lines) {
lines = new_lines;
} else {
fprintf(stderr, "realloc failed!\n");
return;
}
/* Now, lines[NUMBER_OF_LINES] to lines[2*NUMBER_OF_LINES-1] are
* available to point someplace useful. They don't point anywhere
* useful yet. We have to allocate memory for them, just like earlier */
start = NUMBER_OF_LINES*reallocCount;
for (i=0; i < NUMBER_OF_LINES; ++i) {
/* You weren't allocating memory here, and were writing to
* lines[0] through lines[NUMBER_OF_LINES-1], which is not what
* you want. */
lines[start+i] = malloc(CHARS_PER_LINE * sizeof *lines[start+i]);
/* check the result of malloc here */
}
fgets(lines[start+i], CHARS_PER_LINE, inputFile);
One final note: it's almost always wrong to use while (!feof(fp))
to read lines from a file.