Topic : An introduction to C
Author : Tom Torfs
Page : << Previous 18  Next >>
Go to page :


optimization purposes is usually a bad idea.
[*] Actually, that isn't even guaranteed. Using operators like ++ and --, which modify the same variable twice in the same expression (and not separated by a so-called sequence point, but I'm not going to explain all that here; see question 3.8 in the c.l.c FAQ, the URL can be found in 17.2. Other interesting C-related online material) gives undefined behaviour according to the standard, which means anything may happen.
Here are some examples of what might happen if you ignore the above warnings:
- not putting parentheses around macro parameters:

#define SQR(x) (x*x)
/* SQR(a+2) yields (a+2*a+2) */

- not putting parentheses around the entire macro value:

#define INC(x) (x)+1
/* INC(i)*3 yields (i)+1*3 */

- not being careful with multiple evaluation of macro parameters:

#define CHECK_BOUNDS(value,min,max) ((value)>=(min) && (value)<=(max))

/* the ask_for_integer() function asks the user to enter a number
   and returns this number as an integer */
int ask_for_integer(void);

/* CHECK_BOUNDS(ask_for_integer(),1,100) will ask the user to
   enter the number twice */


In a macro value, the operator '#' may be used to expand a macro parameter to the textual appearance of the actual argument in the form of a string literal, exactly as supplied in the calling source. The parameter must follow the '#' immediately. For example:

#define TEXT(m) #m
TEXT(300)


will expand to a string "300" (including the quotation marks). (Quotation marks and backslashes in the macro argument will be escaped with a backslash when generating the string literal.)
This feature is useful if you want to export parts of the source to the program output. It is mostly used for debugging or logging purposes.
In a macro value, the operator '##' may be used to concatenate a macro parameter's invocation argument with adjacent text. This can be used to generate identifiers. For example:

#define GEN_ID(a,b) a##b
int GEN_ID(i,9);

expands to:

int i9;


Any macro may be undefined using the #undef preprocessor directive. For example, the following code will undefine the macro TEST if it is defined, or have no effect otherwise:

#undef TEST

Now onto another preprocessor construct:

#ifdef USEDUTCH

#else

#endif


The code between the #ifdef and the #else lines will only be compiled if the USEDUTCH macro has been defined, and the code between the #else and #endif lines will only be compiled if that macro has not been defined.
The difference with a normal if statement is that this is evaluated by the preprocessor at compile-time. So while if code that may never be executed would probably still be in the executable, it will not be when a preprocessor #ifdef is used.
See below for a more advanced form of preprocessor conditionals.

   printf("This is version %d of the preproc program\n", PROGVERSION);

   printf("The greatest value of %d and %d is %d\n", q, r, MAX(q,r));

   printf("File %s, line %ld, compilation date %s\n",
          __FILE__, (long)__LINE__, __DATE__);


(or the equivalent version in Dutch)
The first line prints the program version, as defined in our PROGVERSION macro. Since this is an integer constant (in our case 1) the %d printf format specifier is used.
The second line uses the MAX(a,b) macro. The MAX(q,r) will be replaced by:

   ((q) > (r) ? (q) : (r))

Which, as seen in 7.4. Using the ?: operator, will give the value of q in case q is greater than r, or the value of r otherwise.
This means that the third number that is printed will always be the greatest value of the two.
The third line uses some macros that are pre-defined for you by the compiler. The __FILE__ macro expands to a string containing the current filename, the __LINE__ macro expands to a decimal constant containing the current line number in the file, and the __DATE__ macro expands to a string containing the compilation date. The __LINE__ macro is cast to a long because we use the %ld format specifier and we don't know of what type the line number constant will be (in a very long program it might be too large to fit in an integer). The following such macros are available:

__FILE__   source filename (string literal)
__LINE__   line number (decimal constant)
__DATE__   compilation date (string literal)
__TIME__   compilation time (string literal)
__STDC__   set to 1 for standard-conforming mode (decimal constant)

On to the next lines of code:

#if defined(TEST_ASSERT)
   q++;
#endif

