© German Gonzalez-Morris and Ivor Horton 2020
G. Gonzalez-Morris, I. HortonBeginning Chttps://doi.org/10.1007/978-1-4842-5976-4_13

13. The Preprocessor and Debugging

German Gonzalez-Morris1  and Ivor Horton2
(1)
Santiago, Chile
(2)
STRATFORD UPON AVON, UK
 

In this chapter, you’ll delve deeper into the capabilities of the preprocessor, and I’ll explain how you can use it to help find bugs in your code. You’ll also explore some library functions that complement some of the standard capabilities of the preprocessor.

In this chapter, you’ll learn
  • More about the preprocessor and its operation

  • How to write preprocessor macros

  • What standard preprocessor macros are available

  • What logical preprocessor directives are and how you can use them

  • What conditional compilation is and how you can apply it

  • More about the debugging methods that are available to you

  • How you get the current date and time at runtime

Preprocessing

As you are certainly aware by now, preprocessing of your source code occurs before it’s compiled to machine instructions. The preprocessing phase can execute a range of service operations specified by preprocessing directives, which are identified by the # symbol as the first character of each preprocessor directive. The preprocessing phase provides an opportunity for manipulating and modifying your C source code prior to compilation. Once the preprocessing phase is complete and all directives have been analyzed and executed, all such preprocessing directives will no longer appear in the source code. The compiler begins the compile phase proper, which generates the machine code equivalent of your program.

You’ve already used preprocessor directives in all the examples so far, and you’re familiar with both the #include and #define directives . There are other directives that add considerable flexibility to the way in which you write your programs. Keep in mind as you proceed that all these are preprocessing operations that occur before your program is compiled. They modify the set of statements that constitute your program. They aren’t involved in the execution of your program at all.

Including Header Files

You’re completely familiar with statements such as this:
#include <stdio.h>
This brings the contents of the standard library header file that supports input/output operations into your program. This is a particular case of the general statement for including standard library headers into a file:
#include <standard_library_file_name>

Any standard library header file name can appear between the angled brackets. If you include a header file that you don’t use, the only effect, apart from slightly confusing anyone reading the program, is to extend the compilation time.

Note

A file introduced into your program by an #include directive may also contain #include directives. If so, preprocessing will deal with these #include directives in the same way and continue replacing such directives with the contents of the corresponding file until there are no #include directives in the program.

Defining Your Own Header Files

You can define your own header files, usually with the extension .h. You can give the file whatever name you like within the constraints of the operating system. In theory you don’t have to use the extension .h for your header files, although it’s a convention commonly adhered to by most programmers in C, so I strongly recommend you stick to it too.

Header files should not include implementation, by which I mean executable code. You create header files to contain declarations, not function definitions or initialized global data. All your function definitions and initialized global variables are placed in source files with the extension .c. You can place function prototypes, struct type definitions, symbol definitions, extern statements, and typedefs in a header file. A very common practice is to create a header file containing the function prototypes and type declarations for a program. These can then be managed as a separate unit and included at the beginning of any source file for the program. You need to avoid duplicating information if you include more than one header file in a source file. Duplicate code will often cause compilation errors. You’ll see later in this chapter in the “Conditional Compilation” section how you can ensure that any given block of code will appear only once in your program, even if you inadvertently include it several times.

You can include your own files into a program source with a slightly different #include statement. A typical example might be this:
#include "myfile.h"

This statement will introduce the contents of the file named between double quotes into the program in place of the #include directive. The contents of any file can be included in your program by this means, not just header files. You simply specify the name of the file between quotes, as shown in the example.

The difference between enclosing the file name between double quotes and using angled brackets lies in the process used to find the file. The precise operation is compiler dependent and will be described in your compiler documentation, but usually the angled brackets form will search a default header file directory that is the repository for standard header files for the required file, whereas the double quotes form will search the current source directory first and then search the default header file directory if the file was not in the current directory.

Managing Multiple Source Files

A complex program is invariably comprised of multiple source files and header files. In theory you can use an #include directive to add the contents of another .c source file to the current .c file, but it’s not usually necessary or even desirable. You should only use #include directives in a .c file to include header files. Of course, header files can and often do contain #include directives to include other header files into them.

Each .c file in a complex program will typically contain a set of related functions. The preprocessor inserts the contents of each header identified in an #include directive before compilation starts. The compiler creates an object file from each .c source file. When all the .c files have been compiled, the object files are combined into an executable module by the linker.

If your C compiler has an interactive development environment with it, it will typically provide a project capability, where a project contains and manages all the source and header files that make up the program. This usually means you don’t have to worry too much about where files are stored for the stages involved in creating an executable. The development environment takes care of it. For larger applications though, it’s better still if you create a decent folder structure yourself instead of letting the IDE put all the files in the same folder.

External Variables

With a program that’s made up of several source files, you’ll often want to use a global variable that’s defined in another file. You can do this by declaring the variable as external to the current source file using the extern keyword. For example, suppose you have global variables defined in another file (which means outside of any of the functions) by these statements:
int number = 0;
double in_to_mm = 2.54;
In a source file in which you want to access these, you can specify that these variable names are external by using these statements:
extern int number;
extern double in_to_mm;

These statements don’t create these variables—they just identify to the compiler that these names are defined elsewhere, and this assumption about these names should apply to the rest of this source file. The variables you specify as extern must be declared and defined somewhere else in the program, usually in another source file. If you want to make these external variables accessible to all functions within the current file, you should declare them as external at the very beginning of the file, prior to any of the function definitions. With programs consisting of several files, you could place all initialized global variables at the beginning of one file and all the extern statements in a header file. The extern statements can then be incorporated into any program file that needs access to these variables by using an #include statement for the header file.

Note

Only one definition of each global variable is allowed. Of course, global variables may be declared as external in as many files as necessary.

Static Functions

By default, all the functions in a source file are implicitly extern, which means they are visible in all object files when they are processed by the linker. This is essential for the linker to be able to bind all the code in several object files into an executable module. However, sometimes you may not want this to be the case. You can ensure that a function is only visible within the source file in which you define it by declaring it as static, for example:
static double average(double x, double y) { return (x + y) / 2.0; }

This function can only be called in the .c file in which this definition appears. Without the static keyword, the function could be called from any function in any of the source files that make up the program.

Note

You can apply the static keyword in a function prototype, and the effect is the same.

Substitutions in Your Program Source Code

You are familiar with preprocessor directives for replacing symbols in your source code before it is compiled. The simplest kind of symbol substitution is one you’ve already seen. For example, the preprocessor directive to substitute the string for a specified numeric value, wherever the character string PI occurs, is as follows:
#define PI 3.14159265
Although the identifier PI looks like a variable, it is not a variable and has nothing to do with variables. PI is a token, rather like a voucher, that is exchanged for the sequence of characters specified in the #define directive during the preprocessing phase. When your program is ready to be compiled after preprocessing has been completed, the string PI will no longer appear, having been replaced by its definition wherever it occurs in the source file. The general form of this sort of preprocessor directive is the following:
#define identifier sequence_of_characters
Here, identifier conforms to the usual definition of an identifier in C: any sequence of letters and digits, the first of which is a letter, and underline characters count as letters. Note that sequence_of_characters, which is the replacement for identifier, is any sequence of characters, not just digits. It’s easy to make this sort of typographical error:
#define PI 3,14159265

This is perfectly correct as a preprocessor directive but is sure to result in compiler errors here.

A very common use of the #define directive is to define array dimensions by way of a substitution to allow a number of array dimensions to be determined by a single token. Only one directive in the program then needs to be modified to alter the dimensions of a number of arrays in the program. This helps considerably in minimizing errors when such changes are necessary, as shown in the following example:
#define MAXLEN 256
char *buffer[MAXLEN];
char *str[MAXLEN];

