C Lab 2 - More on Memory in C

CS3410 Spring 2015

Due in lab section.

Overview

Welcome to CS3410 C Lab 2! This lab will give your more familiarity with pointers and memory in C and introduce you to dealing with structs. After completing each section, have a TA check over your work before you proceed.

To begin, download the clab2.c file file to your VM or CSUG account. It contains some C functions as well as function stubs you will need to implement.

After completing each function, compile your program with gcc -std=c99 -Wall -Werror clab2.c -o clab2. When you're done, you can run it with ./clab2.

Structs

Just like in your previous languages, C allows you define your own types. Somewhat like classes (but without methods!), structs are a way to define a group of fields to be stored together. They're often the basis of data structures.

typedef struct {
	int *buffer;
	int buffersize;
	int length;
} arraylist;
You can use a . to access the fields of a struct (again, like classes). If you have a pointer to a struct, you can use -> to both dereference and access.

arraylist a;
a.length = 5;
arraylist *a_ptr = &a;
a_ptr->length = 10;
(*a_ptr).length = 15;
That is, a->b is the same as (*a).b, but much easier to read and write.

Dynamic Memory

In C, primitive values and even arrays are often declared on the stack. This is known as static memory or static allocation. This is fast and efficient, but a major drawback is that the size of objects on the stack must be known at compile-time. Therefore, such common tasks as dealing with variable-length arrays cannot be done using only static memory.

C also allows the programmer to use dynamic memory, which is asking the operating system for more memory at runtime, and then using that newly allocated memory. While allocating memory does take time, dynamic memory is incredibly flexible and powerful, since any amount of memory can be requested and used at runtime (only limited by the amount of memory on the system).

The two functions in C to deal with dynamic memory (defined in stdlib.h) are malloc and free. malloc allocates new dynamic memory. It takes a single argument, the amount of bytes to allocate, and returns a void* pointer to the newly allocated memory. A void* pointer can point to objects of any type (e.g. int, float, or even a struct potato_salad) and can be cast to the appropriate type the programmer wants to use. Commonly used with malloc is the sizeof operator, which returns the size in bytes of any type defined in the program.


int *int_ptr = (int*)malloc(sizeof(int)); //allocates room for one int
int *int_ptr = (int*)malloc(5 * sizeof(int)); //allocates room for five ints

Any memory allocated using malloc must eventually be freed by a corresponding call to free. The free function takes a single argument: a pointer that was allocated using malloc. It releases the associated memory back to the operating system. After a pointer has been freed, accessing or using that pointer again leads to undefined and unstable behavior. Don't do this!

The code below is a simple example of dynamic memory in C, including the use of pointers as arrays.


int *int_ptr;
int_ptr = (int*)malloc(sizeof(int));
*int_ptr = 5;
printf("I stored the int %d at address %p\n", *int_ptr, int_ptr);
free(int_ptr); // don't forget to free memory when you're done with it!

/* Since arrays in C are just contiguous regions of bytes, pointers can also
 * be used to point to arrays. */
int *int_arr;
int i;
// here, we malloc enough space for 5 ints - creating an int array of length 5!
int_arr = (int*)malloc(5 * sizeof(int));
for (i = 0; i < 5; i++) {
	int_arr[i] = i;
}
// now, int_arr points to the int array [0;1;2;3;4]
free(int_arr);

Step 1: Implement the rand_array function

Using your new knowledge of pointers (and arrays as pointers), implement the rand_array function to allocate, initialize, and return a new array of random integers of a specified length. Use the rand_num function we provide to populate each array element with a random int. Note that this function is slightly different than last lab.

Step 2: Implement the initialize_complex_array function

Now implement the initialize_complex_array function to allocate, initialize, and return a new array of complex numbers made from the values in the input arrays. Note that complex_number is defined at the top as a struct.

Step 3: Implement the calculate_magnitude function

Take in the pointer to a complex_number struct, calculate the magnitude of the complex number, and return it as a float. To do so, you'll want to use the sqrt function from the math library; to do so, import math.h and add the flag -lm when you compile your code. The -lm flag links in the math library, meaning that the compiler will bring in code from the math library as appropriate; you'll discuss this much more rigorously later in the semester.

Step 4: Implement the calculate_magnitudes function

Take in the pointer to an array of complex_number structs, calculate the magnitude of each complex number, and return the values as a float array. Note the steps that lead you through the parts of using malloc. This is the same as what you've done before, but we want you to consider exactly what's happening.

Step 5: Debugging memory leaks

If you're on your own machine, install the valgrind tool with sudo yum -y install valgrind; it's already installed if you're on CSUG. Then run it on your lab 1 program (valgrind ./clab2). This is a tool used by C programmers to help find memory issues in their programs. Valgrind should report errors indicating that there was heap (dynamic) memory still in use when the program terminated. This is because something very important is missing from the main function we provided. Find it, and fix it!

Step 6: Have a TA check over your final program

Congratulations, you've completed C lab 2 and now have a deeper understanding of arrays, pointers, and memory management in C!