Originally published by Robert Beisert at fortcollinsprogram.robert-beisert.com

Linux + C – Structures, Switches, and Your First Header

This will be a bit of a longer section, because I couldn’t think of a fun way to write the code otherwise. Bonus!

Structures

A structure is a data element defined by the programmer which contains several “member” data elements. We define these structures to allow ourselves to handle a set of related data much more easily, without having pieces scattered around our code.

For example, we could create a structure named temps with two floating point values: one for Fahrenheit temperature and one for Celsius temperature. That could look like this:

struct temps

{

float fahrenheit;

float celsius;

};

When we want to reference these values, we use the format

structure_name.data_member

Switch-Case statements

There are times where one integer value selects between a host of possible results. Perhaps you create a random number, which you compare to a list of monsters you want to spawn in your game. In these cases, it is easier to use the switch-case decision tree than an if-else stack.

For example, let us suppose we have a variable called “apples”, and we want to deal with the values 0 through 3. We can write a switch case statement to handle all the possible values like this:

switch(apples)
{
case 0:
//Code
break;
case 1:
//Code
break;
case 2:
//Code
break;
case 3:
//Code
break;
default:
//Code
break;
}

Note: We always want to end these statements with “break”, which tells the program to leave the switch statement. Otherwise, the program will try to execute the code that comes in another case, which is bad.

Note: We create a default tag to handle any values that we didn’t expect or didn’t want.

Headers

We’ve already discussed how we have two basic program files: headers and code. We’ll now talk a little bit about how headers should be used.

A header file should NEVER contain executable code. When we put functions in headers, we open the door to frustrating errors which we just can’t resolve. You might not even be able to determine whether the error is even in the header. Just don’t do it.

What we do put in header files are:

  • #include statements relevant to the related .c file
  • Global variable declarations
  • Global constants (using #define)
  • Structures we want other programs to use
  • Function prototypes for functions we want other programs to use

Note that we can define functions inside of code files without creating prototypes for them in the headers. These functions can still be executed as part of another function, but the other programs won’t know they exist. It’s really useful for applying object oriented programming principles in C.

An Example Header: person_record.h

The header below defines everything we need to create a basic personnel record. Pay close attention to how we defined a function prototype, and how it matches up exactly with the function in person_record.c


#include <stdio.h>

#define CEO 0
#define PRESIDENT 1
#define VP 2
#define MANAGER 3
#define EMPLOYEE 4
#define INTERN 5

//We'll create a simple personnel record here
struct personnel_record
{
char *first_name;
char *last_name;
int position;

//This right here is a pointer. We'll talk about these in more detail soon.
struct personnel_record *boss;
};

//Below is a Doxygen comment. We can use it to create HTML files to describe our functions, which makes documentation super easy
/**
Create a new record
@param first_name    The first name of the person associated with this record
@param last_name    The last name of that person
@param position        An enumerated value associated with a position in the company
@param boss        A pointer to the record of this person's boss
@return            The created record
**/
struct personnel_record new_record(char *first_name, char *last_name, int position, struct personnel_record *boss);

person_record.c

We almost always name the .c file after its related .h file. This helps make sure we’ve got all the files we wanted (and it makes generating makefiles slightly easier).


#include "person_record.h"

struct personnel_record new_record(char *first_name, char *last_name, int position, struct personnel_record *boss)
{
struct personnel_record thisrecord;
if(boss == NULL)
{
thisrecord.boss = NULL;
thisrecord.position = CEO;
}

else
{
thisrecord.position = position;
thisrecord.boss = boss;
}
thisrecord.first_name = first_name;
thisrecord.last_name = last_name;

return thisrecord;
}

new_record.c

Finally, we create the program that will use our new header file.

[c] #include “person_record.h”

char * Create_String(int input)
{
char * value;
switch (input)
{
case CEO:
value = “CEO”;
break;
case PRESIDENT:
value = &PRESIDENT”;
break;
case VP:
value = "VICE PRESIDENT";
break;
case MANAGER:
value = "MANAGER";
break;
case EMPLOYEE:
value = "EMPLOYEE";
break;
default:
value = "NO VALUE ASSIGNED";
break;
}
return value;
}

int main(int argc, char *argv[])
{
struct personnel_record Jack = new_record("Jack", "Benny", CEO, NULL);
struct personnel_record Bob = new_record("Bob", "Sagat", MANAGER, &Jack);

printf("%s %s %s %sn", Bob.first_name, Bob.last_name, Create_String(Bob.position), Bob.boss->first_name);
return 0;
}
[/code]

There are three possible ways to compile this program. The first is to create one long gcc call, which includes all the .c files we are using. That call looks like this:

gcc -o new_record new_record.c person_record.c

Note: We always arrange the gcc call so that the base libraries are called first. Otherwise, gcc might throw away person_record.c before it finishes compiling new_record.c, which would result in a weird error and compiler failure.

The other way is to compile person_record.c into its object code first, then pass it to gcc. That takes two commands, like this:

gcc -c person_record.c

gcc -o new_record new_record.c person_record.o

The third way is to compile person_record.c into its object code, copy that object code into a library, and pass that library to gcc. This is very useful when you have a lot of .c files to include.

gcc -c person_record.c

ar rc recordlib.a person_record.o

gcc -o new_record new_record.c recordlib.a