Summary: In this laboratory, you will experiment with dynamic memory allocation. You will also gain experience with a tool, called valgrind, that can detect memory leaks in C programs.
Prerequisites: Arrays, pointers, and structs in C.
Contents:
a. Recall that you can use malloc to "dynamically allocate" a single chunk of memory large enough to store multiple data values, and then use that memory as if it were an array. Because of the close correspondence in C between pointers and arrays, you can subscript the pointer returned from malloc to access the array elements.
Write a program that dynamically allocates enough (contiguous) memory for 4 integer values. Your program should then read 4 integers from the user, store them in your newly-allocated memory, and print the values in reverse order.
If you did not do so already, please add code to check the return value of malloc, and at the end of the program please free your dynamically-allocated memory.
b. Modify your program from the previous exercise to allow the user to enter as many values as s/he wishes (signaling the end of input by entering ctrl-d). Your program should initially allocate space for 4 integers as before, but then allocate additional space each time you need to store a new value when the array is full.
This data structure is sometimes called an "expandable array." How much additional space should you allocate each time it needs to expand? A common strategy is to double the size of the array. This approach balances the objectives of saving space and saving time. (Note that malloc and realloc are relatively expensive function calls.)
a. Recall that you used the following type definition in a previous lab.
int MAX 20;
typedef struct {
char first[MAX];
char last[MAX];
char dorm[MAX];
int phone;
} student_t;
Write a function readData with the prototype:
student_t* readData()that dynamically allocates space for a single student_t variable, reads data from stdin to fill the struct, and returns a pointer to the new struct variable.
Write a test program that uses your function to read and then print data for one student. At the end of your program, remember to free your dynamically-allocated memory.
Reflect on the relative merits of returning a pointer from a function if it points to:
b. Modify your program from the previous exercise to read data for 4 students. To do this, your main program should allocate space (statically or dynamically) for a 4-element array of student_t pointers. Then call readData once for each of the four students you will store. After all the data is entered, print it out in a nice format. (Remember that you already wrote a function for this in a previous lab.)
If you have time at the end of this lab, you can return to this problem and modify it use an expandable array to store as many student data records as needed -- without knowing how many that will be in advance. But for now, let's move on.
a. The purpose of this exercise is to give you some experience with common coding mistakes and the error messages that result.
Write a short C program that allocates two integer arrays: one of them statically (ie, with something like "int arr[5]"), and the other dynamically.
Add a line of code that attempts to free the statically-allocated array. Compile and run your program to see the error it generates, and then remove the offending code.
Similarly, add a line of code that attempts to free the dynamically-allocated array a second time. (You did free it once already, right?) After witnessing the error, you may again remove the offending code.
b. Consider the following program. What is wrong with it? What do you expect it to do when it is run?
#include <stdio.h>
#include <stdlib.h>
#define FALSE 0
#define TRUE 1
int main() {
int done = FALSE;
int j=0;
while (!done) {
int n = 10000000;
int* a = (int*)malloc(n * sizeof(int));
int i;
for (i=0; i < n; i++)
a[i] = i;
j++;
printf("%d\n", j);
}
return 0;
}
Now copy the program and run it. On my machine, it prints numbers up to around 80 before it crashes. How about yours? Do you understand why it crashes?
Add the following code immediately after the malloc call to confirm your understanding. The library function perror(), declared in stdio.h, prints a message regarding the most recent error that occured in any system call or C library call. Thus, with this placement, perror will print any error that may occur related to malloc.
if (!a) {
perror(NULL);
exit(1);
}
If you still are not sure why the error occured, please ask.
c. In the next few exercises, you will experiment with a (non-GNU) Linux tool named Valgrind that can detect and report on several types of errors related to dynamic memory management. Actually, Valgrind is a suite of debugging tools; the specific Valgrind tool we will use is called Memcheck. According to the documentation at http://valgrind.org, Valgrind is pronounced with a short i (like grinned), and the origins of the name are related to Norse mythology.
Modify your program from the previous exercise so that it allocates (and fails to free) only ten arrays or so. Then build your program with a command like the following. Note that the -g option is necessary; Valgrind needs the debugging information it adds to the executable code.
gcc -Wall -g myprog.c
Valgrind is a "virtual machine", which means that you will run Valgrind, and it 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.
Next, run your program with Valgrind, using a command like the following. (For future reference, if your program takes command-line arguments, you can simply add these to the end of the command line.)
valgrind --leak-check=yes ./a.out
Your output should include some header information about Valgrind, then the output of your program, and then some diagnostic information about the memory leak.
Do not be misled by the line that says "ERROR SUMMARY: 0 errors from 0 contexts". This apparently relates to specific error types. Continue reading, and you should see "malloc/free: 10 allocs, 0 frees" and also the following.
==22813== LEAK SUMMARY: ==22813== definitely lost: 0 bytes in 0 blocks. ==22813== possibly lost: 400,000,000 bytes in 10 blocks.
d. Next, 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, and run it with Valgrind to see the improved output message.
e. In this exercise, you will experiment with a few more memory-related errors Valgrind can catch. First, add an extra call to free() somewhere in your program. Then rebuild your program and take a look Valgrind's output. (After you have done so, remove the offending call again.)
Another common error that Valgrind can catch is accessing memory after it has been free'd. To test this, you can add statements such as the following immediately after your call to free(). Go ahead and try it, noting that Valgrind tells you the line numbers where the errors occur, and then remove the offending code.
a[0] = 5;
printf("a[0]=%d\n", a[0]);
Next, Valgrind 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 Valgrind gives you about the error. (Then remove the error afterwards.) Unfortunately, Valgrind can not detect out-of-bounds errors with statically allocated arrays. It can only do this for dynamically-allocated memory.