This is equivalent to:

#ifdef TEST_ASSERT
   q++;
#endif


Which means that the code to increment the q variable by 1 will only be compiled when the TEST_ASSERT macro is defined (see below for the effect this increment has).
The #if preprocessor directive is a lot more flexible than #ifdef. It allows for expressions combined using operators, such as e.g.:

#if defined(MYCOMPILER_VERSION) && (MYCOMPILER_VERSION>=2)
   /* some code specific to MYCOMPILER versions greater than 2 */
#endif


Which means the code between the #if and the #endif will only be compiled if the MYCOMPILER_VERSION macro is defined, and the value of this macro is greater than or equal to 2.
You can only use things that are known at preprocessing-time in an #if statement. For example, you can't check for a typedef using #if defined(mytypedef) and you can't check for certain sizes using something like #if sizeof(int)>=4. As an alternative for the latter, you can check the value of e.g. UINT_MAX from limits.h.
There's also the #elif preprocessor directive, which works similar to an else if () construct.
The #error directive can be used to force a diagnostic message from the compiler. The #pragma directive can be used to control compiler specific features. The #line directive can be used to change the current line number value. See your compiler manual / online help if you want to know more details.13.3. Using the assert() macro

   assert(q==5);

The assert() macro, defined in assert.h, can be used to make absolutely sure a certain condition is fulfilled, intended for debugging purposes.
What the assert() macro does depends on whether the NDEBUG macro is defined when assert.h is included or not. If the NDEBUG (no debugging) macro is defined at that time, the assert() macro will do nothing (this is intended for release code, to get rid of unnecessary debugging code).
If the NDEBUG macro isn't defined when assert.h is included, the assert() macro checks the condition that is passed to it as parameter. If this condition is true, nothing happens. If this condition is false, however, the program prints an error message containing the source file, line number and failed condition, and aborts the program abnormally.
Obviously this is not very elegant, but it is only intended for debugging purposes. The conditions you check with it should be conditions that absolutely have to be fulfilled, unless some serious error has occurred in the program's internal logic. It should never be used to trap errors that are caused by external factors such as user input or not-enough-memory errors etc.
In our example, the assert() macro makes sure the q variable equals 5. It should, because we assigned that value to it in its definition. However, when the TEST_ASSERT macro is defined some code above has incremented the value of q by 1, so that it no longer equals 5 but 6, and this will of course trigger the assert() macro.

14. Variable argument functions
14.1. The vararg program

/* prog14-1.c: vararg */

#include <stdio.h>
#include <stdarg.h>

int sumintegers(int numbers, ...)
{
   va_list vl;
   int i;
   int sum;

   va_start(vl,numbers);

   sum = 0;
   for (i=0; i<numbers; i++)
      sum += va_arg(vl,int);

   va_end(vl);

   return sum;
}

int main(void)
{
   int a,b;

   printf("Sum #1 = %d\n",
          sumintegers(10,1,2,3,5,7,9,11,13,17,19));

   a = 100;
   b = 200;

   printf("Sum #2 = %d\n",
          sumintegers(2,a,b));

   return 0;
}


The output of this program should be:

Sum #1 = 87
Sum #2 = 300

14.2. Using variable argument functions

#include <stdarg.h>

The standard header stdarg.h is needed for variable argument functions.

int sumintegers(int numbers, ...)
{
}


This seems like a normal function, except for the three dots. This is called an ellipsis and means that a variable number of arguments may follow. An ellipsis must be the last parameter of a function and it may not be on its own, i.o.w. there should always be at least one normal, fixed parameter, before the variable argument list can begin.

   va_list vl;
   int i;
   int sum;


The vl variable of type va_list is required for processing the variable argument list. The i variable is just a counter and the sum variable will hold the calculated sum.

   va_start(vl,numbers);

The va_start() function initializes the variable argument list. This call is required before any of the variable arguments can be accessed. The first parameter must be the abovementioned va_list variable and the second parameter must be the last fixed parameter of the function (i.o.w. the parameter right before the

Page : << Previous 18  Next >>