0% found this document useful (0 votes)
15 views53 pages

03B+04A Dynamic Memory Allocation Memory Leaks

The document discusses dynamic memory allocation and safe memory management in C/C++. It covers motivation for dynamic allocation when size is unknown at compile-time, functions like malloc(), calloc(), and realloc() to allocate and reallocate heap memory, and free() to deallocate memory. It also discusses memory bugs like segmentation faults that can occur if memory is accessed without being allocated or after being freed, and how to avoid these bugs by checking for valid pointers and using memory management schemes.

Uploaded by

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

03B+04A Dynamic Memory Allocation Memory Leaks

The document discusses dynamic memory allocation and safe memory management in C/C++. It covers motivation for dynamic allocation when size is unknown at compile-time, functions like malloc(), calloc(), and realloc() to allocate and reallocate heap memory, and free() to deallocate memory. It also discusses memory bugs like segmentation faults that can occur if memory is accessed without being allocated or after being freed, and how to avoid these bugs by checking for valid pointers and using memory management schemes.

Uploaded by

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

C

Programming Workshop in C/C++ (67315)


Dynamic memory allocation &
safe memory management

2
Expected Learning Outcomes
Dynamic memory allocation & safe memory management

0 Motivation for dynamic memory allocation


Understand

0 Memory allocation and de-allocation functions


0 Memory bugs: segmentation fault & memory leakage
0 Resource ownership schemes: exclusive vs. dynamic

0 Safely allocate, reallocate and free heap memory


0 1D arrays of primitive or aggregate data types
Apply

0 Avoid segmentation faults


0 Use resource ownership schemes to safely manage
memory and avoid memory bugs

3
Motivation for dynamic allocation

4
Motivation for dynamic allocation
The size of global and local* variables must be
known at compile-time
int global_int; // sizeof(int)
int global_array[1000]; // 1000*sizeof(int)

void foo ()
{
int x = 5; // sizeof(int)
int arr[] = {1, 2, 3}; // 3*sizeof(int)
int *p = &x; // size of a memory address
}

What if don’t know the size at compile-time?


* local VLA arrays are an exception but not allowed in course due to safety issues
5
Example: an app with a 2D board

e.g. Slither:

6
Case 1: board size is known at compile time

Easy to implement using a static (fixed-size) array:


#define BOARD_SIZE 5
:
int main ()
{
int board[BOARD_SIZE][BOARD_SIZE];
...

return EXIT_SUCCESS;
}

7
Case 2: user specifies board size
at run-time
#include <errno.h> // global error tracking – details later…

//! reads n from user, return true if successful


bool read_n (int *n);

int get_board_size_from_user ()
{
printf("Please enter board size:\n")
long int size = -1;
bool ok = read_n(&size);
if(!ok)
{
errno = EIO; // global error tracking – details later…
}
return size;
}
8
Case 2: user-specified board size