The dimensions of both arrays can be changed by modifying the single #define directive, and of course the array declarations that are affected can be anywhere in the program file. The advantages of this approach in a large program involving dozens or even hundreds of functions should be obvious. Not only is it easy to make a change but using this approach also ensures that the same value is being used throughout a program. This is especially important with large projects involving several programmers working together to produce the final product.

Of course, you can also define a value such as MAXLEN as a const variable:
const size_t MAXLEN = 256;

The difference between this approach and using the #define directive is that MAXLEN here is no longer a token but is a variable of a specific type with the name MAXLEN. The MAXLEN in the #define directive does not exist once the source file has been preprocessed because all occurrences of MAXLEN in the code will be replaced by 256. You will find that the preprocessor #define directive is often a better way of specifying array dimensions because an array with a dimension specified by a variable, even a const variable, is likely to be interpreted as a variable-length array by the compiler.

I used numerical substitutions in the previous two examples, but as I said, you’re in no way limited to this. You could, for example, write the following:
#define Black White

This will cause any occurrence of Black in your program to be replaced with White. The sequence of characters that is to replace the token identifier can be anything at all. The preprocessor will not make substitutions inside string literals though.

Macros

A macro is another preprocessor capability that is based on the ideas implicit in the #define directive examples you’ve seen so far, but it provides greater flexibility by allowing what might be called multiple parameterized substitutions . This involves substitution of a sequence of characters for a token identifier, where the substitution string can contain parameters that may be replaced by argument values wherever the parameters appear in the substitution sequence. An example will make this easier to understand:
#define Print(My_var) printf_s("%d", My_var)
My_var is a parameter name, for which you can specify a string. This directive provides for two levels of substitution. An occurrence of Print(My_var) in your code will be replaced by the string immediately following it and with whatever argument you specify for My_var. You could, for example, write the following:
Print(ival);
This will be converted during preprocessing to this statement:
printf_s("%d", ival);

You could use this directive to specify a printf_s() function call to output the value of an integer at various points in your program. A common use for this kind of macro is to provide a simple representation of a complicated function call in order to enhance the readability of a program.

Macros That Look Like Functions

The general form of the kind of substitution directive just discussed is the following:
#define macro_name( list_of_identifiers ) substitution_string
The list of identifiers separated by commas appears between parentheses following macro_name, and each of these identifiers can appear one or more times in the substitution string. This enables you to define more complex substitutions. To illustrate how you use this, you can define a macro for producing a maximum of two values with the following directive:
#define max(x, y) x>y ? x : y
You can then put the statement in the program:
int result = max(myval, 99);
This will be expanded during preprocessing to produce the following code:
int result = myval>99 ? myval : 99;
It’s important to be conscious of the substitution that is taking place and not to assume that this is a function. You can get some strange results otherwise, particularly if your substitution identifiers include an explicit or implicit assignment. For example, the following modest extension of the previous example can produce an erroneous result:
int result = max(++myval, 99);
The substitution process will generate this statement:
int result = ++myval>99 ? ++myval : 99;
The consequence of this is that if the value of myval is larger than or equal to 99, myval will be incremented twice. Note that it does not help to use parentheses in this situation. Suppose you write this statement:
int result = max((++myval), 99);
Preprocessing will convert this to
int result = (++myval)>99 ? (++myval) : 99;
The way to get the correct result for result is to write
++myval;
int result = max(myval, 99);
You need to be very cautious if you’re writing macros that generate expressions of any kind. In addition to the multiple substitution trap you’ve just seen, precedence rules can also catch you out. A simple example will illustrate this. Suppose you write a macro for the product of two parameters:
#define product(m, n)  m*n
You then try to use this macro with the following statement:
int result = product(x, y + 1);
Of course everything works fine so far as the macro substitution is concerned, but you don’t get the result you want, as the macro expands to this:
int result = x*y + 1;
It could take a long time to discover that you aren’t getting the product of the two arguments because there’s no external indication of an error. There’s just a more or less erroneous value propagating through the program. The solution is very simple. If you use macros to generate expressions, put parentheses around every parameter occurrence and the whole substitution string. So you should rewrite the example as follows:
#define product(m, n)  ((m)*(n))

Now everything will work as it should. The inclusion of the outer parentheses may seem excessive, but because you don’t know the context in which the macro expansion will be placed, it’s better to include them. If you write a macro to sum its parameters, you will easily see that without the outer parentheses, there are many contexts in which you will get a result that’s different from what you expect. Even with parentheses, expanded expressions that repeat a parameter, such as the one you saw earlier that uses the conditional operator, will still not work properly when the argument involves the increment or decrement operator.

Strings As Macro Arguments

String constants are a potential source of confusion when they are used with macros. The simplest string substitution is a single-level definition such as the following:
#define MYSTR "This string"
Suppose you now write the statement
printf_s("%s", MYSTR);
This will be converted during preprocessing into the statement
printf_s("%s", "This string");
This should be what you are expecting. You couldn’t use the #define directive without the quotes in the substitution sequence and expect to be able to put the quotes in your program text instead. For example, suppose you write the following:
#define  MYSTR  This string
  ...
printf_s("%s", "MYSTR");

There will be no substitution for MYSTR in the printf_s() function argument in this case. Anything in quotes in your program is assumed to be a literal string, so it won’t be analyzed during preprocessing.

There’s a special way of specifying that the substitution for a macro argument is to be implemented as a string. For example, you could specify a macro to display a string using the function printf_s() as follows:
#define PrintStr(arg) printf_s("%s", #arg)
The # character preceding the appearance of the arg parameter in the macro expansion indicates that the argument is to be surrounded by double quotes when the substitution is generated. Suppose you write the following statement in your program:
PrintStr(Output);
This will be converted during preprocessing to
printf_s("%s", "Output");

You may be wondering why this apparent complication has been introduced into preprocessing. Well, without this facility, you wouldn’t be able to include a variable string in a macro definition at all. If you were to put the double quotes around the macro parameter, it wouldn’t be interpreted as a variable; it would be merely a string with quotes around it. On the other hand, if you put the quotes in the macro expansion, the string between the quotes wouldn’t be interpreted as an identifier for a parameter; it would be just a string constant. So what might appear to be an unnecessary complication at first sight is actually an essential tool for creating macros that allows strings between quotes to be created.

A common use of this mechanism is for converting a variable name to a string, such as in this directive:
#define show(var) printf_s(#var" = %d ", var);
If you now write
show(number);
this will generate the statement
printf_s("number"" = %d ", number);
The strings "number" and " = %d " will be joined by the compiler to form the format string that will be used for the output:
"number = %d "
You can also generate a substitution that would allow you to display a string with double quotes included. Assuming you’ve defined the macro PrintStr as shown previously and you write the statement
PrintStr("Output");
this will be preprocessed into the statement
printf_s("%s", ""Output"");

This is possible because the preprocessing phase is clever enough to recognize the need to put " at each end to get a string that includes double quotes to be displayed correctly.

Joining Two Arguments in a Macro Expansion

There are times when you may wish to join two or more macro arguments together in a macro expansion with no spaces between them. Suppose you try to define a macro to do this as follows:
#define join(a, b) ab
This can’t work in the way you need it to. The definition of the expansion will be interpreted as ab, not as the parameter a followed by the parameter b. If you separate them with a space, the result will be separated with a space, which isn’t what you want either. The preprocessor provides another operator to solve this problem. The solution is to specify the macro like this:
#define join(a, b) a##b
The presence of the operator that consists of two hash characters, ##, serves to separate the parameters and to indicate that the arguments for the parameters are to be joined. For example, suppose you write this:
strnlen_s(join(var, 123), sizeof(join(var,123)));
This will result in the following statement:
strnlen_s(var123, sizeof(var123));

