Download as pdf or txt
Download as pdf or txt
You are on page 1of 71

Pointers to pointers & multi-

dimensional arrays
Dynamic Allocation - Recap
• malloc( ),calloc( ) and free( ) library
functions

2
Library Functions for Dynamic Memory Allocation

void *malloc(size_t size);

malloc – Name of the function.


size_t – a new name definition (typedef – will be explained next chapter) for
unsigned integer.
void * – malloc() allocates size consecutive bytes in memory and returns the
address of the first byte, a pointer to it. malloc() does not know how we
are going to use this piece of memory (array of chars? array of doubles? etc.),
so it returns the memory as a general pointer (void *).
The allocated memory is placed on the heap.
The allocated memory contains ‘garbage’.
Library Functions for Dynamic Memory Allocation –
cont’d

void *calloc(size_t nitems, size_t size);

calloc does the same thing as malloc, with two differences :

— It gets two parameters instead of one.


— It allocates consecutive nitems*size bytes of memory.
— The memory is ‘cleaned’, all the bytes are 0.

Both malloc and calloc return NULL if there is insufficient memory on the heap.
Library Function free()
void free(void *ptr);

block – A pointer to the beginning of the allocated memory.

free() frees the block of memory that was allocated with malloc( ) or
calloc( ).

Why should we use free ?…


— The memory allocated on the heap is not automatically released, and some
implementations don’t perform ‘garbage collection’.
A dynamic memory allocation that is not freed is called a
‘memory leak’.
— The heap is not endless. If, for example, a function performs many dynamic
allocations, why not release memory when it is no longer needed to make
room for new allocations?
free() – cont’d
Question :
— How does the computer know how many bytes to free ?
Answer :
— The operating system manages a table of addresses.
— When we allocate memory, the address of the first byte is written in that
table, as well as the number of bytes that were allocated.
— When we call the function free(), we send a pointer to the first byte of the
allocated memory. The operating system finds that address in the table and
sees how many bytes to free.

Question :
— The following code is legal. Is it a smart code ?…
int *ptr = (int*)malloc(8 * sizeof(int));
ptr++;
Pointers to pointers
int i=3;
int j=4;
i: 3 j: 4 k: 5
int k=5;

int *ip1 = &i; ip1: ip2:

int *ip2 = &j;


int **ipp = &ip1;
ipp:

7
Pointers to pointers
int i=3;
int j=4;
i: 3 j: 4 k: 5
int k=5;

int *ip1 = &i; ip1: ip2:

int *ip2 = &j;


int **ipp = &ip1;
ipp:
ipp = &ip2;

8
Pointers to pointers
int i=3;
int j=4;
i: 3 j: 4 k: 5
int k=5;

int *ip1 = &i; ip1: ip2:

int *ip2 = &j;


int **ipp = &ip1;
ipp:
ipp = &ip2;
*ipp = &k;
9
Reminder – the swap function
Does nothing Works
void swap(int a, int b) void swap(int *pa, int *pb)
{ {
int temp = a; int temp = *pa;
a = b; *pa = *pb;
b = temp; *pb = temp;
} }
int main() int main()
{ {
int x, y; int x, y;
x = 3; y = 7; x = 3; y = 7;
swap(x, y); swap(&x, &y);
// now x==3, y==7 // x == 7, y == 3
} }
Pointers to pointers: example
//put pointer to an allocated string in pp
int allocString( size_t len, char ** pp)
{
char *str = (char*)malloc(len + 1);
if (str==NULL)
{
return 1;
}
*pp = str;
return 0;
}

11
Pointers to pointers: example
// copy a string using "allocString"
void main()
{
char *s = "example";
char *copy;
allocString( strlen(s), &copy );
strcpy( copy, s);
free (copy);
}
12
Array of pointers