int main()
{
size_t size = get_board_size_from_user ();
int board[size][size]; // is that ok?
// a variable size for local array  VLA!
// not allowed in this course

9
A valid solution: slack memory
Allocate extra memory at compile-time, But what if
and use a smaller subset as needed size>MAX_SIZE?
#define MAX_SIZE 500

{
int board[MAX_SIZE][MAX_SIZE] = {0};
size_t size = get_board_size();
// fill only relevant cells:
for(int i = 0; i < size; i++)
{
for(int j = 0; j < size; j++)
{
board[i][j] = (i+1)*(j+1);

10
Compile-time size limit is problematic

❑ The program cannot handle a board larger than

MAX_SIZE, specified at compile time

❑ If we set a large MAX_SIZE values, the program

requires this amount of stack memory, regardless


of actual needs.

Can we use memory exactly as needed?

11
 Dynamic heap
1. Memory is allocated
0xffffff
and freed at run
time by the memory Stack
manager
Free space
2. The programmer
controls how Heap
much is allocated
and when Data (Global/Static)
3. Adapt based on
run-time conditions, Code (text)
available computer 0x000000
memory
12
Dynamic allocation
of heap memory

13
malloc(): allocating heap memory

void* malloc (size_t size); // <stdlib.h>

malloc() returns a pointer to a new memory


block of size bytes, or the NULL pointer
constant if it fails
Reminder: The void* data type is convertible to any <type>* data type

14
malloc(): alternative syntaxes

int n = …; // number of elements


int *p = malloc (sizeof(*p) * n);
int *p = malloc (sizeof *p * n);
int *p = malloc (sizeof(int) * n);

// optional in C99: explicit type-casting


int *p = (int*) malloc (sizeof(int) * n);

15
calloc(): Alternative to malloc()

void* calloc (size_t n, size_t size);


• very similar to malloc().
• Returns a pointer to a new contiguous
memory block of n elements of size size
each, or NULL if it fails.
• Unlike malloc(), all elements are
initialized to zero.

* Why is it even needed? Out of scope, but… speed, memory alignment


16
calloc(): Alternative to malloc()

int n = …; // number of elements


int *p = calloc (n, sizeof(*p));
int *p = calloc (n, sizeof *p));
int *p = calloc (n, sizeof(int));

// Optional in C99: explicit type-casting


int *p = (int*) calloc (n, sizeof(int));

17
Efficiency: static vs. dynamic arrays

int arr[3]; // Option I - faster

int *arr = // Option II - slower

malloc (sizeof(*arr) * 3);

Dynamic allocation is less efficient:


• Slower memory access
• Heap memory management
• Size not known at compile-time = less optimizations
 Prefer static arrays (array data type) if efficiency is
critical

18
Rule: Always check if ptr is valid!

char *str = malloc (sizeof(char) * 5);


if (str != NULL)
{
<assume str is valid>
}

19
Example: a dynamic array of N complex numbers
typedef struct Complex {
double imag, real;
} Complex;

//! Retrieves number of complex numbers from user


//! Handles errors nicely for us
int get_n_from_user();
request contiguous heap malloc returns a
memory sufficient for n pointer of the generic
int main() Complex structures. type void*, it can be
{
converted to any
int n = get_n_from_user (); other pointer type
Complex *cp = malloc (n * sizeof (Complex));
if(cp)
{
cp[1].real = 10; // access the second dynamic array element
}
… We allocated but how do
we de-allocate to prevent
return EXIT_SUCCESS;
a memory leak?
}

20
De-allocating
memory

21
free(): De-allocating memory
void free (void *p ); // <stdlib.h>

Tells the memory manager to return the memory block


pointed by p to the pool of unused memory

No error checking!

● If p was not allocated by malloc(), or if it was freed


before, undefined behavior

● free(NULL) : no operation is performed, all is good

22
Good practice: set ptr to NULL after free()
int *p = malloc (sizeof(*p));

free (p);
p = NULL; // good practice

But - no need to set p to NULL if it is a local


variable that goes out-of-scope right after free:

free (p);
} // OK - p is going out-of-scope

23
How much memory to free?

Q: free() gets a parameter of data


type void* - how does it know how
many bytes should be freed?

int *p = malloc (sizeof(*p) * 20);



free (p); // how many bytes to free?

24
How much memory is de-allocated?
The memory manager (provided by the compiler
directly or via the operating system) keeps track of
how much memory was allocated, (e.g., by putting
some information right before the allocated address)
and thus knows how much memory to free

Do not forget: free() must get the exact


same address previously returned by
malloc(), calloc(), or realloc().

25
Q: Will the code free
elements arr[5]..arr[9]?
size_t n = 10;
int *arr = malloc
(n * sizeof(int));

free(arr + 5);
A. Yes! Do not forget: free()
B. Run-time bug  must get the exact
same address
C. Compile error  previously returned by
D. It depends… malloc(), calloc(),
or realloc().

26
Reallocating
memory

27
realloc(): change size for an existing array

void* realloc(void *ptr, size_t new_size);

• attempts to resize the memory block pointed


to by ptr
• ptr must have been previously allocated using
malloc()/calloc()/realloc().
• If ptr is NULL, equivalent to malloc()
• If realloc() fails, it returns NULL but it does
not free ptr
28
Safe usage of realloc()
Avoiding memory leakage

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



{
void *tmp = realloc (arr, sizeof(*arr) * 10);
if (!tmp)
{
free (arr);
arr = NULL; // indicate it is invalid
<handle error and return/exit>
}
arr = tmp; // success
}

free (arr); // do NOT forget to free when done

29
Memory bugs:
Segmentation fault

32
Segmentation fault

Segmentation fault (seg-fault) is a nasty error


caused by accessing memory that “does not belong”
to our program.

int *p; // uninitialized

// These may or may not invoke a seg-fault:

int a = *p; // read from a random address

*p = 4; // write to a random address

33
Q: Seg-fault
Bob gets a "segmentation fault" while
working on his C course assignment.
What is a likely explanation?

A. Bob's program is trying to read or write


an invalid memory location
B. Bob's program is trying to write into an
invalid memory location
C. Bob's program is trying to read from an
invalid memory location
D. Bob's program can't be compiled,
some function declarations are missing

34
Memory bugs:
memory leakage

35
Example: strdup(), a non-standard
library function for duplicating strings
A possible implementation:
char *strdup (const char *str) // <string.h>
{
char *dup = malloc (strlen (str) + 1); // +1 for '\0'
if (!dup)
{
return NULL;
}
strcpy (dup, str); // Copy the characters
return dup; // Return the new string
}

36
Is there a bug in this code?

Stack
>call<
void foo (char const* p) p

{ q

char *q = strdup (p);


// do something with q
}


Heap
37
Is there a bug in this code?

Stack
>call<
void foo (char const* p) p

{ q

char *q = strdup (p);


// do something with q
}


’a‘
’b‘

Heap
’0\‘
38
Is there a bug in this code?
>call<

Stack
void foo (char const* p) p

{ q

char *q = strdup (p);


// do something with q
}


Memory leak: The allocated memory
is locked and we threw away the keys ’a‘
(pointer q) ’b‘

Heap
’0\‘
39
 Don’t forget to free!
>call<

Stack
void foo (char const* p) p

{ q

char *q = strdup (p);


// do something with q
free(q);


}

Avoding memory leakge: free the ’a‘


allocated memory before you throw ’b‘

Heap
away the keys… ’0\‘
40
Resource ownership schemes for
avoiding memory bugs

41
Resource ownership:
Who is responsible for cleanup?
• System resources must be managed carefully:
• Dynamically allocated memory

• Storage files

• Network connection

• I/O devices

• Major challenges in resource management:


• Accounting
Ensuring resources are freed when and only when they are no longer required

• Speed/space
Efficiently allocating and freeing both space-wise (fragmentation) and time-wise (slow

operations) – out of the scope off this course=

42
Scheme #1: Fixed Ownership
a.k.a. clean thy own mess

• A resource belongs to the code entity (e.g. a

function) that allocated it

• That code entity is solely responsible for freeing

that resource

43
Example for fixed ownership:
local stack variables
• Local stack variables are freed automatically when the
scope that declared them ends

• Their ownership cannot be “passed” to another code


int* foo()
{
int x = 5;
double y = 10.3;
char z[] = "Hello world\n";
return &x; // BUG! cannot “pass ownership”
}
44
Scheme #2: Dynamic ownership
Ownership can be transferred
• The resource is owned by the allocating code entity

• Ownership can be transferred from entity to entity


(e.g. from called function to calling function)

• To transfer ownership, we need a key:

• Pointer

• File handle

• Etc.

45
Example for dynamic ownership:
Returning a newly heap-allocated array
• We can choose to pass a memory address for a newly-allocated heap
memory
• This is a programmatic API decision - the compiler doesn’t know (or
care!) whether ownership of arr is fixed or dynmic

// API: foo() returns a newly allocated array


int* foo(int n)
{
int* arr = calloc(n, sizeof(*arr));
if (arr) { … }
return arr; // passing address of heap memory
}

46
Can we have fixed ownership over heap-
allocated memory? Our decision!
• As programmers, we are allowed to decide whether we
want to have fixed ownership scheme for some heap
memory

// API: foo() does not pass ownership of anything


void foo(int n)
{
int* arr = calloc(n, sizeof(*arr));
if (arr) { … }
free (arr); // not passing ownership  cleanup
}

47
Fix leaks by formally

Stack
considering API of ownership >call<

p
void foo (char const* p) q

{
char *q = strdup (p);
// do something with q


} // is this a leak?

API of strdup(): “The returned pointer must


be passed to free to avoid a memory leak”
 strdup() transfers ownership to foo() –
just like malloc() would

Heap
48
Fix leaks by formally
considering API of ownership >call<

Stack
p
void foo (char const* p) q

{
char *q = strdup (p);
// do something with q


} // is this a leak?

Memory leak: strdup() transferred


ownership to foo(), so foo() now owns q ’a‘
and must either free q or pass ownership over ’b‘

Heap
q to the calling code entity
’0\‘
49
I. Solution 1 – free (fixed ownership)
>call<

Stack
void foo (char const* p) p

{ q

char *q = strdup (p);


// do something with q
free (q)


}

’a‘
’b‘

Heap
’0\‘
50
I. Solution 2 – transfer (dynamic ownership)
>call<

Stack
char* foo (char const* p) p

{ q

char *q = strdup (p);


// do something with q
return q;


}

’a‘
’b‘

Heap
’0\‘
51
Free() or transfer rule
• Any code entity (eg a function) that owns a resource (eg a
pointer, a file) should either free it (fixed ownership) or
return it (dynamic ownership).
• If ownership is transferred, it must be documented (API
decision)

52
Scheme #3: Weak ownership
= access-only ownership
• A code entity may need to access (read or write)
the contents of a resource without truly owning it
(= no responsibility for memory de-allocation)
• This is also an API decision – the compiler is not
aware of fixed/dynamic/weak ownership

53
Example for weak (access-only)
ownership
• strcmp(const char *s1, const char *s2) has read access to
the memory of s1 and s2, but it does not get ownership over it:
• the compiler is responsibe for clearing the stack memory of s1
• the calling code entity must free s2

{
char s1[] = “hello”;
char* s2 = strdup(s2);
// strcmp gets weak ownership of s1 and s2
if (s2 && strcmp(s1, s2)) …
free (s2); // code entity frees s2 (user)
} // s1 is freed automatically (compiler)

54
Summary: Memory ownership & bugs
• Memory ownership schemes
• Fixed ownership – you own it, you free it
• Dynamic ownership - you own it, pass it on
• Weak ownership – access only

• Ownership in C is typically an API decision


• Whenever you add a pointer, ask yourself:
• What code entity owns it?
• Does that entity need to free it?

55

You might also like