/* This code demonstrates the problems that the introduction of the C99 error handling model (math_errhandling) has caused. As far as I can recall, this serious incompatibility (an undeclared Quiet Change) was inserted in the Final Committee Draft, which in itself is a pretty unreasonable activity. It is certainly a gross breach of principles 1, 4 and 9 (as described in the Rationale), even if it is not a breach of ISO or ANSI rules. The example is a function that applies a function provided as an argument to an array, returning 0 for success, 1 if there was an error detected (including failure of the detection mechanism). Note that it isn't bulletproof (especially when run under K&R or extended C90 compilers, or if the error detection is more than usually perverse), but those aspects are both insoluble and unchanged in nature (even if very different in detail) between C90 and C99. Furthermore, note that the core code must be replicated fairly often, and cannot simply be placed at the start and end of a large calculation function. This is because both C90 and C99 explicitly permit both errno and the floating-point exception flags to be set spuriously by almost all C90/C99 non- functions and any non-C90/C99 ones (e.g. non-C I/O or system calls). Experience is that this problem is real. There is also an ambiguity about whether functions like tan() are permitted to set errno and/or the flags spuriously, but I have not seen that happen. Note that __STDC__ is not tested, both because it can't be used to distinguish C90 and C99 (despite repeated requests to WG14) and because it is set weirdly on many systems. __STDC__ = 0 is commonly used to indicate sort-of conformance, for example, and I have seen __STDC__ set to other values. */ #if defined(C90) == defined(C99) #error Exactly one of -DC90 or -DC99 must be set for this test #elif defined(C90) /* ==================== Start of C90 example ==================== */ #include #include int apply (double array[], size_t length, double (*function)(double)) { /* Note that this uses errno alone, as the implementation-defined value returned on a domain error (and even HUGE_VAL) can be normal values, such as 0.0, and Java encourages that. Also, there is no way for a portable function to check against an implementation-defined value! This will also work with all K&R compilers that support errno error flagging, which is almost all that handle errors at all! */ size_t i; /* ========== Start of core code ========== */ errno = 0; for (i = 0; i < length; ++i) array[i] = function(array[i]); return (errno != 0); /* ========== End of core code ========== */ } /* ==================== End of C90 example ==================== */ #elif defined(C99) /* ==================== Start of C90/C99 example ==================== */ #include #include #include #include /* Note that the following code is NOT only for migration, but is needed even if we can assume that we have a C99 compiler, because we still have to deal with the case of math_errhandling == MATH_ERREXCEPT and __STD_IEC_559__ unset. Of course, C99 does not actually define what that MEANS, so we have to assume that the implementor has done what we expect. */ #if MATH_ERRNO == 1 || MATH_ERREXCEPT == 2 || defined(__STD_IEC_559__) #if MATH_ERRNO != 1 || MATH_ERREXCEPT != 2 #error This implementation has inconsistent error handling #endif #include #pragma STDC FENV_ACCESS ON #if defined(FE_OVERFLOW) && defined(FE_INVALID) &&defined(FE_DIVBYZERO) #define OK_FLAGS 1 #endif #define TRY_FLAGS 1 #endif int apply (double array[], size_t length, double (*function)(double)) { size_t i; /* Start with checking that we CAN handle errors. While C99 doesn't permit an implementation to set neither MATH_ERRNO nor MATH_ERREXCEPT, the wording is not as clear as it could be, and it is quite likely. In that case, we are dead (see above for why). Note that the Annex F definition of HUGE_VAL and the domain error return value does not help, as the case we are testing for is when we are in C99 but not full IEEE mode. We also check for what should be an impossible state. */ #if TRY_FLAGS if (! (math_errhandling&(MATH_ERRNO|MATH_ERREXCEPT)) || ((math_errhandling&MATH_ERREXCEPT) && ! OK_FLAGS)) abort(); #endif /* Note that the core code does not check for the consistency of the error flagging, and that supporting C90 adds no complexity over that needed to support all of C99. It is extremely unlikely that more than a few programmers will ever bother with error handling of this complexity, let alone get it correct, yet it is the minimum necessary to comply with C99's contortions. */ /* ========== Start of core code ========== */ #if TRY_FLAGS if (math_errhandling&MATH_ERRNO) errno = 0; if (math_errhandling&MATH_ERREXCEPT) feclearexcept(FE_ALL_EXCEPT); #else errno = 0; #endif for (i = 0; i < length; ++i) array[i] = function(array[i]); #if TRY_FLAGS if ((math_errhandling&MATH_ERRNO) && errno != 0) return 1; if ((math_errhandling&MATH_ERREXCEPT) && fetestexcept(FE_OVERFLOW|FE_INVALID|FE_DIVBYZERO)) return 1; return 0; #else return (errno != 0); #endif /* ========== End of core code ========== */ } /* ==================== End of C90/C99 example ==================== */ #endif