13
Example: grades.c
1 /* grades.c
2 This program calculates the average grade of a class. The
3 programmer doesn’t know the number of students in the class, so
4 he can’t declare an array.
5 Dynamic allocation is used instead */
6
7 #include <stdio.h>
8 #include <stdlib.h> /* for the prototypes of malloc, free and
9 exit */
10 #define EXIT_FAIL 42
11
12 void main(void)
13 {
14 float *grades = NULL; /* the grades are of type float */
15 int num_students; /* will be entered by the user */
16 float sum = 0; /* will help calculating the average. */
17 int ctr;
Example: grades.c – cont’d
18 do
19 { /* loop until a legal number of students is entered */
20 printf("How many students are there in class?=> ");
21 scanf("%d", &num_students);
22 } while(num_students <= 0);
23 /* now we know the number of students, so we can dynamically
24 allocate an array for holding their grades (floats) */
25 grades = (float*)malloc(num_students * sizeof(float));
26 /* if there isn’t enough consecutive memory,malloc() returns
27 NULL */
28 if(! grades)
29 {
30 puts("There is not enough memory. Sorry.\n");
31 exit (EXIT_FAIL); /* exit the program */
32 }
Example: grades.c – cont’d
34 for (ctr = 0; ctr < num_students ; ++ctr) /* get the
35 grades */
36 {
37 printf("Enter grade #%d: ", ctr + 1);
38 scanf("%f", &grades[ctr]);
39 sum += grades[ctr]; /* for calculating the average
40 later */
41 }
42
43 printf("The average of the grades is : %f\n",
44 sum/num_students);
45 /* memory that was allocated by malloc/calloc must be freed!*/
46 free(grades); /* free the allocated memory */
47 }
Array of Pointers
The problem:
We have to read, for example, a list of 10 names.
The names differ in length.
The maximum length is, in our case, 23.
First Solution:
char arr[10][23];
A list of names is an array of strings. since a string is an
array of char, we have an array of arrays of char - a two-
dimensional array of char. Look at the waste of memory !

S n o w w h i t e \0
L i t t l e r e d r i d i n g h o o d \0
C i n d e r e l l a \0
Array of Pointers
— Second Solution:

char *arr[10];

arr is array of 10 elements, each one of them is a


pointer to char.
Each element will point to a different array of a different
size.

0 . S n o w w h i t e \0
1 L i t t l e r e d r i d i n g h o o d \0
.
2 C i n d e r e l l a \0
.
Example: names1.c
1 /*names1.c Creates an array of strings, each of an unknown length*/
2 #include <stdio.h>
3 #include <stdlib.h> /* prototypes of malloc(), free(), exit() */
4 #include <string.h>
5 #define EXIT_FAIL 42
6 void free_names(char * names[], int num); /* prototype */
7
8 int get_names(char *names[], int num)
9 {
10 int i = 0;
11 char str[128];
12 do {
13 puts("Enter a name (press CTRL/Z ENTER to stop) "
14 " => ");
15 if (gets(str) == NULL)
16 break;
17
Example: names1.c – cont’d
18 if( (names[i] = (char*) malloc(strlen(str)+1))
19 == NULL)
20 {
21 puts ("Not enough memory. Sorry.\n");
22 free_names(names, i); /* some memory has
23 been allocated ! */
24 exit(EXIT_FAIL);
25 }
26 strcpy(names[i], str); /* copy the name into the
27 memory */
28 }while (++i < num);
29 return i;/* return the actual number of names in the array */
30 }
31
32 void print_names(const char *names[], int num)
33 {
34 int i;
35 for (i = 0 ; i < num ; i++)
36 printf("%2d) %s\n", i + 1, names[i]);
37 }
Example: names1.c – cont’d
37 void free_names(char * names[], int num)
38 {
39 int i;
40 for( i = 0 ; i < num ; ++i )
41 free(names[i]);
42 }
43
44 void main(void)
45 {
46 char *names[10]; /* an array of 10 pointers to char */
47 int final_num;/*in case the user enters less than 10 names*/
48 final_num = get_names(names, 10);/* get names into the
49 array */
50 print_names(names, final_num);/*print the array of names*/
51 free_names(names, final_num);/* free all the allocated
52 memory */
53 }
Dynamic Allocation
The previous program allocated memory for the names according to their lengths.
No memory was wasted in the case of short names, and no memory was missing
in the case of long names.

BUT it is good only as long as the user doesn’t have more than 10 friends’ names
to keep. If the user has 11 friends, he must give up one name. How can he choose
who to insult ?…

The next program will not only allocate memory for the names according to their
lengths, but it will also allocate the array itself according to the number of names
that the user wants to keep !

Turn to the next page for SUPER DYNAMIC ALLOCATION !


