0% found this document useful (0 votes)
5 views68 pages

C Lecture 5

This lecture covers pointers, pointer arithmetic, arrays, dynamic memory allocation, and the stack and heap in C programming. Key concepts include how pointers hold memory addresses, how to perform arithmetic on pointers, and the use of malloc and calloc for dynamic memory management. Additionally, it discusses the importance of freeing allocated memory to avoid memory leaks.

Uploaded by

Abad Ur Rehman
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
5 views68 pages

C Lecture 5

This lecture covers pointers, pointer arithmetic, arrays, dynamic memory allocation, and the stack and heap in C programming. Key concepts include how pointers hold memory addresses, how to perform arithmetic on pointers, and the use of malloc and calloc for dynamic memory management. Additionally, it discusses the importance of freeing allocated memory to avoid memory leaks.

Uploaded by

Abad Ur Rehman
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 68

CS 11 C track: lecture 5

n Last week: pointers


n This week:
n Pointer arithmetic
n Arrays and pointers
n Dynamic memory allocation
n The stack and the heap
Pointers (from last week)
n Address: location where data stored
n Pointer: variable that holds an address
int i = 10;
int *j = &i;
int k = 2 * (*j); /* dereference j */
Pointer arithmetic (1)
n Can add/subtract integers to/from pointers
int arr[] = { 1, 2, 3, 4, 5 };
int *p = arr; /* (*p) == ? */
p++; /* (*p) == ? */
p += 2; /* (*p) == ? */
p -= 3; /* (*p) == ? */
Pointer arithmetic (2)
int arr[] = { 1, 2, 3, 4, 5 };
int *p = arr; /* (*p) == ? */

1 2 3 4 5

arr
p
Pointer arithmetic (3)

p++; /* (*p) == ? */

1 2 3 4 5

arr
p
Pointer arithmetic (4)

p += 2; /* (*p) == ? */

1 2 3 4 5

arr
p
Pointer arithmetic (5)

p -= 3; /* (*p) == ? */

1 2 3 4 5

arr
p
Pointer arithmetic (6)

Let's try that using


addresses only...
Pointer arithmetic (7)
int arr[] = { 1, 2, 3, 4, 5 };
int *p = arr; /* (*p) == ? */

0x1234

1 2 3 4 5

arr 0x1234
p 0x1234
Pointer arithmetic (8)
p++; /* (*p) == ? */

0x1234

1 2 3 4 5

arr 0x1234
p 0x1238 (assume 4 byte integers)
Pointer arithmetic (9)
p += 2; /* (*p) == ? */

0x1234

1 2 3 4 5

arr 0x1234
(0x1240 = 0x1234 + 0x0c;
p 0x1240
0x0c == 12 decimal or 3x4)
Pointer arithmetic (10)
p-= 3; /* (*p) == ? */

0x1234

1 2 3 4 5

arr 0x1234
p 0x1234
Pointer arithmetic (11)
n Get size of a type using the sizeof
operator:
printf("size of integer: %d\n",
sizeof(int));
printf("size of (int *): %d\n",
sizeof(int *));

n N.B. sizeof is not a function


n takes a type name as an argument!
Pointer arithmetic (12)
n N.B. pointer arithmetic doesn't add/
subtract address directly but in multiples
of the size of the type in bytes

int arr[] = { 1, 2, 3, 4, 5 };
int *p = arr;
p++; /* means: p = p + sizeof(int);*/
Pointer arithmetic (13)
p++; /* (*p) == ? */

0x1234

1 2 3 4 5

arr 0x1234
(j = 0x1234 + sizeof(int) = 0x1238,
p 0x1238
not 0x1235)
Arrays and pointers (1)
n Arrays are pointers in disguise!
n Arrays: "syntactic sugar" for pointers

int arr[] = {1, 2, 3, 4, 5};


printf("arr[3] = %d\n", arr[3]);
printf("arr[3] = %d\n", *(arr + 3));

