| CSC 161 | Grinnell College | Spring, 2015 |
| Imperative Problem Solving and Data Structures | ||
This laboratory exercise provides practice with basic elements of pointers, addresses, values, and memory allocation in C.
Write a short C program that declares and initializes (to any value you like) a double, an int, and a string. Your program should then print the address of, and value stored in, each of the variables.
Use the format string "%u" to print the addresses as unsigned (32-bit non-negative) integers.
Reminders:
Draw a small memory diagram showing the location of each of the variables in your program. Are they allocated in the same order that you declared them? Is there any empty space between them?
Modify your 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?
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.
Consider the following code segment that swaps the values of two variables x and y.
/* declare and initialize variables x and y */ double x = 5.5; double y = 7.7; /* swap values for x and y using a temporary intermediate */ double temp = x; x = y; y = temp;
Include the above code segment into a program.
/* declare and initialize variables x and y */ double x = 5.5; double y = 7.7; /* WRONG --- but why to swap values for x and y */ x = y; y = x;
Separate the code for swapping x and y into a separate swap function. The function should have two parameters of type double.
Will the parameters have to pass values by value or by reference? Why will one approach work, while the other approach will not?
Complete the code for the function swap.
Write a driver function (i.e., main) to test your swap routine to be sure it works.
Review program max-min.c from Step 6 of the lab on arrays. In that lab, you wrote a function find_max_min_avg that computed the maximum, minimum, and average of the values in an array.
In a few sentences, explain how the function works and how it is called.
Remember: The name of an array is simply a pointer to the first element of the array.
Expand the main program, so that it calls the same function with several arrays, printing the results each time. (That is, the function has one array parameter, but it is called in the main program several times with arrays of different names.)
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.c expands the former program slightly to take n pictures, display them in order, and then display them in reverse order.
Copy and run scribbler-movie.c, and review how the program works.
Program scribbler-movie.c takes advantage of a "Variable-Length Array (VLA)" option within 1999 Standard C. 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.
Within scribbler-movie.c, replace
Picture pics[numPics];
with the lines
Picture *pics;
pics = malloc (numPics * sizeof (Picture));
Notes:
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.
Modify scribbler-movie.c further to obtain scribbler-movie-10.c, so that every time the Scribbler 2 robot takes 10 pictures, it displays all of the pictures (from the first to the most recent). This version of the program should not display the pictures in reverse order.
Consider the following problem:
The Scribbler 2 is to take a long sequence of pictures:
Observations:
One approach to this problem utilizes an array to store picture references — just as in scribbler-movie-10.c.
Modify scribbler-movie-10.c to implement this new approach for handling many pictures without an initial designation of how many pictures might be taken.
Consider the following program.
#include <stdio.h>
#include <stdlib.h>
#define FALSE 0
#define TRUE 1
int main() {
int done = FALSE;
int j=0;
while (!done) {
int n = 100000000;
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;
}
What is wrong with it? 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 30 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 occurred in any system or C library call. Thus, with this placement, perror will print any error that may occur related to malloc. (We will discuss system calls later in the course.)
if (!a) {
perror(NULL);
exit(1);
}
If you still are not sure why the error occurred, please ask.
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 -o myprog -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.
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 ./myprog
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.
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.
In this exercise, you will experiment with a few more memory-related errors Valgrind can catch.
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 freed. 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]);
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.
Look at the on-line documentation for Valgrind: http://valgrind.org. In particular, I suggest reading quickly through the "Quick Start" information, and also Sections 4.1 and 4.3 in the "User Manual".
Development of laboratory exercises is an iterative process. Prof. Walker welcomes your feedback! Feel free to talk to him during class or stop by his office.