This might be applied to synthesizing a variable name for some reason or when generating a format control string from two or more macro parameters.

Preprocessor Directives on Multiple Lines

A preprocessor directive must be a single logical line, but this doesn’t prevent you from using the statement continuation character, which is just a backslash, . In doing so, you can span a directive across multiple physical lines, using the continuation character to designate those physical lines as a single, logical line.

You could write the following:
#define min(x, y)
               ((x)<(y) ? (x) : (y))

Here, the directive definition continues on the second physical line with the first nonblank character found, so you can position the text on the second line to wherever you feel looks like the nicest arrangement. Note that the must be the last character on the line, immediately before you press Enter. The result is seen by the compiler as a single, logical line.

Logical Preprocessor Directives

The previous example you looked at appears to be of limited value, because it’s hard to envision when you would want to simply join var to 123. After all, you could always use one parameter and write var123 as the argument. One aspect of preprocessing that adds considerably more potential to the previous example is the possibility for multiple macro substitutions where the arguments for one macro are derived from substitutions defined in another. In the previous example, both arguments to the join() macro could be generated by other #define substitutions or macros. Preprocessing also supports directives that provide a logical if capability, which vastly expands the scope of what you can do during the preprocessing phase.

Conditional Compilation

The first logical directive I’ll discuss allows you to test whether an identifier exists as a result of having been created in a previous #define directive. It takes the following form:
#if defined identifier
// Statements...
#endif

If the specified identifier is defined by a #define directive prior to this point, statements that follow the #if are included in the program code until the directive #endif is reached. If the identifier isn’t defined, the statements between the #if and the #endif will be skipped. This is the same logical process you use in C programming, except that here you’re applying it to the inclusion or exclusion of program statements in the source file.

You can also test for the absence of an identifier. In fact, this tends to be used more frequently than the form you’ve just seen. The general form of this directive is
#if !defined identifier
// Statements...
#endif

Here the statements following the #if down to the #endif will be included if identifier hasn’t previously been defined. This provides you with a method of avoiding duplicating functions, or other blocks of code and directives, in a program consisting of several files or ensuring bits of code that may occur repeatedly in different libraries aren’t repeated when the #include statements in your program are processed.

The mechanism is simply to top and tail the block of code you want to avoid duplicating as follows:
#if !defined block1
  #define block1
  /* Statements you do not          */
  /* want to occur more than once.  */
#endif

If the identifier block1 hasn’t been defined, the sequence of statements following the #define directive for block1 will be included and processed, and the identifier block1 will be defined. Any subsequent occurrence of this directive to include the same group of statements won’t include the code because the identifier block1 now exists.

The #define directive for block1 doesn’t need to specify a substitution value in this case. For the conditional directives to operate, it’s sufficient for block1 to appear in a #define directive without a substitution string. You can now include this block of code anywhere you think you might need it, with the assurance that it will never be duplicated within a program. The preprocessing directives ensure this can’t happen.

This is how you ensure that the contents of a header file cannot be included more than once into a source file. You just structure all your header files like this:
// MyHeader.h
#if !defined MYHEADER_H
  #define MYHEADER_H
  // All the statements in the file...
#endif

With this arrangement, it is impossible for the contents of MyHeader.h to appear more than once in a source file.

Note

You should always protect code in your own header files in this way.

Testing for Multiple Conditions

You aren’t limited to testing for the existence of just one identifier with the #if preprocessor directive. You can use logical operators to test if multiple identifiers have been defined, for example:
#if defined block1 && defined block2
 // Statements...
#endif

This will evaluate to true if both block1 and block2 have previously been defined, so the code that follows such a directive won’t be included unless this is the case. You can use the || and ! operators in combination with && if you really want to go to town.

Undefining Identifiers

A further flexibility you have with preprocessor directives is the ability to undefine an identifier you’ve previously defined. This is achieved using a directive such as
 #undef block1

If block1 was previously defined, it is no longer defined after this directive. The ways in which these directives can all be combined to useful effect are only limited by your own ingenuity.

There are alternative ways of writing these directives that are slightly more concise. You can use whichever of the following forms you prefer. The directive #ifdef block is the same as the #if defined block. And the directive #ifndef block is the same as the #if !defined block.

Testing for Specific Values for Identifiers

You can use a form of the #if directive to test the value of a constant expression. If the value of the constant expression is nonzero, the following statements down to the next #endif are included in the program code. If the constant expression evaluates to zero, the following statements down to the next #endif are skipped. The general form of the #if directive is:
#if constant_expression
This is most frequently applied to test for a specific value being assigned to an identifier by a previous preprocessing directive. You might have the following sequence of statements, for example:
#if CPU == Intel_i7
  printf_s("Performance should be good. " );
#endif

The printf_s() statement will be included in the program here only if the identifier CPU has been defined as Intel_i7 in a previous #define directive.

Multiple-Choice Selections

To complement the #if directives, you have the #else directive. This works exactly the same way as the else statement does in that it identifies a group of directives to be executed or statements to be included if the #if condition fails, for example:
#if CPU == Intel_i7
  printf_s("Performance should be good. " );
#else
  printf_s("Performance may not be so good. " );
#endif

In this case, one or the other of the printf_s() statements will be included, depending on whether or not CPU has been defined as Intel_i7.

The preprocessing phase also supports a special form of the #if for multiple-choice selections, in which only one of several choices of statements for inclusion in the program is required. This is the #elif directive, which has the general form:
#elif constant_expression
Here’s an example using this:
#define US 0
#define UK 1
#define France 2
#define Germany 3
#define Country US
#if Country == US || Country == UK
  #define Greeting "Hello."
#elif Country == France
  #define Greeting "Bonjour."
#elif Country == Germany
  #define Greeting "Guten Tag."
#endif
printf_s("%s ", Greeting );
        #if Country == US
           #define Currency "Dollar."
        #elif Country =WrongExpression= UK
           #define Currency "Pound."
        #elif Country == France
           #define Currency "Euro."
        #elif Country == Germany
           #define Currency "Euro."
        #endif
    printf_s("%s ", Currency);

With this sequence of directives, the output of the printf_s() statement will depend on the value assigned to the identifier Country, in this case US.

We need to be careful about these evaluations that if the first expression evaluates to true, then the rest of expressions will not be evaluated at all; therefore, we may have invalid expressions in our source code, but it will compile successfully. This behavior was clarified in C17. We can see in the preceding example, the second expression is wrong on purpose, and currency will be printed as Dollar.

Standard Preprocessing Macros

There are usually a considerable number of standard preprocessing macros defined, which you’ll find described in your compiler documentation. I’ll mention those that are of general interest and that are available in a conforming implementation.

You can always obtain the name of any function in the code that represents the function body by using the identifier __func__ , for example:
#include <stdio.h>
void print_the_name(void)
{
  printf("%s was called. ", __func__);
}
This function just outputs its own name within the format string, so the output will be
print_the_name was called.

The __DATE__ macro generates a string representation of the date in the form Mmm dd yyyy when it’s invoked in your program. Here Mmm is the month in characters, such as Jan, Feb, and so on. The pair of characters dd is the day in the form of a pair of digits 1–31, where single-digit days are preceded by a space. Finally, yyyy is the year as four digits—2012, for example.

A similar macro, __TIME__ , provides a string containing the value of the time when it’s invoked, in the form hh:mm:ss, which is evidently a string containing pairs of digits for hours, minutes, and seconds, separated by colons. Note that the time is when the compiler is executed, not when the program is run.