n arr[3] and *(arr + 3) are identical!


n arr is identical to &arr[0]
Arrays and pointers (2)
n Can use pointer arithmetic wherever we
use array operations; consider this:

int i;
double array[1000];
for (i = 1; i < 999; i++) {
array[i] = (array[i-1] +
array[i] + array[i+1]) / 3.0;
}
Arrays and pointers (3)
n Exactly the same as:

int i;
double array[1000];
for (i = 1; i < 999; i++) {
*(array+i) = (*(array+i-1) +
*(array+i) + *(array+i+1)) / 3.0;
}
Arrays and pointers (4)
n When you say *(array + i), you have
to add i to array and dereference
n For large values of i, this is relatively slow
n Incrementing pointers by 1 is faster than
adding a large number to a pointer
n Can use this fact to optimize the preceding
code in an interesting way
Arrays and pointers (5)

double array[1000];
double *p1, *p2, *p3;
p1=array; p2=array+1; p3=array+2;
for (i = 1; i < 999; i++) {
*p2 = (*p1 + *p2 + *p3) / 3.0;
p1++; p2++; p3++;
}
Arrays and pointers (6)
array
...

Add *p1, *p2, *p3


p1 together, divide by 3,
p2
put result into *p2

p3
Arrays and pointers (7)
array
...

Increment *p1, *p2, *p3


p1 by 1 each, continue
p2

p3
Arrays and pointers (8)
n We replaced 3 pointer additions with
three pointer increments, which are
usually faster

n Even more significant for 2-d arrays


Dynamic memory allocation (1)
Recall that we can't do this:
n

int n = 10;
int arr[n]; /* not legal C */
n However, often want to allocate an array
where size of array not known in advance
n This is known as "dynamic memory
allocation"
n dynamic as opposed to "static" (size known at
compile time)
Dynamic memory allocation (2)
n Let's say we want to allocate memory for
e.g. arrays "on the fly"
n Later will have to deallocate memory
n Three new library functions for this:
n void *malloc(int size)
n void *calloc(int nitems, int size)
n void free(void *ptr)
n All found in <stdlib.h> header file
void *
n What does void * mean?
n It's a "pointer to anything"
n Actual type either doesn't matter or will be
given later by a type cast
n malloc/calloc return void *
n free takes a void * argument
Using malloc() (1)
n malloc()stands for "memory allocator"
n malloc() takes one argument: the size of the
chunk of memory to be allocated in bytes
n recall: a byte == 8 bits

n an int is 32 bits or 4 bytes

n malloc() returns the address of the chunk of


memory that was allocated
Using malloc() (2)
n malloc()is often used to dynamically allocate
arrays
n For instance, to dynamically allocate an array of
10 ints:

