|
P99
|
P99 implements a lot of different features through macros and functions, too much to mention explicitly in such an overview. You will find a structured hierarchy of descriptions below the "Modules" tag and the documentation of the individual items under "Files" -> "Globals". Here we will introduce some main features:
In section Use of temporary lvalues we have seen a way to provide default arguments to functions by overloading them with macros. The general declaration pattern here is as follows
#define NAME(...) P99_CALL_DEFARG(NAME, N, __VA_ARGS__)
Where NAME becomes the name of a macro and where we also suppose that there is already a function of the same name NAME.
Other than this would be in C++, the default value for the ::hostname macro that we had given was itself produced by a macro, namely hostname_defarg_0. The convention here is as simple as that:
NAME ## _defarg_ ## M ()
NAME with the token _defarg_ and the decimal number M N As we have seen in the example (a) is computed in the context of the caller. This let us simply use a temporary (here a local compound literal) that was thus valid in that context.
To obtain the same behavior as for C++, namely to provide a default argument that is evaluated at the place of declaration and not at the place of the call we have to use (b), a function call. This will be as efficient as macro call if we use Inline functions for that purpose.
To ease the programming of this functional approach P99 provides some machinery. We need three things as in the following example:
P99_PROTOTYPE(rand48_t *, rand48_t_init, rand48_t*, unsigned short, unsigned short, unsigned short); #define rand48_t_init(...) P99_CALL_DEFARG(rand48_t_init, 4, __VA_ARGS__) P99_DECLARE_DEFARG(rand48_t_init, , useconds(), getpid(), atomic_fetch_add(&rand48_counter) );
That are
Here in the example there is no default argument for position 0 but one for positions 1 to 3. All three have the type unsigned short. The above leads to the automatic generation of three inline function that look something like:
inline unsigned short rand48_t_init_defarg_1(void) { return useconds(); } inline unsigned short rand48_t_init_defarg_2(void) { return getpid(); } inline unsigned short rand48_t_init_defarg_3(void) { return atomic_fetch_add(&rand48_counter); }
This declaration and definition is placed in the context of the above declaration and not in the context of the caller. Thus the expression is evaluated in that context, and not in the context of the caller. In particular for the third function, this fixes the variable rand48_counter to the one that is visible at the point of declaration.
Resource management can be tedious in C. E.g to protect a critical block from simultaneous execution in a threaded environment you'd have to place a lock / unlock pair before and after that block:
mtx_t guard; mtx_init(&guard); mtx_lock(&guard); // critical block comes here mtx_unlock(&guard);
This is very much error prone since you have to provide such calls every time you have such a block. If the block is longer than some lines it is difficult to keep track of that, since the lock / unlock calls are spread on the same level as the other code.
Within C99 (and equally in C++, BTW) it is possible to extend the language of some sorts such that you may make this easier visible and guarantee that your lock / unlock calls are matching. Below, we will give an example of a macro that will help us to write something like
P99_PROTECTED_BLOCK(mtx_lock(&guard), mtx_unlock(&guard)) { // critical block comes here }
To make this even a bit more comfortable we have
P99_MUTUAL_EXCLUDE(&guard) { // critical block comes here }
There is an equivalent block protection that uses an atomic_flag as a spin lock. Such a spin lock only uses atomic operations and can be much more efficient than a protection through a mtx_t, if the code inside the critical section is really small and fast:
P99_SPIN_EXCLUDE(&cat) { // critical block comes here }
For cases where the atomic_flag variable would be specific to the block, you don't even have to define it yourself:
P99_CRITICAL { // critical block comes here }
Generally there should be no run time performance cost for using such a macro. Any decent compiler will detect that the dependent code is executed exactly once, and thus optimize out all the control that has to do with our specific implementation of theses blocks.
Other such block macros that can be implemented with such a technique:
An even more sophisticated tool for scope bound resources management is provided by the macro P99_UNWIND_PROTECT
double toto(double x) { P99_UNWIND_PROTECT { // do something while (cond0) { for (;cond1;) { if (cond2) P99_UNWIND(-1); // preliminary return if (cond3) P99_UNWIND_RETURN 5.7777E-30; } } P99_PROTECT : // do some cleanup here // if everything went well ::p99_unwind_code has value 0 otherwise it // receives a value from P99_UNWIND } // regular return return x * x; }
In this code fragment the statement P99_UNWIND will ensure that the two levels of loops are broken and that the execution continues at the special label P99_PROTECT.
P99_UNWIND_RETURN goes one step further. As for P99_UNWIND, it executes the clause after P99_PROTECT, but when it reaches the end of the P99_UNWIND_PROTECT scope it will return to the caller with a return value as specified after P99_UNWIND_RETURN, here the value 5.7777E-30.
We provide some utilities to ease the programming of loop iterations in one or multiple dimensions. The simplest to use is P99_DO, that resembles much to a do loop in fortran. It fixes the bounds of the iteration once, before entering the iteration itself.
P99_DO(size_t, i, a, n, inc) { A[i] *= B[i-1] }
::P99_FORALL allows to generate nested for loops over arbitrary many dimensions:
size_t const D[3] = { 20, 17, 31 }; P99_FORALL(D, i, j, k) { A[i][j][k] *= B[i][j][k]; }
will iterate over all combinations of i, j, k in the bounds specified by D.
P99_PARALLEL_FOR, where available, will provide a parallelized version of a simple for loop, and P99_PARALLEL_DO and ::P99_PARALLEL_FORALL implements nested parallel loops with otherwise the same semantics as for P99_DO or ::P99_FORALL, respectively.
P99 provides you with macro features that can become handy if you have to generate code repetition that might be subject to changes, later. As examples suppose that you'd have to code something like
tata = A[0]; tete = A[1]; titi = A[2]; toto = A[3]; typedef int hui_0; typedef unsigned hui_1; typedef double hui_2;
If over time there are many additions and removals to these lists, maintaining such a code will not really be a pleasure. In P99 you may write equivalent statements and declarations just as
P99_VASSIGNS(A, tata, tete, titi, toto); P99_TYPEDEFS(hui, int, unsigned, double);
There are a handful of such predefined macros that you may look up under Produce C99 statements or expression lists. Under the hood they all use a more general macro that you may yourself use to define your own macros: ::P99_FOR. The use of this will be described more in detail under Macro programming with P99.
The predefined macros from above usually are also able a nasty special case if the variadic part of the argument list is empty. Something like
P99_VASSIGNS(A); P99_TYPEDEFS(hui);
would at least cause a warning with conforming preprocessors if the macros were implemented directly with something like
#define P99_VASSIGNS(NAME, ...) do_something_here #define P99_TYPEDEFS(NAME, ...) do_something_else_here
since the variable length part should not be empty, according to the standard. With P99 you don't have these sort of problems, the above should just result in empty statements or declarations, that are even capable to swallow the then superfluent semicolon at the end.
P99 avoids this by testing for the length of the argument list as a whole with P99_NARG and by using a macro conditional controlled by that length. Such conditionals like P99_IF_EMPTY ensure that the preprocessor decides which code of two different variants the compiler will see. The fragment
P99_IF_EMPTY(BLA)(special_version)(general_version)
will expand to either special_version or general_version according to BLA. If it expands to an empty token, the first variant is produced, if it is at least one non-empty token the second one results.
P99 also implements logical and arithmetic operations in the preprocessor. Logical operations just evaluate to the tokens 0 or 1. Arithmetic is restricted to small decimal numbers, less than P99_MAX_NUMBER. Just some examples
See Preprocessor operations for more about that.
Consistent initialization of variables is an important issue in C. P99 provides some tools to help you on that, most importantly a macro P99_NEW. Therefore we have to relay on some assumptions that are specified in Variable initialization, in particular that there is an `init' function for each type that we want to use with P99_NEW.
For the example type of a circular list element
// Forward declare struct elem and elem typedef struct elem elem; . . . struct elem { elem* pred; elem* succ; };
we might want to ensure that the fields pred and succ are always properly initialized. An `init' function could look as follows:
#define ELEM_INITIALIZER(HERE, PRED, SUCC) {
.pred = (PRED) ? (PRED) : (HERE),
.succ = (SUCC) ? (SUCC) ; (HERE),
}
A static initialization of a 4 element list in file scope can then be done as
extern elem * head; . . static elem L0; static elem L1; static elem L2; static elem L3; static elem L0 = ELEM_INITIALIZER(&L0, &L1, &L3); static elem L1 = ELEM_INITIALIZER(&L1, &L0, &L2); static elem L2 = ELEM_INITIALIZER(&L2, &L1, &L3); static elem L3 = ELEM_INITIALIZER(&L3, &L2, &L0); head = &L0;
Dynamic initialization of a 4 element list on the stack in function scope
elem L[4] = {
[0] = ELEM_INITIALIZER(&L[0], &L[1], &L[3]),
[1] = ELEM_INITIALIZER(&L[1], &L[0], &L[2]),
[2] = ELEM_INITIALIZER(&L[2], &L[1], &L[3]),
[3] = ELEM_INITIALIZER(&L[3], &L[2], &L[0]),
};
To do dynamic initialization we would then define something like this:
elem * elem_init(elem* here, elem* there) {
if (here) {
if (there) {
here->pred = there;
here->succ = there->succ;
there->succ = here;
here->succ->pred = here;
} else {
here->pred = here;
here->succ = here;
}
}
return here;
}
Initializations of this type of heap variables in function scope can now simply look like this
or
These define cyclic lists of 3 elements, well initialized and ready to go.
In fact, the P99_NEW macro takes a list of arguments that may be arbitrarily 3 long. It just needs the first, which is supposed to be the type of the object that is to be created. The other are then passed as supplementary arguments to the `init' function, here the parameter there.
If the `init' function accepts default arguments to some parameters, so will P99_NEW. With Default arguments and types for functions, calls to P99_NEW may then omit the second argument:
#define elem_init(...) P99_CALL_DEFARG(elem_init, 2, __VA_ARGS__) #define elem_init_defarg_1() P99_0(elem*) . . . elem * a = P99_NEW(elem); elem * head = P99_NEW(elem, P99_NEW(elem, P99_NEW(elem)));
1.7.6.1