You could use this macro to record in the output when your program was last compiled with this statement:
printf_s("Program last compiled at %s on %s ", __TIME__, __DATE__ );
Executing this statement will produce output similar to the following:
Program last compiled at 13:47:02 on Nov 24 2012

Note that both __DATE__ and __TIME__ have two underscore characters at the beginning and the end. Once the program containing this statement is compiled, the values that will be output are fixed until you compile it again. On each execution of the program, the time and date that it was last compiled will be output. Don’t confuse these macros with the time() function, which I’ll discuss later in this chapter in the section “Date and Time Functions.”

The __FILE__ macro represents the name of the current source file as a string literal. This is typically a string literal comprising the entire file path, such as "C:\Projects\Test\MyFile.c".

The __LINE__ macro results in an integer constant corresponding to the current line number. You can use this in combination with the __FILE__ macro to identify where in the source code a particular event or error has been detected, for example:
  if(fopen_s(&pfile, filename, "rb"))                 // Open for binary read
  {
    fprintf_s(stderr, "Failed to open file in %s line %d ", __FILE__, __LINE__);
    return -1;
  }

If fopen_s() fails, there will be a message specifying the source file name and the line number within the source file where the failure occurred.

_Generic Macro

Since C11, _Generic was added; thus, it can have dynamic type macros at compilation time. Hence it is being introduced a more flexible typing (that is new in C) for macros. This new feature behaves like a function. Well, here there is another vision about macros vs. functions and the trade-off between both approaches).

In the signature, we can find the expression and then several comma-separated associations with the corresponding value and last a default value. We can see more clearly in the following example:
// Program 13.1b _Generic macro example
#include <stdio.h>
#include <math.h>
#define custom_exp(x) _Generic((x),
   double: exp,
   float: expf,
   long double: expl,
   default: clone
)(x)
//for default type:
int clone(int a) {
   return a;
}
int main(void)
{
   int i = 2;
   double d = 1;
   float f = 1;
   long double ld = 1;
   printf("double %f ", custom_exp(d));
   printf("float %f ", custom_exp(f));
   printf("long double %Lf ", custom_exp(ld));
   printf("default %d ", custom_exp(i));
   return 0;
}

As you can see, the macro can return a function from your code or from a standard library (math.h).

_Generic is not supported by Visual Studio 2019 yet; please use GCC or Pelles for this example.

The output is
double 2.718282
float 2.718282
long double 2.718282
default 2

Debugging Methods

Most of your programs will contain errors, or bugs , when you first complete them. Removing such bugs from a program can represent a substantial proportion of the time required to write the program. The larger and more complex the program, the more bugs it’s likely to contain and the more time it will take to get the program to run properly. Very large programs, such as those typified by operating systems, or complex applications, such as word processing systems or even C program development systems, can be so involved that all the bugs can never be eliminated. You may already have experience of this in practice with some of the systems on your own computer. Usually these kinds of residual bugs are relatively minor, with ways in the system to work around them.

Your approach to writing a program can significantly affect how difficult it will be to test. A well-structured program consisting of compact functions, each with a well-defined purpose, is much easier to test than one without these attributes. Finding bugs will also be easier in a program that has extensive comments documenting the operation and purpose of its component functions and has well-chosen variable and function names. Good use of indentation and statement layout also makes testing and fault finding simpler. It’s beyond the scope of this book to deal with debugging comprehensively, but in this section I’ll introduce the basic ideas that you need to be aware of.

Integrated Debuggers

Many compilers are supplied with extensive debugging tools built into the program development environment. These can be very powerful facilities that can dramatically reduce the time required to get a program working. They typically provide a varied range of aids to testing a program that include the following:
  • Tracing program flow : This capability allows you to execute your program one source statement at a time. It operates by pausing execution after each statement and continuing with the next statement after you press a designated key. Other provisions of the debug environment will usually allow you to display information easily, pausing to show you what’s happening to the data in your program.

  • Setting breakpoints: Executing a large or complex program one statement at a time can be very tedious. It may even be impossible in a reasonable period of time. All you need is a loop that executes 10,000 times to make it an unrealistic proposition. Breakpoints provide an excellent alternative. With breakpoints, you define specific selected statements in your program at which a pause should occur to allow you to check what’s happening. Execution continues to the next breakpoint when you press a specified key.

  • Setting watches: This sort of facility allows you to identify variables that you want to track the value of as execution progresses. The values of the variables you select are displayed at each pause point in your program. If you step through your program statement by statement, you can see the exact point at which values are changed or perhaps not changed when you expect them to be.

  • Inspecting program elements: It may also be possible to examine a wide variety of program components. For example, at breakpoints the inspection can show details of a function such as its return type and its arguments. You can also see details of a pointer in terms of its address, the address it contains, and the data stored at the address contained in the pointer. Seeing the values of expressions and modifying variables may also be provided for. Modifying variables can help to bypass problem areas to allow other areas to be executed with correct data, even though an earlier part of the program may not be working properly .

The Preprocessor in Debugging

By using conditional preprocessor directives, you can arrange for blocks of code to be included in your program to assist in testing. In spite of the power of the debug facilities included with many C development systems, the addition of tracing code of your own can still be very useful. You have complete control of the formatting of data to be displayed for debugging purposes, and you can even arrange for the kind of output to vary according to conditions or relationships within the program.

Try it out: Using Preprocessor Directives
I can illustrate how you can use preprocessor directives to control execution and switch debugging output on and off through a program that calls functions at random through an array of function pointers:
// Program 13.1 Debugging using preprocessing directives
#define __STDC_WANT_LIB_EXT1__ 1
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
// Macro to generate pseudo-random number from 0 to NumValues */
#define random(NumValues) ((int)(((double)(rand())*(NumValues))/(RAND_MAX+1.0)))
#define iterations 6
#define test                            // Select testing output
#define testf                           // Select function call trace
#define repeatable                      // Select repeatable execution
// Function prototypes
int sum(int, int);
int product(int, int);
int difference(int, int);
int main(void)
{
  int funsel = 0;                      // Index for function selection
  int a = 10, b = 5;                   // Starting values
  int result = 0;                      // Storage for results
  // Function pointer array declaration
  int (*pfun[])(int, int) = {sum, product, difference};
#ifdef repeatable                      // Conditional code for repeatable execution
  srand(1);
#else
  srand((unsigned int)time(NULL));     // Seed random number generation
#endif
  // Execute random function selections
  int element_count = sizeof(pfun)/sizeof(pfun[0]);
  for(int i = 0 ; i < iterations ; ++i)
  {
    funsel = random(element_count);    // Generate random index to pfun array
    if( funsel > element_count - 1 )
    {
      printf_s("Invalid array index = %d ", funsel);
      exit(1);
    }
 #ifdef test
    printf_s("Random index = %d ", funsel);
 #endif
    result = pfun[funsel](a , b);      // Call random function
    printf_s("result = %d ", result );
  }
  return 0;
}
// Definition of the function sum
int sum(int x, int y)
{
#ifdef testf
  printf_s("Function sum called args %d and %d. ", x, y);
#endif
  return x + y;
}
// Definition of the function product
int product( int x, int y )
{
  #ifdef testf
  printf_s("Function product called args %d and %d. ", x, y);
  #endif
  return x * y;
}
// Definition of the function difference
int difference(int x, int y)
{
  #ifdef testf
  printf_s("Function difference called args %d and %d. ", x, y);
  #endif
  return x - y;
}

How It Works

You have a macro defined at the beginning of the program:
#define random(NumValues) ((int)(((double)(rand())*(NumValues))/(RAND_MAX+1.0)))