Example: names2.c
1 /* names2.c
2 Creates an array for unknown number of strings. The length of each
3 string is also unknown. Dynamic memory allocation is used. */
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <string.h>
7 #define EXIT_FAIL 42
8 void free_names(char **names, int num)
9 {
10 while(num > 0)
11 free(names[--num]);
12 }
13
14 void print_names(const char **names, int num)
15 {
16 int ctr;
17 printf("The following %d names were entered :\n",
18 num);
19 for(ctr = 0 ; ctr < num ; ++ctr)
20 printf("%2d) %s\n", ctr + 1, names[ctr]);
21 }
Example: names2.c – cont’d
22 void get_names(char **names, int *num)
23 {
24 int i = 0;
25 char str[128]; /* for getting names from the user */
26 /* loop until num names have been entered or until the user
27 chooses to stop entering names. */
28 do
29 {
30 printf("Enter the #%d name (press CTRL/Z ENTER"
31 "to stop)=> ",i+1);
32 if(gets(str) == NULL) /* the user wants to stop
33 entering names */
34 break;
35 if((names[i] = (char*)malloc(strlen(str)+1))==NULL)
36 {
37 puts ("Not enough memory. Sorry.\n");
38 free_names(names, i); /* some memory was
39 allocated! Free it ! */
40 free(names);
41 exit(EXIT_FAIL);
42 }
Example: names2.c – cont’d
43 strcpy(names[i], str); /* copy the name into the
44 memory */
45 }while (++i < *num);
46
47 *num = i; /* update the number of names */
48 }
49
50 int get_number_of_names()
51 {
52 int num = 0;
53
54 while(num <= 0) /* loop until a legal number has been
55 entered */
56 {
57 printf("How many names do you wish to enter ? "
58 "=> ");
59 scanf("%d", &num);
60 getchar();
61 }
62 return num;
63 }
Example: names2.c – cont’d
64 void main(void)
65 {
66 char **names = NULL; /* will be an array of string */
67 int num;/* the number of names that the user wants to keep */
68
69 num = get_number_of_names();
70
71 /* allocate memory for num elements, each is a pointer to char*/
72 names = (char**)malloc(num * sizeof(char*));
73 if(! names) /* there is not enough available consecutive
74 memory */
75 {
76 puts("There is not enough memory for the"
77 "program. Sorry.\n");
78 exit(EXIT_FAIL);
79 }
80 /* call a function that will get the names. The variable num
81 is sent by address so that if the user chooses to enter fewer
82 names that he originally intended to enter, the function will
83 change num to the correct number of names that were
84 entered.*/
Example: names2.c – cont’d
85 get_names(names, &num);
86
87 /* print the array of names */
88 print_names(names, num);
89
90 /* free the memory that was dynamically allocated for the
91 names */
92 free_names(names, num);
93
94 /* the array of pointers was also allocated dynamically.
95 Free it */
96 free(names);
97 }
Illustration

names
char** char* char char char char
char* char char
char*
char char char char char char
Example: bad-alloc.c
1 /* bad_alloc.c
2 This program does not work well and is dangerous. Why ? */
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <string.h>
6 void get_memory(char *arr, int length)
7 {
8 if((arr=(char*)malloc(length * sizeof(char))) == NULL)
9 {
10 puts("Not enough memory. Sorry.\n");
11 exit(-1);
12 }
13 }
14 void main(void)
15 {
16 char *str = NULL;
17 get_memory(str, 128);
18 strcpy(str,"Program illegally runs over memory !\n");
19 puts(str);
20 free(str);
21 }
Example: good-alloc.c
1 /* good_alloc.c This is the fixed version of bad_alloc.c */
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <string.h>
5 void get_memory(char **arr, int length)
6 {
7 if( (*arr = (char*)malloc(length * sizeof(char))) ==
8 NULL)
9 {
10 puts("Not enough memory. Sorry.\n");
11 exit(-1);
12 }
13 }
14 void main(void)
15 {
16 char *str = NULL;
17 get_memory(&str, 128);
18 strcpy(str, "This program works fine !\n");
19 puts(str);
20 free(str);
21 }
Multi-dimensional arrays

31
Multi-dimensional arrays
Can be created in few ways:
1. Automatically:
int m[2][3]; // 2 rows, 3 columns
— continuous memory (divided to 2 blocks)
— access: m[row][col] = 0;

32
Automatic multi-dimensional arrays
#define R 2
#define C 3
int m[R][C];

int* m_ptr= &(m[0][0]);


size_t i;
for (i=0; i<R*C; ++i) {
printf(“%d\n”, *(m_ptr++) );
}
33
Automatic multi-dimensional arrays
#define R 2
#define C 3
int m[R][C];