int *arr;
arr = (int *) malloc(10 * sizeof(int));
/* now arr has the address
of an array of 10 ints */
Using calloc() (1)
n calloc()is a variant of malloc()
n calloc() takes two arguments: the number of
"things" to be allocated and the size of each
"thing" (in bytes)
n calloc() returns the address of the chunk of
memory that was allocated
n calloc() also sets all the values in the allocated
memory to zeros (malloc() doesn't)
Using calloc() (2)
n calloc()is also used to dynamically allocate
arrays
n For instance, to dynamically allocate an array of
10 ints:

int *arr;
arr = (int *) calloc(10, sizeof(int));
/* now arr has the address
of an array of 10 ints, all 0s */
malloc/calloc return value (1)
n malloc and calloc both return the address of
the newly-allocated block of memory
n However, they are not guaranteed to succeed!
n maybe there is no more memory available
n If they fail, they return NULL
n You must always check for NULL when using
malloc or calloc
n We sometimes leave it out here for brevity
malloc/calloc return value (2)
n bad:
int *arr = (int *) malloc(10 * sizeof(int));
/* code that uses arr... */
n good:
int *arr = (int *) malloc(10 * sizeof(int));
if (arr == NULL) {
fprintf(stderr, "out of memory!\n");
exit(1);
}
n Always do this!
malloc() vs. calloc()
n malloc/calloc both allocate memory
n calloc has slightly different syntax
n as we've seen
n Most importantly: calloc() zeros out
allocated memory, malloc() doesn't.
n calloc() a tiny bit slower
n I prefer calloc()
Using free() (1)
n malloc() and calloc() return the address of
the chunk of memory that was allocated
n Normally, we store this address in a pointer
variable
n When we have finished working with this chunk of
memory, we "get rid of it" by calling the free()
function with the pointer variable as its argument
n This is also known as "deallocating" the memory
or just "freeing" it
Using free() (2)
int *arr;
arr = (int *) calloc(10, sizeof(int));
/* now arr has the address
of an array of 10 ints, all 0s */
/* Code that uses the array... */
/* Now we no longer need the array, so "free"
it: */
free(arr);
/* Now we can't use arr anymore. */
Using free() (3)
n NOTE: When we free() some memory, the
memory is not erased or destroyed
n Instead, the operating system is informed that we
don't need the memory any more, so it may use it
for e.g. another program
n Trying to use memory after freeing it can cause a
segmentation violation (program crash)
Dynamic memory allocation (3)
#include <stdlib.h>
int *foo(int n) {
int i[10]; /* memory allocated here */
int i2[n]; /* ERROR: NOT VALID! */
int *j;
j = (int *)malloc(n * sizeof(int));
/* Alternatively: */
/* j = (int *)calloc(n, sizeof(int)); */
return j;
} /* i’s memory deallocated here; j’s not */
Dynamic memory allocation (4)
void bar(void) {
int *arr = foo(10);
arr[0] = 10;
arr[1] = 20;
/* ... do something with arr ... */
free(arr); /* deallocate memory */
}

n Not calling free() leads to memory leaks !


Memory leaks (1)
void leaker(void) {
int *arr = (int *)malloc(10 * sizeof(int));
/* Now have allocated space for 10 ints;
* do something with it and return without
* calling free().
*/
} /* arr memory is leaked here. */

n After leaker() returns, nothing points to


memory allocated in the function à memory leak
Memory leaks (2)
void not_leaker(void) {
int *arr = (int *)malloc(10 * sizeof(int));
/* Now have allocated space for 10 ints;
* do something with it.
*/
free(arr); /* free arr's memory */
} /* No leak. */

n Here, we explicitly free() the memory allocated


by malloc() before exiting the function.
Memory leaks (3)
void not_leaker2(void) {
int arr[10];
/* Now have allocated space for 10 ints;
* do something with it.
*/
} /* No leak. */

n Here, we don't have to free() the memory,


since it was allocated locally (on the "stack").
n "What's the stack?" (you may ask...)
Memory leaks (4)
void crasher(void) {
int arr[10];
/* Now have allocated space for 10 ints;
* do something with it.
*/
free(arr); /* BAD! */
}

n Here, we free() memory we don't need to free!


n Anything can happen (e.g. core dump)
Memory leaks (5)
n Rules of thumb:
n 1) Any time you allocate memory using malloc()
or calloc(), you must eventually call free() on
that memory
n 2) You must free() the exact same pointer
(address) that was returned from malloc() or
calloc()
n 3) You don't have to free() the memory in the
same function as the one where malloc/calloc
was called
The stack and the heap (1)
n Local variables, function arguments, return value
are stored on a stack
n Each function call generates a new "stack frame"
n After function returns, stack frame disappears
n along with all local variables and function
arguments for that invocation
The stack and the heap (2)
int contrived_example(int i, float f)
{
int j = 10;
double d = 3.14;
int arr[10];
/* do some stuff, then return */
return (j + i);
}
The stack and the heap (3)
/* somewhere in code */
int k = contrived_example(42, 3.3);
n What does this look like on the stack?
The stack and the heap (4)