This defines the random() macro in terms of the rand() function that’s declared in stdlib.h. The rand() function generates random numbers in the range 0–RAND_MAX, which is a constant defined in stdlib.h. The macro maps values from this range to produce values from 0 to NumValues-1. You cast the value from rand() to double to ensure that computation will be carried out as type double, and you cast the overall result back to int because you want an integer in the program.

I defined random() as a macro to show you how, but it would be better defined as a function because this would eliminate any potential problems that might arise with argument values to the macro.

You then have four directives that define symbols:
#define iterations 6
#define test                            // Select testing output
#define testf                           // Select function call trace
#define repeatable                      // Select repeatable execution

The first defines a symbol that specifies the number of iterations in the loop that executes one of three functions at random. The other three are symbols that control the selection of code to be included in the program. Defining the test symbol causes code to be included that will output the value of the index that selects a function. Defining testf causes code that traces function calls to be included in the function definitions. When the repeatable symbol is defined, the srand() function is called with a fixed seed value, so the rand() function will always generate the same pseudo-random sequence, and the same output will be produced on successive runs of the program. Having repeatable output during test runs of the program obviously makes the testing process somewhat easier. If you remove the directive that defines the repeatable symbol, srand() will be called with the current time value as the argument, so the seed will be different each time the program executes, and you will get a different output on each execution of the program.

After setting up the initial variables used in main(), you have the following statement declaring and initializing the pfun array :
  int (*pfun[])(int, int) = {sum, product, difference};

This defines an array of pointers to functions that have two parameters of type int and a return value of type int. The array is initialized using the names of three functions, so the array will contain three elements.

Next, you have a directive that includes one of two alternative statements in the source, depending on whether or not the repeatable symbol is defined:
#ifdef repeatable                      // Conditional code for repeatable execution
  srand(1);
#else
  srand((unsigned int)time(NULL));     // Seed random number generation
#endif

If repeatable is defined, the statement that calls srand() with the argument value 1 will be included in the source for compilation. This will result in the same output each time you execute the program. Otherwise, the statement with the result of the time() function as the argument will be included, and you will get a different output each time you run the program.

In the loop in main(), the number of iterations is determined by the value of the iterations symbol; in this case it is 6. The first action in the loop is
    funsel = random(element_count);    // Generate random index to pfun array
    if( funsel > element_count - 1 )
    {
      printf_s("Invalid array index = %d ", funsel);
      exit(1);
    }

This executes the random() macro with element_count as the argument. This is the number of elements in the pfun array and is calculated immediately before the loop. The preprocessor will substitute element_count in the macro expansion before the code is compiled. For safety, there is a check that we do indeed get a valid index value for the pfun array .

The next three lines are the following:
#ifdef test
    printf_s("Random index = %d ", funsel);
 #endif

These include the printf_s() statement in the code when the test symbol is defined. If you remove the directive that defines test, the printf_s() call will not be included in the program that is compiled.

The last two statements in the loop call a function through one of the pointers in the pfun array and output the result of the call:
    result = pfun[funsel](a , b);      // Call random function
    printf_s("result = %d ", result );
Let’s look at just one of the functions that may be called because they are all similar, product(), for example:
int product( int x, int y )
{
  #ifdef testf
  printf_s("Function product called args %d and %d. ", x, y);
  #endif
  return x * y;
}

The function definition includes an output statement if the testf symbol is defined. You can therefore control whether the statements in the #ifdef block are included here independently from the output block in main() that is controlled by test. With the program as written with both test and testf defined, you’ll get trace output for the random index values generated and a message from each function as it’s called, so you can follow the sequence of calls in the program exactly.

You can have as many different symbolic constants defined as you wish. As you’ve seen previously in this chapter, you can combine them into logical expressions using the #ifdef form of the conditional directive.

Assertions

An assertion is an error message that is output when some condition is met. There are two kinds of assertions: compile-time assertions and runtime assertions. I’ll discuss the latter first because they are used more widely.

Runtime Assertions

The assert() macro is defined in the standard library header file assert.h. This macro enables you to insert tests of arbitrary expressions in your program that will cause the program to be terminated with a diagnostic message if a specified expression is false (i.e., evaluates to 0) during execution. The argument to the assert() macro is an expression that results in a scalar value, for example:
assert(a == b);

The expression will be true (nonzero) if a is equal to b. If a and b are unequal, the argument to the macro will be false, and the program will be terminated with a message relating to the assertion that includes the text of the argument to the macro, the source file name, the line number, and the name of the function in which the assert() appears. Termination is achieved by calling abort(), so it’s an abnormal end to the program. When abort() is called, the program terminates immediately. Whether stream output buffers are flushed, open streams are closed, or temporary files are removed, it is implementation dependent, so consult your compiler documentation on this.

In Program 13.1 I could have used an assertion to verify that funsel is valid:
assert(funsel < element_count);
If funsel is not less than element_count, the expression will be false, so the program will assert. Typical output from the assertion looks like this:
Assertion failed: file  d:examplesprogram13_01.c, func main line 44, funsel<element_count abort -- terminating

You can see that the function name and the line number of the code are identified as well as the condition that was not met.

Also, there is a #line directive with two possible arguments that will reset the line number and change file name, of course, with a debugging purpose. The following lines will be adding to the set number, and it must be greater than zero and less than or equal to 2147483647:
#line linenumber "filename"
// Program 13.1c Debugging using preprocessing directives
#include <stdio.h>
#include <assert.h>
int main(void)
{
#line 314 "qux.c"
    assert( 3 < 2 );
    printf("Hello World!");
    return 0;
}
The output is
qux.c(315): warning #2154: Unreachable code.

Switching Off Assertions

Runtime assertions can be switched off by defining the symbol NDEBUG before the #include directive for assert.h, like this:
#define NDEBUG                         // Switch off runtime assertions
#include <assert.h>

This code snippet will cause all assert() macros in your code to be ignored.

With some nonstandard systems, assertions are disabled by default, in which case you can enable them by undefining NDEBUG:
#undef NDEBUG                          // Switch on assertions
#include <assert.h>

By including the directive to undefine NDEBUG, you ensure that assertions are enabled for your source file. The #undef directive must appear before the #include directive for assert.h to be effective.

Compile-Time Assertions

The static_assert() macro enables you to output an error message during compilation. The message includes a string literal that you specify, and whether or not the output is produced depends on the value of an expression that is a compile-time constant. The macro is of the form
static_assert(constant_expression, string_literal);

When the constant expression evaluates to zero, compilation stops, and the error message is output.

The static_assert() enables you to build checks into your code about your implementation. For example, suppose your code assumes that type char is an unsigned type. You could include this static assertion in the source file:
static_assert(CHAR_MIN == 0, "Type char is a signed type. Code won't work.");

CHAR_MIN is defined in limits.h and is the minimum value for type char. When char is an unsigned type, CHAR_MIN will be zero, and when it is a signed type, it will be negative. Thus, this will cause compilation to be halted and an error message that includes your string to be produced when type char is signed.

Note

The static_assert is defined in assert.h as _Static_assert, which is a keyword. You can use _Static_assert instead of static_assert without including assert.h into your source file.

I can demonstrate runtime assertions in operation with a simple example.

Try it out: Demonstrating The Assert() Macro
Here’s the code for a program that uses the assert() macro:
// Program 13.2 Demonstrating assertions
#define __STDC_WANT_LIB_EXT1__ 1
#include <stdio.h>
#include <assert.h>
int main(void)
{
  int y = 5;
  for(int x = 0 ; x < 20 ; ++x)
  {
    printf("x = %d   y = %d ", x, y);
    assert(x < y);
  }
  return 0;
}
Compiling and executing this with my compiler produces the following output:
x = 0   y = 5
x = 1   y = 5
x = 2   y = 5
x = 3   y = 5
x = 4   y = 5
x = 5   y = 5
Assertion failed: file C:Projectsprogram13_02.c, func main, line 13, x < y
abort -- terminating
*** Process returned 1 ***

