structs, malloc

February 25 2002

announcements

if you didn't pick up your midterm in lecture, i have your midterm. you can pick them up at the end of discussion.

how do you feel about arrays? we can talk about them, or do some practice problems if you want.

structs

sometimes, we need to represent things in our programs that are best described by a combination of different variable types. for example, suppose we want to write a program that keeps track of inventory for a grocery store. for each item that the grocery store sells, we want to keep track of the name of the item, and the number we have in stock. in c, we can use structs to represent these kinds of things. the declaration of the item struct looks like this:

struct item 
{
  char name[50];
  int  quantity;
};

when we declare the item struct, we are declaring a new data type. it's kind of like drawing the "blueprints" for items... we're telling the computer what things need to be included in every item.

if we actually want some items to play around with, we need to declare some variables of type struct item, like this:

struct item first;
struct item second;

we now have two items that we can use. according to our definition of the item struct, each item consists of an array of 50 characters called name, and an integer called quantity. to access variables within a struct, you use the dot operator, like this:

strcpy(first.name, "apple");
first.quantity = 10;
strcpy(second.name, "orange");
second.quantity = 50;

passing structs to functions

we can pass structs to functions in the same way that we pass any other variable. suppose we want to write a function that displays the name and quantity for an item. the definition looks like this:

void display(struct item i)
{
  printf("%s %d\n", i.name, i.quantity);
}

back inside main, we can now say

display(first);

if we want to display the contents of the first item.

suppose we want to write a function that fills in an item's information for us. we can try to declare it like this:

void broken_fill_in(struct item i, char name[], int quantity)
{
  strcpy(i.name, name);
  i.quantity = quantity;
}

unfortunately, this does not work, because the item will be passed by value [don't forget about call by value]! the broken_fill_in function will receive a copy of the struct, and the copy will be modified. the original item back in main will not be modified.

if we want to make this work, we need to pass the struct by reference, like this:

void fill_in(struct item* i, char name[], int quantity)
{
  strcpy((*i).name, name);
  (*i).quantity = quantity;
}

and in main, we would say something like this:

fill_in(&first, "bananna", 50);

to save some typing, we could have declared fill_in like this:

void fill_in(struct item* i, char name[], int quantity)
{
  strcpy(i->name, name);
  i->quantity = quantity;
}

i->name is equivalent to (*i).name.

arrays of structs

suppose we want to have lots of items in our inventory. we can declare an array of items, like this:

struct item inventory[100];

this makes an array of 100 items. if we want to fill in the information for some of the items and display the info, we can do it like this:

strcpy(inventory[24].name, "bananna");
inventory[24].quantity = 50;
printf("%s %d\n", inventory[24].name, inventory[24].quantity);

fill_in(&(inventory[12]), "grape", 20);
display(inventory[12]);

dynamic memory allocation

our programs can ask the computer for more memory as they are running by using the malloc command. to do this, we ask malloc for some number of bytes of memory, and malloc returns us a pointer to a piece of memory of the requested size. here's a simple example:

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

the sizeof command tells us the number of bytes that are needed to store some data type. so, sizeof(int)tells us how many bytes are needed to store an int [which is usually 4]. it's best to use the sizeof command, even though we know that we need 4 bytes, because in a few years people might be using 8 bytes to store integers, and we would have to go back and change our program to ask for 8 bytes. but, if we use sizeof, we won't need to change our program.

the (int*) in front of the malloc call tells the computer that we want to use the pointer returned by malloc as a pointer to an integer.

after the malloc is done, we have reserved space in memory for an integer, and we have a pointer to that integer. we can now do all the usual stuff with this pointer, like *p = 24.

here's some more examples of malloc:

/* malloc a double */
double* dp = (double*) malloc(sizeof(double));

/* malloc an item */
struct item* ip = (struct item*) malloc(sizeof(struct item));

/*
 * malloc an array of 50 integers - the name of an array is always
 * a pointer to the first element of the array
 */
int* array = (int*) malloc(50 * sizeof(int));

after doing all this, we can use p, dp, and ip like any other pointers, and we can use array like any other array of integers.

when we don't need a malloc'd piece of memory anymore, we need to free it. to do this, we use the free command, and we pass it a pointer to the piece of memory that we want freed, like this:

free(p);
free(dp);
free(ip);
free(array);

exercises

write a function update that takes an item and an integer x, and increases the quantity of the item by x.

using malloc, dynamically allocate memory for a new item. make its name kiwi, and its quantity 5. display the new item. try doing this both with and without using the fill_in and display functions above.