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


year, month and day values are checked for validity [*], and if all this went well, the ok flag variable is set to 1.
[*] Invalid dates, such as the 31st of February, still go undetected. If you wish to detect these cases also, keep in mind that February has 28 days except in leap years when it has 29. All years that are divisible by 4 but not by 100, unless they're also divisible by 400, are leap years.

      personlist[num].birthyear = year;
      personlist[num].birthmonth = month;
      personlist[num].birthday = day;


The just entered and verified birthday values are assigned to the corresponding member variable in our current person structure. The addressing is completely analogous to that of the name member variable above.

   printf("\nOK, thank you.\n");

   printf("\nYou entered the following data:\n");

   printf("\n%-10s%-30s%s\n", "Number", "Name", "Birthday");


Input is ended, and we're going to print out the contents of all entered person structures. To write out the title of this table we use a somewhat peculiar printf() call. The %-10s format specifier will print a string, left-aligned into a 10 character wide field. The value of this string is a literal. We might also have manually aligned this title, but this is more obvious and easier to change. The %-30s format specifier is completely analogous. See 3.3. printf for details.

   for (num=0; num<number_of_persons; num++)
   {
      printf("%-10u%-30s%04d-%02d-%02d\n",
                num,
                personlist[num].name,
                personlist[num].birthyear,
                personlist[num].birthmonth,
                personlist[num].birthday);
   }


We loop through all entered person structures again with a for() loop from 0 to number_of_persons-1 and print out the contents of every structure.
The printf() format string aligns the data in the same way as was done in our title bar (except of course that some of the strings are replaced by numerical data). The %04d format specifier means to align the integer variable to the right in a field of 4 characters wide, and fill up at the left with 0s. The %02d format specifiers are analogous. Again, see 3.3. printf for details.

   free(personlist);

Now that we no longer need the memory we allocated, we must free it by calling the free() function with our previously allocated personlist pointer as parameter. [*]
[*] While the standard guarantees that free(NULL); will work fine, you may not pass any pointers to free() other than those received from malloc(), or nasty things may happen (undefined behaviour).
Note: instead of malloc() you can also use calloc() to dynamically allocate memory. The calloc() function takes two parameters, which it multiplies to get the actual number of bytes that should be allocated [*], and initializes the allocated memory to all-bits-0 (however, this will only correctly initialize integral variables; floating-point variables are not guaranteed to be set to 0.0 and pointers are not guaranteed to be set to NULL).
[*] The result of the multiplication should not be larger than what would normally fit in either, so you can't rely on this feature to get around compiler limits (such as the 64K limit on 16-bit compilers, for example).
Note 2: you can use the realloc() function to resize an allocated block of memory while preserving its contents (see 16. Overview of the standard library). However you must watch out for these:
- the realloc() function may actually move the memory block
- if the realloc() function returns NULL, the original memory block is still allocated and unchanged


13. Preprocessor macros/conditionals
13.1. The preproc program

/* prog13-1.c: preproc */

#include <stdio.h>
#include <assert.h>

/* uncomment the line below for a test of the USEDUTCH macro */
/* #define USEDUTCH */

/* uncomment the line below for a test of the assert() macro */
/* #define TEST_ASSERT */

#define PROGVERSION 1

#define MAX(a,b) ((a) > (b) ? (a) : (b))

int main(void)
{
   int q = 5, r = 3;

#ifdef USEDUTCH

   printf("Dit is versie %d van het preproc programma\n", PROGVERSION);

   printf("De grootste waarde van %d en %d is %d\n", q, r, MAX(q,r));

   printf("Bestand %s, lijn %ld, compilatiedatum %s\n",
          __FILE__, (long)__LINE__, __DATE__);

#else

   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__);

#endif

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

   assert(q==5);

   return 0;
}


This program's output should be something like this:

This is version 1 of the preproc program
The greatest value of 5 and 3 is 5
File prog13-1.c, line 36, compilation date Aug 05 1998

When both the #define USEDUTCH and #define TEST_ASSERT lines are uncommented, the output may resemble this:

Dit is versie 1 van het preproc programma
De grootste waarde van 5 en 3 is 5
Bestand prog13-1.c, lijn 27, compilatiedatum Aug 05 1998
Assertion failed: a==5, file prog13-1.c, line 44
ABNORMAL PROGRAM TERMINATION

13.2. Using preprocessor macros/conditionals

#include <stdio.h>
#include <assert.h>


The assert.h header is included because it defines the assert() macro (see 13.3. Using the assert() macro).

/* uncomment the line below for a test of the USEDUTCH macro */
/* #define USEDUTCH */

/* uncomment the line below for a test of the assert() macro */
/* #define TEST_ASSERT */


We've already seen in 2.3. #include that all lines that begin with # are directives for the preprocessor. After the #include preprocessor directive, the #define preprocessor directive is the most commonly used one.
When the above lines are uncommented (the surrounding /* and */ removed), they will define preprocessor macros named USEDUTCH and TEST_ASSERT. In this case, these macros have only names, no corresponding values. This sort of macro is usually used to turn on or off a certain feature in a program at compile-time. In our case the USEDUTCH macro will, when defined, make the program switch to using Dutch instead of English, and the TEST_ASSERT macro will cause the assert() macro to be triggered (see below).

#define PROGVERSION 1

This is also a preprocessor macro definition, but this time the macro not only has a name but also a corresponding value (1). Everywhere in the program (outside of a string literal) where the PROGVERSION symbol appears, it will be replaced by the value 1 during preprocessing (right before the actual compilation).
This sort of macro is usually used to avoid hardcoding certain "magic numbers", usually for one of these two reasons:
- using a symbolic constant makes the program clearer, e.g. PI may be more obvious than just 3.1415 and is also less likely to contain typing errors
- the value to which the macro corresponds may change (e.g. like in our example, where it is the program version) and by using a macro the value needs to be changed only in one place, instead of in a lot of places possibly spread throughout the source code


#define MAX(a,b) ((a) > (b) ? (a) : (b))

This is yet another preprocessor macro definition, and a more advanced form. The (a,b) are parameters just like if the macro were a function that's called. Everywhere in the program where the MAX(a,b) macro is used, it will be replaced by the following code:

   ((a) > (b) ? (a) : (b))

In which the a and b parameters will be replaced by whatever was inside the MAX() parentheses (see below for an example).
This sort of macro is often used to implement a sort of "fake functions", where the code is actually inserted in the program instead of called like a function. However, there are two very important considerations here:
- watch out for precedence: safest is to put parentheses around the entire macro (in case the macro is used next to a higher precedence operator) and around every parameter (in case the actual parameter value that the macro is "called" with contains lower precedence operators), as is done in the above example.
- watch out for multiple evaluation of the parameters: in our above example the a and b parameters are evaluated twice, once in the comparison and once in the actual value returned. This may become a problem if the parameters passed to the macro do not just have values, but also side-effects, like e.g. a function call which would in this case be called twice instead of once, or the ++ or -- operators which would in this case increment or decrement the value twice instead of once [*]. If there is any risk of such problems, it's probably a good idea to make your function-like macro a real function; using a macro for

Page : << Previous 17  Next >>