Pointers and Memory Allocation
Goals
This laboratory exercise provides practice with basic elements of pointers, addresses, values, and memory allocation in C.
Printing Memory Addresses
-
Download and examine the short C program
memory.cthat declares and initializes adouble, anint, and a string. It then prints the address of and value stored in each of the variables.-
To run a new shell that disables memory address randomization (a security feature) in
Linux, run the following Terminal command before proceeding.
setarch $(uname -m) --addr-no-randomize /bin/bash
- Compile and run the program. Make sure you understand the output.
- Draw a small memory diagram showing the location of each of the variables in the program. Are they allocated in the same order that you declared them? Is there any empty space between them?
- Modify the program by rearranging the variable declarations and/or changing the length of the string. (In particular, try a string that uses 5 or 7 bytes, including the null terminator.) Does this change the results you got previously?
-
To run a new shell that disables memory address randomization (a security feature) in
Linux, run the following Terminal command before proceeding.
The take-home message:
Small changes within a program can change how memory is laid out for a given program. The compiler will try to arrange memory for optimal performance, and this may include aligning variables with 4-byte boundaries. For C programmers, this can sometimes mean that a program which appears to work correctly (but in fact overwrites the end of an array), can suddenly stop working due to seemingly innocuous changes—for example, changing the order in which variables are declared.
Allocating and Freeing Memory
The Variable-Length Array (VLA) option within 1999 Standard C allows yet another mechanism for declaring arrays, as follows.
void takePictures(int numPics)
{
Picture frames[numPics];
...
}
The size of an array is a variable (numPics), and a
value is assigned to this variable before space for the array is
allocated. This technique allows the user to specify the size of an
array at run time; but once the array is declared, its size cannot be
changed. (The details are discussed in King, Section 8.3.)
-
What does the compiler know about the size of VLAs, versus
statically declared arrays, versus dynamically allocated memory? To
answer this question, consider the following program.
/* Program to compare sizes of static arrays, VLAs, and malloced memory. */ #include #include int main() { int length = 5; int staticArray[5]; /* Compiler statically allocated array */ int varLenArray[length]; /* Program dynamically allocates array */ /* Programmer dynamically allocates memory */ int * dynamicArray = malloc(length * sizeof(int)); if (dynamicArray==NULL) { /* Verify memory was available and allocated */ printf("unable to allocate dynamic array. exiting."); return 1; } printf("sizeof(int) = %lu\n", sizeof(int) ); printf("sizeof(staticArray) = %lu\n", sizeof(staticArray) ); printf("sizeof(varLenArray) = %lu\n", sizeof(varLenArray) ); printf("sizeof(dynamicArray) = %lu\n", sizeof(dynamicArray) ); free(dynamicArray); /* Free memory allocation */ } - What output do you expect this program to produce?
- Copy, compile, and run the program to verify your predictions.
-
You might notice that the dynamic allocation of a VLA versus a
call to
mallocis subtly different. What test can we make withmallocthat we cannot with a VLA? What does this suggest about the runtime verifiability of VLAs?
The take-home message:
You should only use the
sizeof operator on types, and never
on variable names. Such uses frequently lead to confusion.
-
When arrays were first discussed, an early application was to use the Scribbler 2 to take 3 pictures and then display those pictures in the order they were taken. Program
scribbler-movie.cexpands the former program slightly to takenumPicspictures, display them in order, and then display them in reverse order.Copy and run
scribbler-movie.c, and then review how the program works. -
In this problem, we explore the alternative strategy using dynamic
memory allocation.
-
Within
scribbler-movie.c, replace the declaration
with the linesPicture pics[numPics]; /* Declare a C99 VLA */
Notes:Picture* pics; pics = malloc (numPics * sizeof (Picture));-
In this revised declaration,
picsis a pointer to an array of pictures. That is,picsidentifies the location of an array where each element has typePicture. -
In the first line above,
picsonly indicates a location for an array. Thus the program must allocate space for the array separately, in the second line. Themallocstatement asks the C library to perform this memory allocation. -
Once declared and initialized, references to
the
picsarray are exactly the same as in the original version.
-
In this revised declaration,
- Add the corresponding lines to verify the memory allocation (printing an error message and exiting if it fails).
- Add the corresponding line to free the memory when it is no longer needed.
-
Recompile and run the revised program
scribbler-movie.c. -
Draw a diagram of main memory for both the original and revised
versions of
scribbler-movie.c. In the diagram, show what variables are stored on the run-time stack and what information (if any) is stored elsewhere.
-
Within
Memory Leaks and Other Problems
-
Consider the following program.
#include #include int main (void) { int j=0; while (j>=0) { int n = 100000000; int * a = (int*) malloc (n * sizeof(int)); for (int i=0; i < n; i++) a[i] = i; j++; printf ("%d\n", j); } return 0; } // main - What is wrong with the program? What do you expect it to do when run?
-
Now copy the program and run it. On my machine, it prints
numbers up to around 40 before it crashes. How about yours? Do
you understand why it crashes?
(If you find yourself waiting for the crash, read on below about Address Sanitizer.)
-
Add the following code immediately after
the
malloccall to confirm your understanding.
The library functionif (a==NULL) { perror ("Error allocating memory"); exit (EXIT_FAILURE); }perror(), declared instdio.h, prints a message regarding the most recent error that occurred in any system or C library call. Thus, with this placement,perrorwill print any error that may occur related tomalloc. (We will discuss system calls later in the course.)If you still are not sure why the error occurred, please ask.
Detecting Memory Errors
In the next few exercises, you will experiment with a tool built-in
to clang (but not gcc)
named AddressSanitizer
(or ASan) that can detect and
report on several types of errors related to dynamic memory
management:
- Use after free (aka dangling pointer dereference)
- Use after return or scope
- Buffer overflows (heap, stack or global variables)
- Memory leaks
ASan will invoke your executable code line by line. This allows it to monitor your use of memory and report related errors. It also adds a lot of overhead, so you may notice that it runs slowly.
-
Modify your program from the previous exercise so that it allocates
(and fails to free) only ten arrays or so. To enable
AddressSanitizer in your executable, you need to use
the compiler flag
-fsanitize=addressalong with the-gflag to get the helpful debugging symbols. Usingmake, you'd add:make CFLAGS="-g -fsanitize=address" target
- After compling with ASan enabled, run your program. Your program will function normally until AddressSanitizer detects one of the errors above, spewing out a lot of diagnostic information related to the problem.
- Read through the output to make sure you understand what problem it has diagnosed and where (i.e., what line of code) it manifests on.
-
Modify your code from the previous exercise to free the memory you have allocated. Note that you will need a call to free in each loop iteration, so that you can free the memory before you lose the pointer to it!
Now rebuild your code with ASan again and run it. What happens to the output when you run the program?
-
In this exercise, you will experiment with a few more memory-related
errors ASan can catch.
-
Add an extra call to
free()somewhere in your program. Then rebuild your program, run it, and examine the diagnostic output. (After you have done so, remove the offending call again.) -
Another common error that ASan can
catch is accessing memory after it has been freed (known as
"use free after" or "dangling pointers dereference"). To test
this, add statements such as the following immediately after
your call to
free().
Compile your program, run it, and study the ASan diagnostic output. After you understand what "use after free" means, remove the offending code from your program.a[0] = 5; printf("a[0]=%d\n", a[0]); - ASan can also tell you when you access elements that are out-of-bounds of an allocated memory block. Modify your program to test this, noting what information ASan gives you about the error. (Then remove the error afterwards.)
-
Add an extra call to
- Note that ASan will stop your program when it encounters the firt three kinds of memory errors, but it can only report a memory leak after the program completes. To learn more, look at the on-line documentation for AddressSanitizer.
