Thread: Woes about 2D arrays, pointers and allocating memory

  1. #1
    Registered User
    Join Date
    Apr 2010
    Posts
    12

    Woes about 2D arrays, pointers and allocating memory

    I'm writing a program to do some numerical analysis. I am not an experienced programmer, though I'm quite good at the Matlab scripting language. I have decided to learn C since it's pretty much standard in what I do.

    I do have a problem understanding 2D arrays and the equivalence between double pointers. I can make a 2D array like this:


    Code:
    int **array;
    	array = malloc(nrows * sizeof(int *));
    	if(array == NULL)
    		{
    		fprintf(stderr, "out of memory\n");
    		exit or return
    		}
    	for(i = 0; i < nrows; i++)
    		{
    		array[i] = malloc(ncolumns * sizeof(int));
    		if(array[i] == NULL)
    			{
    			fprintf(stderr, "out of memory\n");
    			exit or return
    			}
    		}
    That allocates the memory for the array. I can now acces and write to the array via 2 for loops targeting array[i][j].

    However it seems like an awful waste to call malloc so many times when creating the array. Could I just call malloc(ncolumns*nrows*sizeof(int))? If so, how would I access and write to the array?

  2. #2
    Novice
    Join Date
    Jul 2009
    Posts
    568
    I don't know why'd you want to, but this is how I would have done it. Maybe there's a better way, tho.
    Code:
    #include <stdio.h>
    #include <stdlib.h>
    
    int main(void) {
        
        const int row = 3;
        const int col = 11;
        
        int* p;
        p = (int*) malloc(row * col * sizeof(int));
        
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < col; j++) {
                p[(i * col) + j] = j;
            }
        }
    	return 0;
    }

  3. #3
    Registered User
    Join Date
    Apr 2010
    Posts
    12
    Thanks, it looks like it works. I figured it would be much more time-efficient to call malloc one time instead of several times.

    One thing that does confuse me however is how much memory the 2 methods use. The method I posted used nrows*sizeof(int *) +ncolumns*sizeof(int). The other method is simply (nrows+ncolumns)*sizeof(int). This hints that sizeof(int *)=sizeof(int). Is this true? Then why is it neccesary in the first place to call malloc(nrows * sizeof(int *)) and not simply malloc(nrows * sizeof(int))?

  4. #4
    and the Hat of Guessing tabstop's Avatar
    Join Date
    Nov 2007
    Posts
    14,336
    Often the two are the same (although they don't have to be). However, your analysis of numbers isn't so hot -- the first uses nrows*sizeof(int*) + nrows*ncolumns*sizeof(int), while the second doesn't use the extra indirection level and gets away with just nrows*ncolumns*sizeof(int).

  5. #5
    spurious conceit MK27's Avatar
    Join Date
    Jul 2008
    Location
    segmentation fault
    Posts
    8,300
    Quote Originally Posted by The Physicist View Post
    Then why is it neccesary in the first place to call malloc(nrows * sizeof(int *)) and not simply malloc(nrows * sizeof(int))?
    On a 64-bit system (for example) they are not the same, so you do need to distinguish one from the other.
    C programming resources:
    GNU C Function and Macro Index -- glibc reference manual
    The C Book -- nice online learner guide
    Current ISO draft standard
    CCAN -- new CPAN like open source library repository
    3 (different) GNU debugger tutorials: #1 -- #2 -- #3
    cpwiki -- our wiki on sourceforge

  6. #6
    Novice
    Join Date
    Jul 2009
    Posts
    568
    Quote Originally Posted by MK27 View Post
    On a 64-bit system (for example) they are not the same, so you do need to distinguish one from the other.
    I'm assuming that means an int* on a 64bit system would be 8 bytes instead int's 4 bytes? Are all pointers on 64bit systems 8 bytes? (Just curious. )

  7. #7
    Registered User
    Join Date
    Apr 2010
    Posts
    12
    Quote Originally Posted by tabstop View Post
    Often the two are the same (although they don't have to be). However, your analysis of numbers isn't so hot -- the first uses nrows*sizeof(int*) + nrows*ncolumns*sizeof(int), while the second doesn't use the extra indirection level and gets away with just nrows*ncolumns*sizeof(int).
    How embarassing! You're correct, that was a silly mistake, I guess I was so confused that nothing made sense

    I think I understand it better now, but just as I thought I had it all figured out I ran into a strange problem. I have made a minimal working example below.

    Code:
    #include <stdio.h>
    #include <stdlib.h>
    
    
    #define MAXCOLS 2
    
    int dataini(double** data);
    double** make2Darr(int nrows, int ncols);
    void print2Darr(double** arr, int nrows, int ncols, FILE *fpt);
    
    int main()
    {
        double** data=NULL; //Array to hold data
    	int nrows=dataini(data); //initialises array, would normally also take a filepath as argument
    
        fprintf(stdout,"Printing data array in main:\n");
        print2Darr(data, nrows,MAXCOLS, stdout); //SIGSEGV error
    
        printf("Press any key to end\n");
        getchar();
    
    
    	return 0;
    }
    Functions used:
    Code:
    int dataini(double** data)
    {
    	long maxrows=2; //maximum 2 rows
    
        data=make2Darr(maxrows,MAXCOLS); //Allocates memory
    
        int row, col;
        for(row=0;row<maxrows;row++)
        {
    		for(col = 0; col < MAXCOLS; col++)
    		{
    			data[row][col] = 0; //data is a 2 by 2 0 matrix
    		}
    
    		row++;
    	}
    
        fprintf(stdout,"Printing data array:\n");
        print2Darr(data, maxrows,MAXCOLS, stdout); //Prints just fine in contrast to the call in main
        fprintf(stdout,"Returning to main\n");
        fprintf(stdout,"\n");
    
        return maxrows;
    }
    
    
    
    double** make2Darr(int nrows, int ncols)
    {
        double** arr;
        arr=calloc(nrows,sizeof(double *)); //double**
    	int i=0;
    	if(arr==NULL)
    	{
    		perror("Failed to allocate memory\n");
    		exit(EXIT_FAILURE);
    	}
    	for(i = 0; i < nrows; i++)
    	{
    		arr[i] = calloc(ncols , sizeof(double)); //double*
            if(arr[i]==NULL)
            {
                perror("Failed to allocate memory\n");
                exit(EXIT_FAILURE);
            }
    	}
    	return arr;
    }
    
    
    void print2Darr(double** arr, int nrows, int ncols, FILE *fpt)
    {
     int i,j;
    
     for(i=0;i<nrows;i++)
     {
     for(j=0;j<ncols;j++)
     {
         fprintf(fpt,"%f\t", arr[i][j]);
     }
     fprintf(fpt,"\n");
     }
    }
    The problem is this: I define a double** in main to store my data, and initialise it in the dataini function (of course it's silly to make a specific function to fill it with zeroes, but in my real program it initialises it with data from a filename). Just before the dataini function returns it prints the contents of data just fine. However in main, just after dataini returned, I can no longer print data - here I get a SIGSEGV error meaning that I try to access memory outside the program. It puzzles me that somehow the data array/matrix has changed even though it's still the same piece of memory. I hope I have made myself understandable.

  8. #8
    and the Hat of Guessing tabstop's Avatar
    Join Date
    Nov 2007
    Posts
    14,336
    Things are passed to functions by value, so the value of data cannot be changed by the function call and must still be NULL after it ends. If you want a variable to change, you must pass its address to the function instead.

  9. #9
    Novice
    Join Date
    Jul 2009
    Posts
    568
    Code:
    for(row=0;row<maxrows;row++)
    {
    	for(col = 0; col < MAXCOLS; col++)
    	{
    		data[row][col] = 0; //data is a 2 by 2 0 matrix
    	}
    
    	row++;
    }
    I take that the bolded bit is unintentional?

    calloc() already initializes the allocated memory to 0.

    As to why it segfaults -- I'm drawing a blank. Sorry.

  10. #10
    Registered User
    Join Date
    Apr 2010
    Posts
    12
    Quote Originally Posted by tabstop View Post
    Things are passed to functions by value, so the value of data cannot be changed by the function call and must still be NULL after it ends. If you want a variable to change, you must pass its address to the function instead.
    Ah I see. I guess that makes sense. What happens to the memory that has been allocated then? Is it lost in time and space?


    Quote Originally Posted by msh View Post
    Code:
    for(row=0;row<maxrows;row++)
    {
    	for(col = 0; col < MAXCOLS; col++)
    	{
    		data[row][col] = 0; //data is a 2 by 2 0 matrix
    	}
    
    	row++;
    }
    I take that the bolded bit is unintentional?

    calloc() already initializes the allocated memory to 0.

    As to why it segfaults -- I'm drawing a blank. Sorry.
    You're right, the bolded thing is unintentional.
    The calloc thing is just a remnant from the real code. You're of course right it initializes to 0. I like to make sure that my allocated memory is 0 from the start in the hope that later errors will be easier to catch. After all it is easier to recognize than some other strange, random value I suspect you would get from a simple malloc.
    Last edited by The Physicist; 04-28-2010 at 11:18 AM.

  11. #11
    and the Hat of Guessing tabstop's Avatar
    Join Date
    Nov 2007
    Posts
    14,336
    Quote Originally Posted by The Physicist View Post
    Ah I see. I guess that makes sense. What happens to the memory that has been allocated then? Is it lost in time and space?
    You betcha. Hence the word "leak".

  12. #12
    spurious conceit MK27's Avatar
    Join Date
    Jul 2008
    Location
    segmentation fault
    Posts
    8,300
    Nb, "lost in time and space" does not mean it no longer exists. It will continue to exist for the duration of the program, since you now have no handle to it once the pointer is gone. It's wasted. If you do something like this repeatedly, you can leak/waste a lot of memory.

    Quote Originally Posted by msh View Post
    Are all pointers on 64bit systems 8 bytes? (Just curious. )
    There is nothing in the C standard that says they have to be anything, but yeah. 64-bits = 8 bytes, and the point of a 64-bit system is it can address 2^64 different memory addresses. Pointers contain addresses.
    Last edited by MK27; 04-28-2010 at 01:10 PM.
    C programming resources:
    GNU C Function and Macro Index -- glibc reference manual
    The C Book -- nice online learner guide
    Current ISO draft standard
    CCAN -- new CPAN like open source library repository
    3 (different) GNU debugger tutorials: #1 -- #2 -- #3
    cpwiki -- our wiki on sourceforge

  13. #13
    Novice
    Join Date
    Jul 2009
    Posts
    568
    Quote Originally Posted by MK27 View Post
    There is nothing in the C standard that says they have to be anything, but yeah. 64-bits = 8 bytes, and the point of a 64-bit system is it can address 2^64 different memory addresses. Pointers contain addresses.
    Thanks.

  14. #14
    Registered User
    Join Date
    Apr 2010
    Posts
    12
    Thank you very much for your help. As a service to the community, and should anyone else ever be in the same situation as me, I have posted some working code below. I have also for completeness added a way to destroy the 2D arrays again thus preventing memory leaks. Constructive criticism is welcome


    Code:
    #include <stdio.h>
    #include <stdlib.h>
    
    
    #define MAXCOLS 2 //Defines the maximum number of columns in the 2D array
    
    int dataini(double*** data); //reads data
    double** make2Darr(int nrows, int ncols); //Allocates memory
    void unmake2Darr(double** arr, int nrows); //Frees memory from 2D array
    void print2Darr(double** arr, int nrows, int ncols, FILE *fpt); //Prints 2D array to filestream fpt (for example stdout)
    
    int main()
    {
        double** data=NULL; //"Data Matrix": 2D array that eventually will hold the data
    	int nrows=dataini(&data); //Initialises the data to the data matrix above. Returns the number of rows (needed for printing and deleting)
    
        fprintf(stdout,"Printing data array in main:\n");
        print2Darr(data, nrows,MAXCOLS, stdout); //Prints the data matrix
    
    
        unmake2Darr(data,nrows); //Frees up the memory allocated to the data matrix. Not really needed since main is about to end anyway
        printf("Press any key to end\n");
        getchar();
    
    
    	return 0;
    }
    Functions used:
    Code:
    int dataini(double*** data_arr) //Takes the memory adress to the data array
    {
    	long maxrows=2; //maximum 2 rows. Can be determined during runtime if needed.
    
        double** data;  //This is what will eventually be the data matrix
        data=make2Darr(maxrows,MAXCOLS);
    
        int row, col;
        for(row=0;row<maxrows;row++)
        {
    		for(col = 0; col < MAXCOLS; col++)
    		{
    			data[row][col] = 2*row+col; //Sets the data matrix to some arbitrary values. The values have here been chosen so the data matrix will be [0,1;2,3]
    		}
    	}
    
        fprintf(stdout,"Printing data array:\n");
        print2Darr(data, maxrows,MAXCOLS, stdout); //Prints the data matrix. Compare with the print from main. Should be the same
        fprintf(stdout,"Returning to main\n");
        fprintf(stdout,"\n");
    
        *data_arr=data; //The data matrix is now fully created
        return maxrows;
    }
    
    
    
    double** make2Darr(int nrows, int ncols)
    {
        double** arr;
        arr=calloc(nrows,sizeof(double *)); //Type double**. Notice: Unneccesary to do cast from calloc. Doing so would only supress some warnings if any
    	int i=0;
    	if(arr==NULL)
    	{
    		perror("Failed to allocate memory\n");
    		exit(EXIT_FAILURE);
    	}
    	for(i = 0; i < nrows; i++)
    	{
    		arr[i] = calloc(ncols , sizeof(double)); //Type double*
            if(arr[i]==NULL)
            {
                perror("Failed to allocate memory\n");
                exit(EXIT_FAILURE);
            }
    	}
    	return arr;
    }
    
    
    void print2Darr(double** arr, int nrows, int ncols, FILE *fpt)
    {
     int i,j;
    
     for(i=0;i<nrows;i++)
     {
     for(j=0;j<ncols;j++)
     {
         fprintf(fpt,"%f\t", arr[i][j]);
     }
     fprintf(fpt,"\n");
     }
    }
    
    void unmake2Darr(double** arr, int nrows)
    {
    	int i=0;
    	for(i=0;i<nrows;i++)
    	{
    	free(arr[i]);
        }
        free(arr);
    }

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Memory problem - malloc, pointers and vector
    By DrSnuggles in forum C++ Programming
    Replies: 18
    Last Post: 08-14-2009, 10:28 AM
  2. pointers
    By InvariantLoop in forum C Programming
    Replies: 13
    Last Post: 02-04-2005, 09:32 AM
  3. Locating A Segmentation Fault
    By Stack Overflow in forum C Programming
    Replies: 12
    Last Post: 12-14-2004, 01:33 PM
  4. Crazy memory problem with arrays
    By fusikon in forum C++ Programming
    Replies: 9
    Last Post: 01-15-2003, 09:24 PM
  5. allocating memory screws up data being read in from file
    By Shadow12345 in forum C++ Programming
    Replies: 5
    Last Post: 12-06-2002, 03:23 PM