Guides:C/C Crash Course/Compiler Directives and Header Files

From CoderGuide

Jump to: navigation, search

Back to TOC

Compiler Directives and Header Files

Before we go on, let's talk a little bit about the #include compiler directive, and compiler directives in general. Compiler directives aren't part of your C program, that is, they aren't converted into machine code later. They are processed by the compiler and, depending on which one's you use, will alter the way the compiler processes your code. All compiler directives begin on a line by themselves, and begin with the the pound sign '#'.

The #include compiler directive tells the compiler to literally include another file in your program as if it were just one continuous file. It takes two forms:

#include <filename.h>
#include "filename.h"

The difference between the two is subtle. The first one will look only in the include file search path for filename.h, which normally only includes those include files required by the standard, and other system libraries. The second one will look for the file, first in the current directory, and then in the library search path. When using your own libraries, you'll want to use the second form and put the include file in the same directory as your file, or within the program's source directory tree.

The #define compiler directive takes the following forms:

#define LABEL blablabla
#define MACRO(x) x*2

What this tells the compiler is, on every occurrence of LABEL, replace LABEL with whatever the text following it is in your program (the exception is in other compiler directive statements, and in quoted text making up strings.

The macro form isn't a function, even though it looks like one. The parameters you give to a macro tell the compiler to replace those labels in the macro with those parameters and then insert in in your program. So if I typed MACRO(bee), then it would replace MACRO(bee) with bee*2 in my program. Macros have their place, but it is better to use functions whenever possible.

One use for a macro might be, to simplify calling the fgets() function to get input from stdio. Here's and example:

#define fgetsstd(string,size) fgets(string,size,stdin)

Now, you only have to use two parameters to use the fgets() function (you'd use fgetsstd() instead, fgets() was not redefined), because this macro inserts the last one, stdin, for you when you use it. Few programmers make use of macros, but they do have there place, every now and then. They are also a good thing to have in header files, and requires no additional code.

I've used macros most when I want to make sure my program can compile on different systems where a few things might be a little different from compiler to compiler. One example is a 1990 C++ compiler, and a C++ compiler that follows the new 2000 ISO standard. They can also be used to make programs compile, and work, on different operating systems. For instance, Windows, Mac OS X, and other Unix systems keep user files in different places. You could alternately configure various defined and constant values to account for these differences.

There are a few extra compiler directives that you will want to use: #ifndef and #endif. These are conditional compiler directives (there are also #if and #else directives) . If the the statement is evaluated to be true, then the code between the #ifndef and #endif will be processed by the compiler, otherwise it will be ignored just as comments in your program are ignored by the compiler. There are many other compiler directives, but we won't be covering them here. Almost any respectable book on C will cover most of these compiler directives.

By using these tags, can ensure that your header file is only processed once by the compiler (processing it multiple times in the same code will certainly result in an error), and thus is a good practice. Let's make a header file for our sgets() function we showed above (save it as safeio.h):

 safeio.h -- Safer I/O functions
char *sgets(char *s, int size)
        Reads a string of size [size] and stores it in [s], returns
        a pointer to the read string.  The trailing 
long sgeti()
        Reads in a integer from stdin.
double sgetf()
        Reads in a float point number from stdin.
char sgetc()
        Gets a single character from stdin, and removes all
        trailing characters from stdin to the first newline in the
char spromptc(char *prompt)
        Prints [prompt] to stdout, and reads in a single character
        from the stdin, and removes all training characters to
        the first newline from the buffer.
char *sprompts(char *prompt,char *string,int size)
        Prints [prompt] to stdout, and read in a string of size 
        [size] and stores it in [string]. Returns a pointer
        to the string stored string.
#ifndef __SAFEIO_H__ /*is __SAFEIO_H__ defined? */
/*Nope, so lets process the rest of the file*/
An empty definition only to serve as a indicator that this
file has already been processed by the compiler at least once
before. The label name should contain the name of the header
file.  If it was "zippy.h"
then "__ZIPPY_H__" would be the label you should use. This 
convention avoids confusion, and possible conflicts later.
We can't use periods in labels, so we use the underscore instead.  
#define __SAFEIO_H__ 
/*Since our library requires functions in the stdio and stdlib
libraries, then then we should also include the required header 
file here.*/
#include <stdio.h>
#include <stdlib.h>
/*our function prototypes*/
char *sgets(char *,int);
long sgeti();
double sgetf();
char sgetc();
char spromptc(char *);
char *sprompts(char *, char *, int);
#endif /*forget this, and the rest of your program won't compile!*/

That's it! Nothing to it!

If you wanted to have a global variable accessible by other programs that use your library, you need to declare that variable in your source file, and again in your header file. In your header file, you prefix the declaration with the extern keyword. This is the only way to let the compiler know that the variable exists outside of the source file you defined it in. So, if in your source file, you have a global variable you defined as:

const char *zippy_version="ZippyLIB Version 10.7";

You would add the following to your header file, if you wanted programs that use your library to access it:

extern const char *zippy_version;

extern tells the compiler that the data type is defined outside of this file, and will be linked to separately.

When writing header files be careful: If you mess up something in your header file, your compiler will spit out a screen full of errors. So, if that happens, and you know the problem isn't in your source code file, then check your header file for syntax errors.

One thing you can be sure of whenever you write your own programs: You're going to make mistakes somewhere, even in small programs. Like, when writing the code in this section, I had to go back and fix a few things. The more you program, the faster you'll get at finding errors, and fixing them, so don't get discouraged if it takes you hours to find one little character you missed.

Personal tools