|
P99
|
As extensions to C89, C99 offers some features that largely improve its possibilities of programming efficiently and portable at the same time. There are four of these new concepts that are particularly important for P99, without them P99 wouldn't be possible.
The preprocessor now has a feature that previously already C functions could have: a macro may accept an argument list that may vary in size. As for functions such a macro is defined with `...' in the argument list to indicate a list following the initial, named, arguments:
#define TOTO(NAME, ...) NAME[__VA_ARGS__]
The variable length list then is referred by the reserved identifier __VA_ARGS__.
This functionality of C99 allows us e.g to implement macros for Default arguments to functions and to do Code Unrolling.
The new keyword inline is a borrow from C++ with some slightly changed semantics. The important part for P99 is that functions can be defined in a header file (an not only declared) when specified as inline.
This allows us to define small wrappers in include files through macros, without generating conflicts in different compilation units. By that we can avoid one of the major drawbacks of C macro programming: a macro cannot define another macro. In addition functions, compared to macros, have other advantages
In C89, initialization of structures can be tedious and error prone:
typedef struct toto toto; struct toto { unsigned a; double b; }; . . toto A = { 0, 1 };
Components are initialized in the order of the type declaration. Here the 0 in the initializer is used to initialize the component A.a and the 1 for A.b.
Whenever the structure toto changes during a development process, we would have to revisit all initializations to see whether or not the are still consistent:
a and b changes, the order of the expressions must be inverseda or b, the initialization of b by 1 in the example is replaced by the default initialization, namely 0.0.Keeping track of these may be particularly difficult if the components are of similar types, such that an initializer for one is valid for the other.
With designated initializers this situation changes substantially:
toto A = { .a = 0, .b = 1 };
By that we avoid all of the problems that are mentioned above. This scheme is robust against reordering and insertion of components. In a certain sense it is also robust against the renaming of components: all initializations will then simply fail at compile time, so it is easy to identify the problem spots.
For a more detailed discussion of initialization and P99 see Variable initialization.
A compound literal is syntactically given as a compound initializer and a cast such as
(int[2]){ 4, 5}
(T){ .d = 1, .a = 10 }.
It is best seen to define a temporary object of the requested type, initialized with the same rules as would a named variable of that type.
Example: The following returns the pointer to a character array that is initialized with all `a', a terminating 0 character and that is a valid objet until the program leaves the current block.
char const*const hui = memset((char[256]){0}, 'a', 255);
It would be equivalent to the following
char tmp[256] = { 0 }; char const*const hui = memset(tmp, 'a', 255);
Using the compound literal here has the advantage that no other non-const reference to the temporary is exposed.
The compound literal syntax is not always very easy to read in fact it might even hurt your eyes. P99 gives you a shortcut for compound literals that are initialized from the all 0 initializer. With that the above could have been written:
char const*const hui = memset(P99_LVAL(char[256]), 'a', 255);
Per se, this is not a new feature of C99 but had been present before. The preprocessor has two special rules, one that applies generally to macros and the other that applies on to functional macros:
Theses features can be used to define a macro and another identifier that have the same name. It is sometimes used to all for a test if some functionality is present on a platform. E.g on my computer I have
#define stdin stdin
This can be used as follows
#ifdef stdin // Do something for an hosted environment // Use stdin as usual #else // Do something for a free standing environment // We don't have stdin at all, write to a log file or so. #endif
But we may equally use this technique for a function symbol. POSIX explicitly allows this for example for the functions in stdio.h
Lets have a look at some random function from there and suppose it would be given as follows:
int putc(int, FILE *); #define putc(C, F) (is_it_special(C) ? do_something_clever(C, F) : putc(C, F) )
(Yes this evaluates C two times.) With that, these uses of putc are still valid:
// Use the macro and implicitly the function, relies on rule 1 putc('A', stdout); // Just use the function not the macro, relies on rule 2 (putc)('A', stdout); // Get the address of putc and store it in my_putc, relies on rule 2 int (*my_putc)(int, FILE*) = &putc;
The example above with putc has a particular pitfall if we have the above definitions in a header file and then include this file at the place where we define the the function:
#include <stdio.h> int putc(int, FILE *) { // do the right thing here }
This will simply explode since the preprocessor will expand the functional reference to putc. This can be explicitly avoided by undefining the macro before the definition, but for this the implementor of putc has to know that it is also a macro.
With P99, we use this technique to overload a function to provide it with Default arguments to functions. A macro defined in that way will avoid this pitfall: if it is called with the same number of arguments (or more) that all are non-empty, it will produce the same token sequence as if the macro had not been defined.
The traditional approach of C had been to specify meta information for the compiler in so called pragmas:
#pragma omp parallel for for (size_t i = 0; i < n; ++i) c[i] += a[i] * b[i];
The inconvenience of such a construct is that it has always to be on a line of its own and could not be placed in a macro. For that reason most compilers provided extensions that let the programmer place meta information more precisely at some specific point of the code, e.g gcc has an __attribute__ extension for that.
C99 adds a keyword to overcome that difficulty and to normalize the link between macros and #pragma: _Pragma.
_Pragma("omp parallel for") for (size_t i = 0; i < n; ++i) c[i] += a[i] * b[i];
P99 uses this feature for extensions concerning OpenMP, in particular the P99_PARALLEL_FOR and ::P99_PARALLEL_FORALL macros.
1.7.6.1