(more frames)
return value 52
function i = 42
arguments f = 3.3
j = 10 stack frame

local d = 3.14 for


contrived_example
variables arr[10] = (42, 3.3)
<garbage>
The stack and the heap (5)
n Another example:
int factorial(int i)
{
if (i == 0) {
return 1;
} else {
return i * factorial (i - 1);
}
}
The stack and the heap (6)
n Pop quiz: what goes on the stack for factorial
(3)?
n For each stack frame, have...
n no local variables
n one argument (i)
n one return value
n Each recursive call generates a new stack frame
n which disappears after the call is complete
The stack and the heap (7)

return value ?
factorial(3) stack frame
i = 3
The stack and the heap (8)

return value ?
factorial(2) stack frame
i = 2
return value ?
factorial(3) stack frame
i = 3
The stack and the heap (9)

return value ?
factorial(1) stack frame
i = 1
return value ?
factorial(2) stack frame
i = 2
return value ?
factorial(3) stack frame
i = 3
The stack and the heap (10)
return value ?
factorial(0) stack frame
i = 0
return value ?
factorial(1) stack frame
i = 1
return value ?
factorial(2) stack frame
i = 2
return value ?
factorial(3) stack frame
i = 3
The stack and the heap (11)
return value 1
factorial(0) stack frame
i = 0
return value ?
factorial(1) stack frame
i = 1
return value ?
factorial(2) stack frame
i = 2
return value ?
factorial(3) stack frame
i = 3
The stack and the heap (12)

return value 1
factorial(1) stack frame
i = 1
return value ?
factorial(2) stack frame
i = 2
return value ?
factorial(3) stack frame
i = 3
The stack and the heap (13)

return value 2
factorial(2) stack frame
i = 2
return value ?
factorial(3) stack frame
i = 3
The stack and the heap (14)

return value 6
factorial(3) stack frame
i = 3
The stack and the heap (15)

factorial(3)

result: 6
The stack and the heap (16)

void foo(void) {
int arr[10]; /* local (on stack) */
/* do something with arr */
} /* arr is deallocated */
n Local variables sometimes called "automatic"
variables; deallocation is automatic
The stack and the heap (17)

foo

local arr[10] =
stack frame
variables <whatever>
for foo()
The stack and the heap (18)
n The "heap" is the general pool of computer memory
n Memory is allocated on the heap using malloc() or
calloc()
n Heap memory must be explicitly freed using free()
n Failure to do so à memory leak!
The stack and the heap (19)
void foo2(void) {
int *arr;
/* allocate memory on the heap: */
arr = (int *)calloc(10, sizeof(int));
/* do something with arr */
} /* arr is NOT deallocated */
The stack and the heap (20)
void foo3(void) {
int *arr;
/* allocate memory on the heap: */
arr = (int *)calloc(10, sizeof(int));
/* do something with arr */
free(arr);
}
The stack and the heap (21)
0x1234 arr[0]
arr[1]
arr[2]
arr[3]
foo2 and arr[4]
foo3
(etc.)

heap
local arr =
0x1234 stack frame
variables
stack
The stack and the heap (22)
0x1234 arr[0]
arr[1]
(after foo2
exits, arr[2]
without arr[3]
memory
freeing arr[4]
leak
memory)
(etc.)

heap

stack
The stack and the heap (23)
0x1234 arr[0]
arr[1]
(after foo3
exits, with arr[2]
freeing arr[3]
memory) arr[4]
(etc.)

heap

stack
Memory leaks
n Memory leaks are one of the worst kinds of bugs
n often, no harm done at all
n eventually may cause long-running program to crash
n out of memory
n very hard to track down
n Special tools (e.g. valgrind) exist to debug
memory leaks
n I supply you with a very simple leak checker
Next week
n struct

n typedef

n Linked lists

You might also like