Function prototypes for library functions supplied with the C compiler, and standard macros are declared in header files.
The ANSI standard on the C programming language lists the
following header files;
Header file | Description |
assert.h | Defines the assert debugging macro |
ctype.h | Character classification and conversion macros |
errno.h | Constant mnemonics for error codes |
float.h | Defines implementation specific macros for dealing with floating point mathematics |
limits.h | Defines implementation specific limits on type values |
locale.h | Country specific parameters |
math.h | Prototypes for mathematics functions |
setjmp.h | Defines typedef and functions for setjmp/longjmp |
signal.h | Constants and declarations for use by signal() and raise() |
stdarg.h | Macros for dealing with argument lists |
stddef.h | Common data types and macros |
stdio.h | Types and macros required for standard I/O |
stdlib.h | Prototypes of commonly used functions and miscellany |
string.h | String manipulation function prototypes |
time.h | Structures for time conversion routines |
The ANSI standard on C includes a macro function for debugging
called assert(). This expands to an if() statement, which if it returns true
terminates the program and outputs to the standard error stream a message
comprised of:
Assertion failed: <test>, file <module>, line <line number>
Abnormal program termination
For example, the following program accidentally assigns a zero
value to a pointer!
#include <stdio.h>
#include <assert.h>
main()
{
/* Demonstration of assert */
int *ptr;
int x;
x = 0;
/* Whoops! error in this line! */
ptr = x;
assert(ptr != NULL);
}
When run, this program terminates with the message:
Assertion failed: ptr != 0, file TEST.C, line 16
Abnormal program termination
When a program is running okay, the assert() functions can be
removed from the compiled program by simply adding the line;
#define NDEBUG
before the #include <assert.h> line. Effectively the assert functions are commented out in the preprocessed source before compilation, this means that the assert expressions are not evaluated, and thus cannot cause any side effects.
Floating point numbers are decimal fractions, decimal
fractions do not accurately equate to normal fractions as not every number will
divide precisely by ten. This creates the potential for rounding errors in
calculations that use floating point numbers. The following program illustrates
one such example of rounding error problems;
#include <stdio.h>
void main()
{
float number;
for(number = 1; number > 0.4; number -= 0.01)
printf ("\n%f",number);
}
At about 0.47 (depending upon the host computer and compiler)
the program starts to store an inaccurate value for 'number'.
This problem can be minimised by using longer floating point
numbers, doubles or long doubles that have larger storage space allocated to
them. For really accurate work though, you should use integers and only convert
to a floating point number for display. You also should notice that most C
compilers default floating point numbers to 'doubles' and when using 'float'
types have to convert the double down to a float!
When a system error occurs within a program, for example when
an attempt to open a file fails, it is helpful to the program's user to display
a message reporting the failure. Equally it is useful to the program's developer
to know why the error occurred, or at least as much about it as possible. To
this end the ANSI standard on C describes a function, perror(), which has the
prototype;
void perror(const char *s);
and is used to display an error message. The program's own
prefixed error message is passed to perror() as the string parameter. This error
message is displayed by perror() followed by the host's system error separated
by a colon. The following example illustrates a use for perror();
#include <stdio.h>
void main()
{
FILE *fp;
char fname[] = "none.xyz";
fp = fopen(fname,"r");
if(!fp)
perror(fname);
return;
}
If the fopen() operation fails, a message similar to;
none.xyz: No such file or directory
is displayed.
You should note, perror() sends its output to the predefined
stream 'stderr
', which is usually the host computer's display unit.
perror() finds its message from the host computer via the
global variable 'errno' that is set by most, but not all system functions.
Unpleasant errors might justify the use of abort(). abort() is
a function that terminates the running program with a message;
"Abnormal program termination"
and returns an exit code of 3 to the parent process or
operating system.
The IBM PC DOS operating system provides a user amendable
critical error handling function. This function is usually discovered by
attempting to write to a disk drive that does not have a disk in it, in which
case the familiar;
Not ready error writing drive A
Abort Retry Ignore?
Message is displayed on the screen. Fine when it occurs from
within a DOS program, not so fine from within your own program!
The following example program shows how to redirect the DOS
critical error interrupt to your own function;
/* DOS critical error handler test */
#include <stdio.h>
#include <dos.h>
void interrupt new_int();
void interrupt (*old_int)();
char status;
main()
{
FILE
*fp;
old_int = getvect(0x24);
/* Set critical error handler to my function */
setvect(0x24,new_int);
/* Generate an error by not having a disc in drive A */
fp = fopen("a:\\data.txt","w+");
/* Display error status returned */
printf
("\nStatus == %d",status);
}
void interrupt new_int()
{
/* set global error code */
status = _DI;
/* ignore error and return */
_AL = 0;
}
When the DOS critical error interrupt is called, a status
message is passed in the low byte of the DI register. This message is one of;
Code | Meaning |
00 | Write-protect error |
01 | Unknown unit |
02 | Drive not ready |
03 | Unknown command |
04 | Data error, bad CRC |
05 | Bad request structure length |
06 | Seek error |
07 | Unknown media type |
08 | Sector not found |
09 | Printer out of paper |
0A | Write error |
0B | ReadRead error |
0C | General failure |
Your critical error interrupt handler can transfer this status message into a
global variable, and then set the result message held in register AL to one of;
Code | Action |
00 | Ignore error |
01 | Retry |
02 | Terminate program |
03 | Fail (Available with DOS 3.3 and above) |
If you choose to set AL to 02, terminate program, you should ensure ALL files
are closed first since DOS will terminate the program abruptly, leaving files
open and memory allocated, not a pretty state to be in!
The example program shown returns an ignore status from the critical error interrupt, and leaves the checking of any errors to the program itself. So, in this example after the call to fopen() we could check the return value in fp, and if it reveals an error (NULL in this case) we could then check the global variable status and act accordingly, perhaps displaying a polite message to the user to put a disk in the floppy drive and ensure that the door is closed.
The following is a practical function for checking whether a specified disc drive can be accessed. It should be used with the earlier critical error handler and global variable 'status'.
int DISCOK(int drive)
{
/* Checks for whether a disc can be read */
/* Returns false (zero) on error */
/* Thus if(!DISCOK(drive)) */
/* error(); */
unsigned char buffer[25];
/* Assume okay */
status = 0;
/* If already logged to disc, return okay */
if ('A' + drive == diry[0])
return(1);
/* Attempt to read disc */
memset(buffer,0,20);
sprintf(buffer,"%c:$$$.$$$",'A'+drive);
_open(buffer,O_RDONLY);
/* Check critical error handler status */
if (status == 0)
return(1);
/* Disc cannot be read */
return(0);
}
Casting, Interrupts, Pointers to Functions, and Prototyping.
Links: Home C Programming Guide C++ programming Guide