How It Works

Apart from the assert() statement, the program doesn’t need much explanation because it simply displays the values of x and y in the for loop. The program is terminated by the assert() macro as soon as the condition x < y becomes false. As you can see from the output, this is when x reaches the value 5. The macro displays the output on stderr. Not only do you get the condition that failed displayed but you also get the file name and line number in the file where the failure occurred. This is particularly useful with multifile programs in which the source of the error is pinpointed exactly.

Assertions are often used for critical conditions in a program in which, if certain conditions aren’t met, disaster will surely ensue. You would want to be sure that the program wouldn’t continue if such errors arise.

You could switch off the assertion mechanism in the example by adding the following directive:
#define NDEBUG

This must be placed before the #include directive for assert.h to be effective. With this #define at the beginning of Program 13.2, you’ll see that you get output for all the values of x from 0 to 19 and no diagnostic message.

Date and Time Functions

The preprocessor macros for the date and the time produce values that are fixed at compile time. The time.h header declares functions that produce the time and date when you call them. They provide output in various forms from the hardware timer in your PC. You can use these functions to obtain the current time and date, to calculate the time elapsed between two events, and to measure how long the processor has been occupied performing a calculation.

Getting Time Values

The simplest function returning a time value has the following prototype:
clock_t clock(void);

This function returns the processor time (not the elapsed time) used by the program since some implementation-defined reference point, often since execution began. You typically call the clock() function at the start and end of some process in a program, and the difference is a measure of the processor time consumed by the process. The return value is of type clock_t, which is an integer type that is defined in time.h. Your computer will typically be executing multiple processes at any given moment. The processor time is the total time the processor has been executing on behalf of the process that called the clock() function. The value that is returned by the clock() function is measured in clock ticks . To convert this value to seconds, you divide it by the value that is produced by the macro CLOCKS_PER_SEC, which is also defined in time.h. The value produced by CLOCKS_PER_SEC is the number of clock ticks in 1 second. The clock() function returns –1 if an error occurs. In C17, it was clarified and added that if the value cannot be represented, the function returns an unspecified value; this may be due to overflow of the clock_t type.

As I said, to determine the processor time used in executing a process, you need to record the time when the process starts executing and subtract this from the time returned when the process finishes, for example:
clock_t start = 0, end = 0;
double cpu_time = 0.0;
start = clock();
// Execute the process for which you want the processor time...
end = clock();
cpu_time = (double)(end-start)/CLOCKS_PER_SEC;      // Processor time in seconds

This fragment stores the total processor time used by the process in cpu_time . The cast to type double is necessary in the last statement to get the correct result.

As you’ve seen, the time() function returns the calendar time as a value of type time_t. The calendar time is the current time usually measured in seconds since a fixed time on a particular date. The fixed time and date is often 00:00:00GMT on January 1, 1970, and this is typical of how time values are defined. However, the reference point is implementation defined, so check your compiler and library documentation to verify this.

The prototype of the time() function is
time_t time(time_t *timer);

If the argument isn’t NULL, the current calendar time is also stored in timer. The type time_t is defined in the header file and is often equivalent to type long.

To calculate the elapsed time in seconds between two successive time_t values returned by time(), you can use the function difftime() , which has this prototype:
double difftime(time_t T2, time_t T1);

The function will return the value of T2 - T1 expressed in seconds as a value of type double. This value is the time elapsed between the two time() function calls that produce the time_t values, T1 and T2.

Try it out: Using Time Functions
You could log the elapsed time and processor time used for a computation by using functions from time.h as follows:
// Program 13.3 Test our timer function
#define __STDC_WANT_LIB_EXT1__ 1
#include <stdio.h>
#include <time.h>
#include <math.h>
#include <ctype.h>
int main(void)
{
  time_t calendar_start = time(NULL);         // Initial calendar time
  clock_t cpu_start = clock();                // Initial processor time
  int count = 0;                              // Count of number of loops
  const long long iterations = 1000000000LL;  // Loop iterations
  char answer = 'y';
  double x = 0.0;
  printf_s("Initial clock time = %lld Initial calendar time = %lld ",
                                (long long)cpu_start, (long long)calendar_start);
  while(tolower(answer) == 'y')
  {
    for(long long i = 0LL ; i < iterations ; ++i)
      x = sqrt(3.14159265);
    printf_s("%lld square roots completed. ", iterations*(++count));
    printf_s("Do you want to run some more(y or n)? ");
    scanf_s(" %c", &answer, sizeof(answer));
  }
  clock_t cpu_end = clock();                  // Final cpu time
  time_t calendar_end = time(NULL);           // Final calendar time
  printf_s("Final clock time = %lld Final calendar time = %lld ",
                               (long long)cpu_end, (long long)calendar_end);
  printf_s("CPU time for %lld iterations is %.2lf seconds ",
                 count*iterations, ((double)(cpu_end-cpu_start))/CLOCKS_PER_SEC);
  printf_s("Elapsed calendar time to execute the program is %8.2lf seconds. ",
                                       difftime(calendar_end, calendar_start));
  return 0;
}
On my machine I get the following output:
Initial clock time = 0 Initial calendar time = 1354017916
1000000000 square roots completed.
Do you want to run some more(y or n)?
y
2000000000 square roots completed.
Do you want to run some more(y or n)?
y
3000000000 square roots completed.
Do you want to run some more(y or n)?
n
Final clock time = 24772 Final calendar time = 1354017941
CPU time for 3000000000 iterations is 24.77 seconds
Elapsed calendar time to execute the program is    25.00 seconds.

How It Works

This program illustrates the use of the functions clock(), time(), and difftime(). The time() function usually returns the current time in seconds, and when this is the case, you may not get values less than 1 second. Depending on the speed of your machine, you may want to adjust the number of iterations in the loop to reduce or increase the time required to execute this program. Note that the clock() function may not be a very accurate way of determining the processor time used in the program. You also need to keep in mind that measuring elapsed time using the time() function can be a second out.

You record and display the initial values for the processor time and the calendar time and set up the controls for the loop that follows with these statements:
  time_t calendar_start = time(NULL);       // Initial calendar time
  clock_t cpu_start = clock();              // Initial processor time
  int count = 0;                            // Count of number of loops
  const long long iterations = 1000000000LL;  // Loop iterations
  char answer = 'y';
  double x = 0.0;
  printf_s("Initial clock time = %lld Initial calendar time = %lld ",
                                (long long)cpu_start, (long long)calendar_start);

Casting the values of cpu_start and calendar_start to type long long obviates any formatting problems that might arise because of the types that clock_t and time_t are implemented as.

You then have a loop controlled by the character stored in answer, so the loop will execute as long as you want it to continue:
  while(tolower(answer) == 'y')
  {
    for(long long i = 0LL ; i < iterations ; ++i)
      x = sqrt(3.14159265);
    printf_s("%lld square roots completed. ", iterations*(++count));
    printf_s("Do you want to run some more(y or n)? ");
    scanf_s(" %c", &answer, sizeof(answer));
  }

The inner loop calls the sqrt() function that is declared in the math.h header iterations times , so this is just to occupy some processor time. If you are leisurely in your entry of a response to the prompt for input, this should extend the elapsed time. Note the newline escape sequence in the beginning of the first argument to scanf_s(). If you leave this out, your program will loop indefinitely, because scanf_s() will not ignore whitespace characters in the input stream buffer.