int* m_ptr;
for (m_ptr= &(m[0][0]);
m_ptr<=&(m[R-1][C-1]);
++m_ptr) {
printf(“%d\n”, *m_ptr );
34 }
Automatic multi-dimensional arrays
int A[R][C];
A[0][0] A[0][1] … A[0][c-1]
… … … …
Like a matrix à A[i][0] A[i][1] … A[i][c-1]
… … … …
A[R-1][0] A[R-1][1] … A[R-1][C-1]

Row-major ordering à
A[0] A[i] A[R-1]

A A A A A A
[0] • • • [0] • • • [i] • • • [i] • • •[R-1] • • • [R-1]
[0] [C-1] [0] [C-1] [0] [C-1]

A A+i*C*4 A+(R-1)*C*4

Starting address of A[i]


35
Semi-dynamic multi-dimensional arrays
2. Semi-dynamic:
Define an array of pointers:

int* m[5]; // allocates memory for 5 pointers


for (i=0; i<5; ++i)
{
m[i] = (int*) malloc( 7*sizeof(int) );
// m[i] now points to a memory for 7 ints
}

36
Dynamic multi-dimensional arrays
3. Dynamically:
int ** m;
m = (int**) malloc( 5*sizeof(int*) );
for (i=0; i<5; ++i)
{
m[i] = (int*)malloc( 7*sizeof(int) );
}
m

37
Semi/Dynamic multi-dimensional arrays

Memory not continuous


• Each pointer can be with different size
• Access: m[ i ][ j ]
• Don’t forget to free all the memory:
for (i=0; i<nrows; i++ )
{
free( m[i] );
}
free( m ); // only for dynamic

38
Dynamic arrays – more efficient way
int* A= (int*) malloc(R*C*sizeof(int));
1-D array R=3 C=2 à
0,0 0,1 0,2 1,0 1,1 1,2

• Access: A[ i*C + j ] // A [i][j]


• One access to memory vs. two in previous
semi/dynamic representations.
• Easier (& more efficient) implementation of
iterators
• But:
• Less readable code (can hide with macro or

39
much better, inline functions)
Pointers to pointers to …
We also have pointers to pointers to pointers,
etc.:

double ** mat1 = getMatrix();


double ** mat2 = getMatrix();
//allocate an array of matrices
double *** matrices =
(double***) malloc(n*sizeof(double**));
matrices[0] = mat1;
matrices[1] = mat2;

40
Automatic multi-dimensional arrays as
arguments to functions
int x[5][7]; // 5 rows, 7 columns

When sending ‘x’ as an argument to a function, only the


1st index can be omitted:
• void func( int x[5][7] ) //ok
• void func( int x[][7] ) //ok
• void func( int x[][] ) //does not compile
• void func( int * x[] ) //something else
• void func( int ** x ) //something else

41
Why ?

42
Pointers to arrays (of size X):
int foo (char m[][20]);

m is a pointer to an array of 20 chars.


Therefore:
sizeof (m) = sizeof (void*);
sizeof (*m) = 20*sizeof (char);

43
Pointers to arrays (of size X):
Explicit declaration:

char (*arr_2d)[20];
// arr_2d is a pointer
// not initialized
Using typedef:
typdef char arr_2d_20[20];
arr_2d_20 *p_arr_2d;
But:
typdef char arr_2d[][]; // comp. error
44
Pointers to arrays (of size X):

char (*m)[5]; char *m[5];

45
Pointers to arrays (of size X):

char (*m)[5]; char *m[5];

m After initialization:
char arr[5];
m= &arr;

arr

46
Pointers to arrays (of size X):

char (*m)[5]; char *m[5];

sizeof (m) = sizeof (m) =


sizeof(void*) 5*sizeof (char*) =
5*sizeof (void*)
sizeof (*m) =
5*sizeof(char) sizeof (*m) =
sizeof (char*)=
sizeof (void*)

47
Multi-dimensional arrays as arguments
to functions

