Your question is unclear because you haven't specified what it is you are attempting to achieve. Later in a comment, you indicate you need a simple 1D array, but that isn't included in your question. Let's cover both case plus the case of using a pointer-to-array and make sure you understand the basics.
To understand dynamic allocation, you must understand there are no arrays involved (the exception being allocating for a pointer-to-array of fixed size).
Otherwise, dynamic allocation encompasses allocation of a block of memory of a given size and assignment of the starting address for that new block of memory to a pointer. In C, there is no need to cast the return of malloc
, it is unnecessary. See: Do I cast the result of malloc? (in C++ it is required)
Allocating Memory For Integers
When you want to allocate a block of memory for the storage of integers, you simply allocate a block big enough to hold the number of integers you want to store, and assign the starting address for the new block of memory to a pointer. Let's take it step-by-step:
int *t;
declares a pointer-to int
to which the beginning address for an allocated block of memory can be assigned.
int n = 100;
t = malloc (n * sizeof *t);
malloc
allocates a new block of memory of n * sizeof *t
bytes which is sufficient in size to hold n
integer values and the beginning address for that block is assigned to the pointer t
. Using the dereferenced sizeof *t
to set the type-size ensures your type-size is correct. (t
being a pointer-to int
, when you dereference t
(e.g. *t
), your type is plain-old int
, so sizeof *t
is equivalent to sizeof (int)
)
It may not seem like it matters now (and for trivial types it doesn't make a lot of difference), but as the type of objects you deal with grow in complexity and levels of pointer indirection, mistakenly setting an incorrect type-size becomes a common problem with dealing with allocated nested structures, etc.. That is why simply using the dereferenced pointer to set type-size is recommended.
Let's take a simple example that users a pointer int *t_1D;
to emphasize it is single linear allocation that can be indexed as a simple 1D array:
#define NUMINT 50 /* number of integers for t_1D */
...
int main (void) {
int *t_1D = NULL; /* pointer to int */
t_1D = malloc (NUMINT * sizeof *t_1D); /* allocate storage for NUMINT int */
if (t_1D == NULL) { /* validate EVERY allocation, handle error */
perror ("malloc-t_1D");
return 1;
}
for (int i = 0; i < NUMINT; i++)
t_1D[i] = i + 1; /* assign a value to each integer */
puts ("\nvalues in t_1D (with \\n every 5th integer)\n");
for (int i = 0; i < NUMINT; i++) { /* loop over each value */
if (i && i % ARRSZ == 0) /* simple mod test to output newline */
putchar ('\n');
printf (" %3d", t_1D[i]); /* output value at address */
}
puts ("\n");
free (t_1D); /* don't forget to free what you allocate */
Above when you are done with that block of memory, you must free()
the block to prevent a memory leak. Understand for trivial examples where you allocate in main()
the memory is freed on program exit. However, it is important to develop good habits early as you will commonly be allocating in a function all and if the memory is not freed before your function returns (or later freed in the caller before it returns) you lose the pointer to the block and it cannot be free -- resulting in a memory leak.
Simulating a 2D Array with a Pointer-To-Pointer
If you use a pointer-to-pointer (e.g. int **t;
) as your pointer, your allocation is a two-step process. First you allocate a block of memory to hold some number of pointers (which you generally think of as the number of rows
for the object you are allocating).
Then in a second set of allocations, you will allocate a block of memory to hold the number of integers-per-row you wish to store per-row (generally thought of as the col
(column) number of integers) and assign the starting address for each allocated block to one of your allocated pointers -- in sequence.
The result is a data structure where you allocate 1-block of memory to hold row
number pointers and and row
blocks of memory to hold col
number of integers. Then you can access each of the memory locations just as you would access values in a 2D array.
However, note since there are multiple allocations required to create an object from a pointer-to-pointer, there will be multiple calls to free
required to free the memory allocated to the object.
Now let's look at how that can be used. Below, we will use int **t_2D;
as the variable name to indicate we are storing an object that we can index as a 2D array:
#define ARRSZ 5 /* number of integers per-row for t_2D and t_PTA */
...
int **t_2D = NULL; /* pointer to pointer to int */
...
/* a pointer to pointer (allocate pointers, then ints for each pointer) */
t_2D = malloc (NUMINT/ARRSZ * sizeof *t_2D); /* allocate 10 pointers */
if (!t_2D) { /* validate EVERY allocation */
perror ("malloc-t_2D");
return 1;
}
for (int i = 0; i < NUMINT/ARRSZ; i++)
/* allocate/validate storage for 5 int assign start address to each ptr */
if (!(t_2D[i] = malloc (ARRSZ * sizeof *t_2D[i]))) { /* on failure */
while (i--) /* free previously allocated block for int */
free (t_2D[i]);
free (t_2D); /* free pointers */
return 1;
}
for (int i = 0; i < NUMINT/ARRSZ; i++) /* loop over pointers */
for (int j = 0; j < ARRSZ; j++) /* loop over integer storage */
t_2D[i][j] = (i * ARRSZ) + j + 1; /* assign value for each int */
puts ("values in t_2D output by row x col:\n");
for (int i = 0; i < NUMINT/ARRSZ; i++) {
for (int j = 0; j < ARRSZ; j++)
printf (" %3d", t_2D[i][j]); /* output each integer */
putchar ('\n');
}
putchar ('\n');
for (int i = 0; i < NUMINT/ARRSZ; i++) /* loop over pointers */
free (t_2D[i]); /* free integers */
free (t_2D); /* free pointers */
Note the loop at the end that loops over each pointer freeing the storage for integers before calling free
to free the block of memory holding the pointers themselves.
A Pointer-To-Array -- Single Allocation, Single-Free, 2D Indexing
There is one other common allocation that you will run into that warrants explanation. You can use a pointer-to-array of fixed length (e.g. int (*t_PTA)[CONST];
) and then allocate storage for some number of the fixed arrays in a single call and be able to address the object as a 2D array just as was done above with t_2D
. Since there is only a single-allocation required, there is only a single-free needed to free the memory associated with the object.
(note: do not confuse a poitner-to-array (e.g. int (*p)[CONST]
) with an array-of-pointers (e.g. int *p[CONST]
), they are two distinct types)
To allocate, use and free an object create from a pointer-to-array, you can do the following:
/* a pointer to array -- single allocation, single-free, 2D indexing */
int (*t_PTA)[ARRSZ] = NULL; /* pointer to array of int[ARRSZ] */
t_PTA = malloc (NUMINT/ARRSZ * sizeof *t_PTA); /* storage for 50 integers */
if (!t_PTA) { /* validate EVERY allocation */
perror ("malloc-t_2D");
return 1;
}
for (int i = 0; i < NUMINT/ARRSZ; i++) /* loop over pointers */
for (int j = 0; j < ARRSZ; j++) /* loop over integer storage */
t_PTA[i][j] = (i * ARRSZ) + j + 1; /* assign value for each int */
puts ("values in t_PTA output by row x col:\n");
for (int i = 0; i < NUMINT/ARRSZ; i++) {
for (int j = 0; j < ARRSZ; j++)
printf (" %3d", t_PTA[i][j]); /* output each integer */
putchar ('\n');
}
putchar ('\n');
free (t_PTA);
(note: the convenience of the single free (t_PTA);
used to free all memory associated with the object.)
Now let's put it altogether in a workable example:
#include <stdio.h>
#include <stdlib.h>
#define ARRSZ 5 /* number of integers per-row for t_2D and t_PTA */
#define NUMINT 50 /* number of integers for t_1D */
int main (void) {
int *t_1D = NULL, /* pointer to int */
**t_2D = NULL, /* pointer to pointer to int */
(*t_PTA)[ARRSZ] = NULL; /* pointer to array of int[ARRSZ] */
/* handling simple storage for integers */
t_1D = malloc (NUMINT * sizeof *t_1D); /* allocate storage for NUMINT int */
if (t_1D == NULL) { /* validate EVERY allocation, handle error */
perror ("malloc-t_1D");
return 1;
}
for (int i = 0; i < NUMINT; i++)
t_1D[i] = i + 1; /* assign a value to each integer */
puts ("\nvalues in t_1D (with \\n every 5th integer)\n");
for (int i = 0; i < NUMINT; i++) { /* loop over each value */
if (i && i % ARRSZ == 0) /* simple mod test to output newline */
putchar ('\n');
printf (" %3d", t_1D[i]); /* output value at address */
}
puts ("\n");
free (t_1D); /* don't forget to free what you allocate */
/* a pointer to pointer (allocate pointers, then ints for each pointer) */
t_2D = malloc (NUMINT/ARRSZ * sizeof *t_2D); /* allocate 10 pointers */
if (!t_2D) { /* validate EVERY allocation */
perror ("malloc-t_2D");
return 1;
}
for (int i = 0; i < NUMINT/ARRSZ; i++)
/* allocate/validate storage for 5 int assign start address to each ptr */
if (!(t_2D[i] = malloc (ARRSZ * sizeof *t_2D[i]))) { /* on failure */
while (i--) /* free previously allocated block for int */
free (t_2D[i]);
free (t_2D); /* free pointers */
return 1;
}
for (int i = 0; i < NUMINT/ARRSZ; i++) /* loop over pointers */
for (int j = 0; j < ARRSZ; j++) /* loop over integer storage */
t_2D[i][j] = (i * ARRSZ) + j + 1; /* assign value for each int */
puts ("values in t_2D output by row x col:\n");
for (int i = 0; i < NUMINT/ARRSZ; i++) {
for (int j = 0; j < ARRSZ; j++)
printf (" %3d", t_2D[i][j]); /* output each integer */
putchar ('\n');
}
putchar ('\n');
for (int i = 0; i < NUMINT/ARRSZ; i++) /* loop over pointers */
free (t_2D[i]); /* free integers */
free (t_2D); /* free pointers */
/* a pointer to array -- single allocation, single-free, 2D indexing */
t_PTA = malloc (NUMINT/ARRSZ * sizeof *t_PTA); /* storage for 50 integers */
if (!t_PTA) { /* validate EVERY allocation */
perror ("malloc-t_2D");
return 1;
}
for (int i = 0; i < NUMINT/ARRSZ; i++) /* loop over pointers */
for (int j = 0; j < ARRSZ; j++) /* loop over integer storage */
t_PTA[i][j] = (i * ARRSZ) + j + 1; /* assign value for each int */
puts ("values in t_PTA output by row x col:\n");
for (int i = 0; i < NUMINT/ARRSZ; i++) {
for (int j = 0; j < ARRSZ; j++)
printf (" %3d", t_PTA[i][j]); /* output each integer */
putchar ('\n');
}
putchar ('\n');
free (t_PTA);
}
Where you compile and run to receive the following:
Example Use/Output
$ ./bin/dynalloc_1_2_pta
values in t_1D (with \n every 5th integer)
1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
16 17 18 19 20
21 22 23 24 25
26 27 28 29 30
31 32 33 34 35
36 37 38 39 40
41 42 43 44 45
46 47 48 49 50
values in t_2D output by row x col:
1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
16 17 18 19 20
21 22 23 24 25
26 27 28 29 30
31 32 33 34 35
36 37 38 39 40
41 42 43 44 45
46 47 48 49 50
values in t_PTA output by row x col:
1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
16 17 18 19 20
21 22 23 24 25
26 27 28 29 30
31 32 33 34 35
36 37 38 39 40
41 42 43 44 45
46 47 48 49 50
Memory Use/Error Check
It is imperative that you use a memory error checking program to ensure you do not attempt to access memory or write beyond/outside the bounds of your allocated block, attempt to read or base a conditional jump on an uninitialized value, and finally, to confirm that you free all the memory you have allocated.
For Linux valgrind
is the normal choice. There are similar memory checkers for every platform. They are all simple to use, just run your program through it.
$ valgrind ./bin/dynalloc_1_2_pta
==10642== Memcheck, a memory error detector
==10642== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==10642== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==10642== Command: ./bin/dynalloc_1_2_pta
==10642==
values in t_1D (with \n every 5th integer)
1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
16 17 18 19 20
21 22 23 24 25
26 27 28 29 30
31 32 33 34 35
36 37 38 39 40
41 42 43 44 45
46 47 48 49 50
values in t_2D output by row x col:
1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
16 17 18 19 20
21 22 23 24 25
26 27 28 29 30
31 32 33 34 35
36 37 38 39 40
41 42 43 44 45
46 47 48 49 50
values in t_PTA output by row x col:
1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
16 17 18 19 20
21 22 23 24 25
26 27 28 29 30
31 32 33 34 35
36 37 38 39 40
41 42 43 44 45
46 47 48 49 50
==10642==
==10642== HEAP SUMMARY:
==10642== in use at exit: 0 bytes in 0 blocks
==10642== total heap usage: 14 allocs, 14 frees, 1,704 bytes allocated
==10642==
==10642== All heap blocks were freed -- no leaks are possible
==10642==
==10642== For counts of detected and suppressed errors, rerun with: -v
==10642== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Always confirm that you have freed all memory you have allocated and that there are no memory errors.
It is a lot to digest at one sitting, but take it piece-by-piece and dynamic allocation will start to make sense. Let me know if you have furher questions.