Finally, you output the final values returned by clock() and time() and calculate the processor and calendar time intervals. The library that comes with your C compiler may well have additional nonstandard functions for obtaining processor time that are more accurate than the clock() function .

Caution

Note that the processor clock can wrap around, and the resolution with which processor time is measured can vary between different hardware platforms. For example, if the processor clock is a 32-bit value that has a microsecond resolution, the clock will wrap back to zero roughly every 72 minutes.

Getting the Date

Having the time in seconds dating to a date over a quarter of a century ago is interesting, but it’s often more convenient to get today’s date as a string. You can do this with the function ctime() , which has this prototype:
char *ctime(const time_t *timer);

The function accepts a pointer to a time_t variable as an argument that contains a calendar time value returned by the time() function. It returns a pointer to a 26-character string containing the day, the date, the time, and the year, which is terminated by a newline and ''.

A typical string returned might be the following:
"Mon Aug 25 10:45:56 2003 "
The ctime() function has no knowledge of the length of the string you have allocated to store the result, which makes this an unsafe operation. There is an optional safer version of the function that has this prototype:
errno_t ctime_s(char * str, rsize_t size, const time_t *timer);
The first parameter is the address of the array where the result is to be stored, and the second parameter is the size of the str array . The function returns 0 if the conversion was successful and a nonzero value otherwise. The str array should have at least 26 elements but not more than RSIZE_MAX elements. Remember RSIZE_MAX is data type size_t, and it is runtime constraint for several _s functions. It is a good practice, for instance, if large numbers appeared when handling unsigned int (by being negative by error). It will depend on your machine (i.e., 9223372036854775807):
#define __STDC_WANT_LIB_EXT1__ 1
#include <stdio.h>
#include <stdint.h>
int main(void)
{
        printf("rsize_max=%zu ", RSIZE_MAX);
        return 0;
}
You might use the ctime_s() function like this:
char time_str[30] = {''};
time_t calendar = time(NULL);
if(!ctime_s(time_str, sizeof(time_str), &calendar))
   printf_s("%s", time_str);                   // Output calendar time as date string
else
   fprintf_s(stderr, "Error converting time_t value ");
You can also get at the various components of the time and date from a calendar time value by using the localtime() function . This has the prototype
struct tm *localtime(const time_t *timer);
This function accepts a pointer to a time_t value and returns a pointer to a structure of type tm, which is defined in time.h. It returns NULL if timer cannot be converted. The optional version has the prototype
struct tm *localtime_s(const time_t * restrict timer, struct tm * restrict result);

Both arguments must be non-NULL . The structure contains at least the members listed in Table 13-1.

We need to do a highlight that this function is from the standard C17 (since C11). Meanwhile, Microsoft compiler has a very different signature (these differences can be seen in Program 13.04, where the arguments are reversed and the function's return is opposed):
errno_t localtime_s(
   struct tm* const tmDest,
   time_t const* const sourceTime
);
Table 13-1.

Members of the tm Structure

Member

Description

tm_sec

Seconds (0–60) after the minute on 24-hour clock. This value goes up to 60 for positive leap-second support

tm_min

Minutes after the hour on 24-hour clock (0–59)

tm_hour

The hour on 24-hour clock (0–23)

tm_mday

Day of the month (1–31)

tm_mon

Month (0–11)

tm_year

Year (current year minus 1900)

tm_wday

Weekday (Sunday is 0; Saturday is 6)

tm_yday

Day of year (0–365)

tm_isdst

Daylight saving flag. Positive for daylight saving time, 0 for not daylight saving time, and negative for not known

All the members are of type int. The localtime() function returns a pointer to the same structure each time you call it, and the structure members are overwritten on each call. If you want to keep any of the member values, you need to copy them elsewhere before the next call to localtime(), or you could create your own tm structure and save the whole lot if you really need to. You supply the structure as an argument to localtime_s() so you control whether you reuse a structure object. This makes operations simpler and less error prone.

The time that localtime() and localtime_s() produce is local to where you are. If you want to get the time in a tm structure that reflects UTC (Coordinated Universal Time), you can use the gmtime() function or, better, the optional gmtime_s() function . These expect the same arguments as the localtime() and localtime_s() functions and return a pointer to a tm structure .

TIME_UTC and timespec_get are new in C11; TIME_UTC can be as argument to function timespec_get(&ts, TIME_UTC).

The timespec_get function sets the interval pointed to by ts to hold the current calendar time based on the specified time base.

Here’s a code fragment that will output the day and the date from the members of the tm structure:
  time_t calendar = time(NULL);                 // Current calendar time
  struct tm time_data;
  const char *days[] =   {"Sunday",   "Monday", "Tuesday", "Wednesday",
                          "Thursday", "Friday", "Saturday"              };
  const char *months[] = {"January", "February", "March",
                          "April",    "May",     "June",
                          "July",    "August",   "September",
                          "October", "November", "December"  };
  if(localtime_s(&calendar, &time_data))
    printf_s("Today is %s %s %d %d ",
                      days[time_data.tm_wday], months[time_data.tm_mon],
                      time_data.tm_mday,       time_data.tm_year+1900);

You’ve defined arrays of strings to hold the days of the week and the months. You use the appropriate member of the structure that has been set up by the call to the localtime_s() function . You use the day in the month and the year values from the structure directly. You can easily extend this to output the time.

Typical output from executing this is
Today is Tuesday November 27 2012
The asctime() and its optional safer partner asctime_s() generate a string representation of a tm structure. Their prototypes are
char *asctime(const struct tm *time_data);
errno_t asctime_s(char *str, rsize_t size, const struct tm *time_data);

The asctime_s() stores the string in str, which must be an array with at least 26 elements and smaller than RSIZE_MAX; size is the number of elements in str. The function returns 0 when everything works and a nonzero integer when it does not. The function works for tm structures where the year is from 1000 to 9999, so it should be okay for a while yet! The string that results is of the same form as that produced by ctime().

Try it out: Getting The Date
It’s very easy to pick out the members you want from the structure of type tm populated by the localtime_s() function . You can demonstrate this with the following example:
// Program 13.4        Getting date data with ease
#define __STDC_WANT_LIB_EXT1__ 1
#include <stdio.h>
#include <time.h>
int main(void)
{
  const char *day[7] = {
                   "Sunday"  , "Monday", "Tuesday", "Wednesday",
                   "Thursday", "Friday", "Saturday"
                       };
  const char *month[12] = {
                     "January",   "February", "March",    "April",
                     "May",       "June",     "July",     "August",
                     "September", "October",  "November", "December"
                          };
  const char *suffix[] = { "st", "nd", "rd", "th" };
  enum sufindex { st, nd, rd, th } sufsel = th;  // Suffix selector
  struct tm ourT;                                // The time structure
  time_t tVal = time(NULL);                      // Calendar time
  //if(localtime_s(&ourT , &tVal))               // VS 2019 - Populate time structure
  if(!localtime_s(&tVal, &ourT))                 // C11 standard - Populate time structure  {
    fprintf_s(stderr, "Failed to populate tm struct. ");
    return -1;
  }
  switch(ourT.tm_mday)
  {
    case 1: case 21: case 31:
      sufsel= st;
      break;
    case 2: case 22:
      sufsel= nd;
      break;
    case 3: case 23:
      sufsel= rd;
      break;
    default:
      sufsel= th;
      break;
  }
  printf_s("Today is %s the %d%s %s %d. ", day[ourT.tm_wday],
    ourT.tm_mday, suffix[sufsel], month[ourT.tm_mon], 1900 + ourT.tm_year);
  printf_s("The time is %d : %d : %d. ",
    ourT.tm_hour, ourT.tm_min, ourT.tm_sec );
  return 0;
}
Here’s an example of output from this program:
Today is Tuesday the 27th November 2012. The time is 15 : 42 : 44.

