Long programs with many many lines of code are a fact of life for all programmers (that and also bugs). Indeed, you can easily spend tens or even hundreds of hours designing, coding and debugging your next confectionary masterpiece of a program that you can call "your own." Inspite of that, there are ways to avoid coding every single line of every program you write, over and over and over again.
You may notice that all of our complete C programs (a completed C program is one that has the 'main' function) and even some of our code snippets (parts of code) have one or more 'include' statements like the following:
#include <conio.h> /* Hardware specific console IO */
These 'include' lines allow you to make use of pre-defined functions, that may have hundreds or thousands of lines of code, within your programs without you having to re-type all those lines that make the code work. For example, by including the 'conio.h' file into your own source code, we were able to make use of functions like 'kbhit()' and 'getch()' like so:
while (kbhit()) getch(); /* Clear the keyboard */
By 'borrowing' functions from other libraries like this, it saves us from having to type and debug the actual code for these functions ourselves, saving a significant amount of time. In addition to that, we also allow the code for these functions to be used in other programs as well. Just include the appropriate header files in our new program and all the library functions for that header are readily available.
This is the kind of convenience that would be ideal for each of the functions we've written so far. The biggest reason that our last program was so large is because we rewrote all the code for every function that we have previously discussed. Now let's look at ways with which we an move our functions into a library that we can reuse in any program that we wish to write.
Okay, let's start with the simple approach. Let's say that we want to put the functionality of the 'SetVideoMode' function into a seperate file. That way we can include its functionality into our new programs without having to re-type the code for it. Let's review the code:
#include <dos.h> /* Required for the int86 functionality */ void SetVideoMode(int mode) { union REGS regs; /* Create register variable */ regs.x.ax = mode; /* Set video mode */ /* Call interrupt to complete the task */ int86(0x10, ®s, ®s); }
These 8 lines of C code are all that is required for the 'SetVideoMode' functionality. With these lines of code, let's save them to a file called 'VMODE.C.' So now, we should have a file named VMODE.C that contains only the code needed to set (and change) video display modes. Now we can use this function in our programs by typing just one line.
#include "vmode.c" /* Note the quotes */
Now we can reuse the code that we saved in the VMODE.C file without having to retype it. In order to provide all the basic necessities that we need for other functions that work in video mode 13h, let's modify the code for VMODE.C to look like this:
#include <dos.h> /* Required for the int86 functionality */ /* Define constants for video modes */ #define TEXT_MODE 0x03 #define VGA256 0x13 /* Define constants for Mode 13h Specifications */ #define ScreenWidth (unsigned)320 #define ScreenHeight (unsigned)200 #define ScreenColors (unsigned)256 /* Set a pointer to the first byte of the video RAM */ unsigned char far *videoMem = (unsigned char far *)0xA0000000L; /* Define the function that changes the video modes */ void SetVideoMode(int mode) { union REGS regs; /* Create register variable */ regs.x.ax = mode; /* Set video mode */ /* Call interrupt to complete the task */ int86(0x10, ®s, ®s); } /* Create macros */ #define SetPixel(X, Y, C) videoMem[Y * 320 + X] = C #define FillScreen(C) _fmemset(videoMem, C, 64000)
Now when we include VMODE.C into our program, we will have a few other constants defined right along with the 'SetVideoMode' function. This is convenient as you will see with this sample program below:
#include <conio.h> /* Required for kbhit() and getch() functionality */ #include <stdlib.h> /* Required for rand() functionality */ #include "vmode.c" /* Import our pre-defined function */ int main(void) { /* Set video to graphics mode */ SetVideoMode(VGA256); while (kbhit()) getch(); /* Clear the keyboard */ /* Set random pixels to random colors */ while (!kbhit()) SetPixel(rand() % ScreenWidth, rand() % ScreenHeight, rand() % ScreenColors); while (kbhit()) getch(); /* Clear the keyboard */ /* Return to text mode */ SetVideoMode(TEXT_MODE); return 0; }
Did you notice something rather odd with the SetPixel
and
FillScreen
functions? The function is not really a function
at all according to how we set it up. They are set up to substitute the
code that makes them work in place of their call. This, of course,
speeds up their execution by eliminating the overhead associated with making
a function call. The main disadvantage of this would be the fact that
the code would be duplicated for every instance of the function call.
This is not too much of a disadvantage for functions that are small like
the SetPixel
and FillScreen
functions.
This type of inline coding is done by the use of macros, they are
defined with the #define
and any ANSI C compiler that is worth
its salt will support the use of macros. In fact, macros are typically
used in the standard headers that come with C. For example, most all
C compilers the function called getchar
from the STDIO
library. The getchar
function is in fact a macro that
translates its call to the code getc(stdin)
. This
means that getchar
is a macro translating its call to
another function call. It is defined in the STDIO.H
file
as so:
#define getchar() getc(stdin)
So every instance of getchar
that's found in a typical C
program is replaced with getc(stdin)
upon compile.
ANSI C compilers can also pass parameters with macros, the putchar
macro is an example of such a macro. It is defined as so:
#define putchar(c) putc(c, stdout)
So if we were to write this line in our source code:
putchar('A'); /* Sends 'A' to standard output */
The C compiler would replace it with this during compile time:
putc('A', stdout); /* Sends 'A' to standard output */
As you can see, this would come quite in handy with the SetPixel
function because it only has one line. We defined the SetPixel
macro like so:
#define SetPixel(X, Y, C) videoMem[Y * 320 + X] = C
Now we can use the code inline, that is, whenever we make a call to SetPixel
like so:
SetPixel(10, 20, 5);
The compiler will replace it during compile time like so:
videoMem[20 * 320 + 10] = 5;
Pretty nifty huh? By using macros, we can virtually eliminate code for the
SetPixel
function from our programs. If the call to the
SetPixel
function is never made in your program, the code for
it is never compiled with your program (eliminating dead code). When the
SetPixel
function is used, the code will run faster by eliminating
the overhead of a function call.
One potential drawback to macros that most C compiler documentation will warn you about is that the parameters passed to macros cannot be checked for proper data types. Unlike functions, that declare their data types at the entry of the function, a macro does no such thing.
/* Functions have a specified return data type and their parameters * are of specified types. */ void SetVideoMode(int mode) { /* Code goes here */ } /* Macros have no specified data types for anything */ #define SetPixel(X, Y, C) videoMem[Y * 320 + X] = C /* And you CANNOT do this... */ #define SetPixel(int X, int Y, int C) videoMem[Y * 320 + X] = C /* Doing so generates a compiler error. */
The compiler cannot check the data types of values passed to macros and perform automatic conversions with their calls. This is not too great of a concern since the compiler will still be able to perform any necessary conversions in the actual code that replaces the macro call. This can, however, lead to hard to track compile errors and possible run time errors if the data type of the passed data are not compatible with the replaced code. This is generally a rare occurence. If you prefer not to use macros in your code, the following source code can be substituted for the VMODE.C code written above.
#include <dos.h> /* Required for the int86 functionality */ /* Define constants for video modes */ #define TEXT_MODE 0x03 #define VGA256 0x13 /* Define constants for Mode 13h Specifications */ #define ScreenWidth (unsigned)320 #define ScreenHeight (unsigned)200 #define ScreenColors (unsigned)256 /* Set a pointer to the first byte of the video RAM */ unsigned char far *videoMem = (unsigned char far *)0xA0000000L; /* Define the function that changes the video modes */ void SetVideoMode(int mode) { union REGS regs; /* Create register variable */ regs.x.ax = mode; /* Set video mode */ /* Call interrupt to complete the task */ int86(0x10, ®s, ®s); } /* The function that sets pixels */ void SetPixel(int X, int Y, unsigned char C) { videoMem[Y * 320 + X] = C; } /* This function sets all pixels to a specified color */ void FillScreen(int C) { _fmemset(videoMem, C, 64000); }
Another way to write inline functions is with the inline
keyword. The inline
keyword is a C++ modifier that
when used with a normal function declaration, makes the function into an
inline function. Inline functions are intended to promote code efficiency
when used with small functions that are associated with, or a member of, a
C++ class. They can, however, be used with any function regardless of
whether or not they are associated with a class or not. If you are using
a C++ compiler, every function we've written so far can be made into an
inline function (with some exceptions depending on your C++ compiler).
The fact that the inline
keyword is a C++ modifier means
that a C++ compiler is required to compile programs that employ their use.
Most C++ compilers on the market can compile in both C++ and C modes and they
have to be set in C++ compiling mode before the compiler will recognize the
inline
keyword. Generally this is done by naming the
source file with a CPP extension instead of a C extension. Many C++
compilers that are backwards compatible with C can also be set to compile in
this mode all the time (regardless of source file extension). To make
a function an inline function, declare it (or its prototype) as so:
// Acceptable... inline void SetPixel(int X, int Y, unsigned char C); // Better, the code and the definition are together. // Inline functions must be declared in the header files and // cannot become a part of the object code libraries. inline void SetPixel(int X, int Y, unsigned char C) { videoMem[Y * 320 + X] = C; }
Inline functions work like macros in the fact that the code within the function replaces their function call. The compiler can also determine the data types at compile time, making them superior to macros. For more information on the use of inline functions and C++ coding, you can check out Borland's C++ FAQ page.