48
Multi-dimensional arrays as arguments
to functions
void foo( int matrix[ROWS_NUM][COLS_NUM] ) {

49
Multi-dimensional arrays as arguments
to functions
void foo( int matrix[ROWS_NUM][COLS_NUM] ) {

What is really sent?

50
Multi-dimensional arrays as arguments
to functions
void foo( int (*matrix)[COLS_NUM] ) {

pointer to an array of
COLS_NUM ints!

51
Multi-dimensional arrays as arguments
to functions
void foo( int (*matrix)[COLS_NUM] ) {

...
... matrix[r][c]

52
Multi-dimensional arrays as arguments
to functions
void foo( int (*matrix)[COLS_NUM] ) {

...
... matrix[r][c]
... (*(matrix+r))[c]

53
Multi-dimensional arrays as arguments
to functions
void foo( int (*matrix)[COLS_NUM] ) {

...
... matrix[r][c]
... (*(matrix+r))[c]

matrix is a pointer to int[COLS_NUM].


addition is done in this units.
This is why COLS_NUM is needed!
54
Multi-dimensional arrays as arguments
to functions
void foo( int (*matrix)[COLS_NUM] ) {

...
... matrix[r][c]
... (*(matrix+r))[c]
... *((*(matrix+r)) + c)

55
Multi-dimensional arrays as arguments
to functions
void foo( int (*matrix)[COLS_NUM] ) {

...
... matrix[r][c]
... (*(matrix+r))[c]
... *((*(matrix+r)) + c) == *(matrix+r*COLS_NUM+c)

The compiler is probably optimizing the


internal computation to this.

56
Example of dynamic multidimensional
array: argv, argc

We want to pass parameters to the program running


via the shell

57
argv, argc
! To pass command line arguments to our program we
should use the following main declaration:
main(int argc, char* argv[]) { ...

ü char** argv
r char argv[][]

! Compare to main(String[] args)in Java

!Unlike Java the first argument is the


name of the program itself.
argv & argc: example

! $ prog1 –u danny –p 1234

argc == 5
argv[0] == “prog1”
argv[1] == “-u”
...
argv[4] == “1234”

Always: argv[argc] == NULL


(redundant since we are also given argc)
Array reallocation – we need to
resize an array

60
Array reallocation – wrong solution

int *arr = (int*)malloc(sizeof(int)*arraySize);


//put some values in arr
...
int* nArr= (int*)malloc(sizeof(int)*(arraySize+1));
//copy values from arr to newArr
...
arr = nArr;
// BAD: lost address of first allocation!

61
Array reallocation
Allocate array on the heap and assign a pointer to it

arr
1 2 3

62
Array reallocation
Use temporary pointer to point to the old array.

arr temp

1 2 3

63
Array reallocation
Use temporary pointer to point to the old array.
Reallocate new array.

arr temp

1 2 3

? ? ? ? ?

64
Array reallocation
Use temporary pointer to point to the old array.
Reallocate new array.
Copy values from the old array to the new one.

arr temp

1 2 3

1 2 3 ? ?

65
Array reallocation
Use temporary pointer to point to the old array.
Reallocate new array.
Copy values from the old array to the new one.
Free the old array.

arr temp
1 2 3

1 2 3 ? ?

66
Array reallocation – version 1
void reallocateMemory(int **parr,
size_t oldSize, size_t newSize) {
assert(newSize>oldSize);

int *old = *parr;


//partial example–do not forget checking malloc!
*parr = (int*)malloc(sizeof(int)*newSize);
size_t i = 0;
while(i < oldSize) {
(*arr)[i] = old[i];
++i;
}
free(old);

}
67
Array reallocation – version 1
int main() {

int *arr = (int*)malloc(sizeof(int)*arrSize);


//do some stuff
...
reallocateMemory(&arr,arrSize,newSize);

free(arr)

68
Array reallocation – version 2
int* reallocateMemory(int *arr,
size_t oldSize, size_t newSize) {
assert(newSize>oldSize);

int *old = arr;


//partial example–do not forget checking malloc!
arr = (int*)malloc(sizeof(int)*newSize);
size_t i = 0;
while(i < oldSize) {
arr[i] = old[i];
++i;
}
free(old);
return arr;
}
69
Array reallocation – version 2
int main() {

int *arr = (int*)malloc(sizeof(int)*arrSize);


//do some stuff

...
arr = reallocateMemory(arr,arrSize,newSize);
free(arr);

70
Array reallocation – using realloc
int * arr = (int*)malloc(sizeof(int)*oldSize);

arr = (int*)realloc(arr,sizeof(int)*newSize);

realloc tries to reallocate the new memory in place,


if fails, tries elsewhere
The old data is preserved
The new cells contents is undefined
If arr=NULL, behaves like alloc

71

You might also like