How It Works

You define arrays of strings in main() for the days of the week, the months in the year, and the suffix to be applied to a date value. Each statement defines an array of pointers to char. You could omit the array dimensions in the first two declarations, and the compiler would compute them for you, but in this case you’re reasonably confident about both these numbers, so this is an instance in which putting them in helps to avoid an error. The const qualifier specifies that the strings pointed to are constants and should not be altered in the code.

The enumeration provides a mechanism for selecting an element from the suffix array:
  enum sufindex { st, nd, rd, th } sufsel = th;       // Suffix selector

The enumeration constants, st, nd, rd, and th, will be assigned values 0–3 by default, so we can use the sufsel variable as an index to access elements in the suffix array. The names for the enumeration constants make the code a little more readable.

You also declare a structure variable in the following declaration:
  struct tm ourT;                                     // The time structure

The values for the members of this structure will be set by the localtime_s() function .

You initialize tVal with the current time using the time() function. You pass the address of tVal as the first argument to localtime_s() to generate the values of the members of the ourT structure whose address you pass as the second argument. If the call to localtime_s() is successful, you execute the switch:
  switch(ourT.tm_mday)
  {
    case 1: case 21: case 31:
      sufsel= st;
      break;
    case 2: case 22:
      sufsel= nd;
      break;
    case 3: case 23:
      sufsel= rd;
      break;
    default:
      sufsel= th;
      break;
  }

The sole purpose of this is to select what to append to the date value. Based on the member tm_mday , the switch selects an index to the suffix array for use when outputting the date by setting the sufsel variable to the appropriate enumeration constant value.

The day, the date, and the time are displayed, with the day and month strings obtained by indexing the appropriate array with the corresponding structure member value. You add 1900 to the value of the tm_year member because this value is measured relative to the year 1900.

Getting the Day for a Date

You can use the mktime() function to determine the day of the week for a given date. The function has the prototype
time_t mktime(struct tm *ptime);

You pass the address of a tm structure object to the function with the tm_mon , tm_mday , and tm_year members set to values corresponding to the date you are interested in. The values of the tm_wday and tm_yday members of the structure will be ignored, and if the operation is successful, the values will be replaced with the values that are correct for the date you have supplied. The function returns the calendar time as a value of type time_t if the operation is successful or –1 if the date cannot be represented as a time_t value, causing the operation to fail. Let’s see it working in an example.

Try it out: Getting The Day for a Date
You can demonstrate the mktime() function with the following invaluable example:
// Program 13.5        Getting the day for a given date
#define __STDC_WANT_LIB_EXT1__ 1
#include <stdio.h>
#include <time.h>
int main(void)
{
  const char *day[7] = {
                   "Sunday"  , "Monday", "Tuesday", "Wednesday",
                   "Thursday", "Friday", "Saturday"
                       };
  const char *month[12] = {
                     "January",   "February", "March",    "April",
                     "May",       "June",     "July",     "August",
                     "September", "October",  "November", "December"
                          };
  const char *suffix[] = { "st", "nd", "rd", "th" };
  enum sufindex { st, nd, rd, th } sufsel = th;  // Suffix selector
  struct tm birthday = {0};                      // A birthday time structure
  char name[30] = {''};
  printf_s("Enter a name: ");
  gets_s(name, sizeof(name));
  printf_s("Enter the birthday for %s as day month year integers separated by spaces."
             " e.g. Enter 1st February 1985 as 1 2 1985 : ", name);
  scanf_s(" %d %d %d", &birthday.tm_mday, &birthday.tm_mon, &birthday.tm_year);
  birthday.tm_mon -= 1;                          // Month zero-based
  birthday.tm_year -= 1900;                      // Year relative to 1900
  if(mktime(&birthday) == - 1)
  {
    fprintf_s(stderr, "Operation failed. ");
    return -1;
  }
  switch(birthday.tm_mday)
  {
    case 1: case 21: case 31:
      sufsel= st;
      break;
    case 2: case 22:
      sufsel= nd;
      break;
    case 3: case 23:
      sufsel= rd;
      break;
    default:
      sufsel= th;
      break;
  }
  printf_s("%s was born on the %d%s %s %d, which was a %s. ", name,
                 birthday.tm_mday, suffix[sufsel], month[birthday.tm_mon],
                            1900 + birthday.tm_year, day[birthday.tm_wday]);
  return 0;
}
Here’s a sample of the output:
Enter a name: Kate Middleton
Enter the birthday for Kate Middleton as day month year integers separated by spaces.
e.g. Enter 1st February 1985 as 1 2 1985 : 9 1 1982
Kate Middleton was born on the 9th January 1982, which was a Saturday.

How It Works

You create arrays of constant strings for the day, month, and date suffixes as you did in Program 13.4. You then create a tm structure and an array to store a name:
  struct tm birthday = {0};                      // A birthday time structure
  char name[30] = {''};
You prompt for and read a name and values for the day, month, and year of a birthday date for the person named:
  printf_s("Enter a name: ");
  gets_s(name, sizeof(name));
  printf_s("Enter the birthday for %s as day month year integers separated by spaces."
             " e.g. Enter 1st February 1985 as 1 2 1985 : ", name);
  scanf_s(" %d %d %d", &birthday.tm_mday, &birthday.tm_mon, &birthday.tm_year);

The date values are read directly into the members of the birthday structure. The month should be zero based and the year relative to 1900, so you adjust the values stored accordingly.

With the date set, you get the values for tm_wday and tm_yday members set by calling the mktime() function :
  if(mktime(&birthday) == - 1)
  {
    fprintf_s(stderr, "Operation failed. ");
    return -1;
  }

The if statement checks whether the function returns –1, indicating that the operation has failed. In this case, you simply output a message and terminate the program. Finally, you display the day corresponding to the birth date that was entered in the same way as in the previous example.

Summary

In this chapter, I discussed the preprocessor directives that you use to manipulate and transform the code in a source file before it is compiled. Because the chapter is primarily about preprocessing, there is no “Designing a Program” section. Your standard library header files are an excellent source of examples of coding preprocessing directives. You can view these examples with any text editor. Virtually all of the capabilities of the preprocessor are used in the libraries, and you’ll find a lot of other interesting code there too. It’s also useful to familiarize yourself with the contents of the libraries, as you can find many things not necessarily described in the library documentation. If you want to know what the type clock_t is, for example, just look in time.h.

The debugging capability that the preprocessor provides is useful, but you will find that the debugging tools provided with many C programming systems are much more powerful. For serious program development, the debugging tools are as important as the efficiency of the compiler. We will wrap up our discussion in the next chapter on advanced and specialized areas of programming.

Exercises

The following exercises enable you to try out what you learned in this chapter. If you get stuck, look back over the chapter for help. If you’re still stuck, you can download the solutions from the Source Code/Download area of the Apress website (www.apress.com), but that really should be a last resort.

Exercise 13-1. Define a macro, COMPARE(x, y), that will result in the value –1 if x < y, 0 if x == y, and 1 if x > y. Write an example to demonstrate that your macro works as it should. Can you see any advantage that your macro has over a function that does the same thing?

Exercise 13-2. Define a function that will return a string containing the current time in 12-hour format (a.m./p.m.) if the argument is 0 and in 24-hour format if the argument is 1. Demonstrate that your function works with a suitable program.

Exercise 13-3. Define a macro, print_value(expr), that will output on a new line expr = result where result is the value that results from evaluating expr. Demonstrate the operation of your macro with a suitable program.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.223.159.195