Obviously, in general, you ought to write clean, portable, code.
Something that's bitten me a few times recently is cases in which
implementations were buggy -- rarely, to be fair, in the core C
language, but "standard" system extensions like POSIX conformance.
Does this happen to other people? (The recent example of MSVC++ having
a buggy preprocessor is presumably one example.)
(You'll regret this question.) Yes. I seem to remember the following cases
(all free software (C) by me):
1)
/*
I know about the "%N$*M$lu" conversion specification, but the Tru64 system
I tested on chokes on it, even though it is certified UNIX 98 (I believe):
$ uname -s -r -v -m
OSF1 V5.1 2650 alpha
$ c89 -V
Compaq C V6.5-011 on Compaq Tru64 UNIX V5.1B (Rev. 2650)
Compiler Driver V6.5-003 (sys) cc Driver
http://www.opengroup.org/openbrand/register/brand2700.htm
*/
2)
# Under SUSv2, the word "time" is not a reserved word in the shell. (Or this
# may not be prohibited, but then the reserved word "time" has to support
# option "-p", and it is strange for a reserved word to take an option (think
# "if", "for")). I'm sure SUSv2 doesn't allow a reserved word to shadow a
# standard utility in an incompatible way. Well, some systems certified UNIX 98
# don't seem to care.
#
# ----------------------------------------------------------------------
# Standards, Environments, and Macros standards(5)
#
# SUSv2 superset of SUS extended to sup- Solaris 7
# port POSIX.1b-1993, POSIX.1c-
# 1996, and ISO/IEC 9899 (C Stan-
# dard) Amendment 1
#
#
http://www.opengroup.org/openbrand/register/xx.htm
#
http://www.opengroup.org/openbrand/register/xw.htm
[snip SUSv2 env setup]
# $ time -p true
# sh: -p: not found
# [...]
# $ command -V time
# time is a reserved shell keyword
# $ 'time' -p true
# real 0.00
# user 0.00
# sys 0.00
# $ command time -p true
# Segmentation Fault (core dumped)
# ----------------------------------------------------------------------
#
# So I have to suppress reserved word recognition by quoting at least one
# character of the string "time". 28-JAN-2009 lacos
3)
/*
Albeit we didn't install any signal handlers, on "cygwin-1.5.19-4" these
primitives still get interrupted by (SIGTSTP, SIGCONT). We have to handle
"EINTR" - the semaphore implementation above is compatible, and the
workaround doesn't disturb us on platforms where "EINTR" is not possible in
this context.
Note that we treat "EINTR" specially only in the "*_reel()"'s.
*/
4)
/*
Close "stdout", ignore return value. Don't do this with "Compaq C V6.2-003
on OpenVMS Alpha V7.3-2", because if "stdin" is redirected from a file (not
a terminal), then the following sequence leads, for some reason, to
"%SYSTEM-F-ACCVIO, access violation, reason mask=00, virtual
address=000000000000001C, PC=FFFFFFFF809CE448, PS=0000001B":
(1) fclose(stdout);
(2) STDOUT_FILENO == socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
(3) read(STDIN_FILENO, &x, 1);
(1) was executed here, (2) was executed in "sock_init()", (3) triggered the
crash in "pkt_ckack()".
*/
5) (This comment talks about a SIGSTOP being delivered to the process
while it is blocked in select(), and then continued with SIGCONT. SIGCONT
is caught and handled by the process, thus the correct way should be most
likely to return -1/EINTR.
/*
Otherwise, repeat. On "OSF1 V4.0 1091 alpha", if the process is
stopped inside "select()", and is continued only after the specified
timeout has passed (in real time), "select()" returns 0. In this
case, similar to the one with EINTR, we need to re-"select()".
6)
/*
Originally, the second argument to "recvfrom()" and "sendto()" was "0"
(NULL), but using "Compaq C V6.2-003 on OpenVMS Alpha V7.3-2", the system
insists on a valid address, even if the size of the buffer is zero. No
conditional preprocessing is done, since the current soulution doesn't
hurt on UNIX either.
This workaround is required also for "cygwin-1.5.19-4 + winsock".
Moreover, specifying a non-NULL buffer doesn't suffice, I have to pass a
nonzero buffer size too: "cygwin-1.5.19-4 + winsock" seem unable to
send/receive empty UDP packets.
*/
7) This is about cygwin and Debian/kFreeBSD not knowing some errno macros.
(Not that the related functionality should be supported, for example,
STREAMS with Debian/kFreeBSD, but the symbolic names were mandated by the
SUSv2.)
----------------------------
revision 1.66
date: 2009/12/01 11:09:04; author: lacos; state: Exp; lines: +155 -4; kopt: kv; ...
conditionally compile in errno macros, part1 (lbzip2-0.18-2 doesn't compile
on kfreebsd)
----------------------------
revision 1.65
date: 2009/11/29 12:47:35; author: lacos; state: Exp; lines: +11 -6; kopt: kv; ...
errstr[]:
- beautify error strings for EBUSY, ECHILD, ENOSPC, ERANGE, EXDEV
- ECANCELED is conditional because Cygwin doesn't seem to know it
----------------------------
8) A "getconf" command line bug. After I worked it around for glibc and
also reported it, it hit me again when Debian switched to EGLIBC, and the
fix was not yet ported over.
#
http://sources.redhat.com/bugzilla/show_bug.cgi?id=7095
# Fixed by Ulrich Drepper on 07-FEB-2009.
if ! getconf --version 2>&1 | grep -E -q 'getconf \((GNU libc|EGLIBC)\)' \
|| ! getconf $SPEC"_$1" 2>/dev/null
then
getconf -v $SPEC $SPEC"_$1"
fi
9)
/*
SUSv1 doesn't exclude a mode which was later called LP64. An
example for such an implementation is "OSF1 V4.0 1091 alpha",
where "8 == sizeof(size_t)", and still, socket functions
modifying address lengths take pointers to "size_t" objects. This
shows that it's possible to satisfy SUSv1 and still have a 64-bit
"size_t".
Unfortunately, the following components:
- "Sun C 5.8 2005/10/13"
- "SunOS 5.10 Generic_118822-25 sun4u sparc
SUNW,Ultra-Enterprise-10000"
- "@(#)socket.h 1.74 05/08/02 SMI"
render "socklen_t" visible and different from "size_t" when in
LP64 mode ("-xarch=generic64"), even while in SUSv1. So let's use
this beautiful generic workaround below and elsewhere where
pointers to socket addresses must be passed.
*/
struct msghdr hdr;
hdr.msg_namelen = sizeof local;
if (-1 == getsockname(sock, (struct sockaddr *)&local,
&hdr.msg_namelen))
If the system is FLOSS, I report a bug, at least when I know that the
developers originally intended to support what I was doing. (For example,
FreeBSD, AFAICT, doesn't intend to support SUSv2 explicitly, which is a
completely logical decision, SUSv2 going back to 1997-1998.)
Do you use #ifdefs? Do you include both the correct code and the code
which works on a particular target?
I try to work it around in a way that is also standards conformant and
does no harm on conformant systems.
1) Replace "%N$*M$lu" with more "normal" format specifiers and repeat
printf arguments explicitly that were initially re-used by the original
format specifiers.
2) Write "'time'" instead of "time".
3) Handle -1/EINTR.
4) #ifndef __VMS
5) Don't just believe a select() timeout, check for a delivered SIGCONT
first.
6) Increase UDP packet payload to 1 octet (all bits zero).
7) #ifdef EXXXXXX
8) Make concession towards the known implementation. The default should
remain to complain loudly on non-conformant plaftorms.
9) Abuse "struct msghdr / msg_namelen" having type size_t or socklen_t
exactly in synch with all relevant socket functions taking pointers to
size_t or socklen_t.
If you have to support multiple targets, only some of which are broken
in a given way, do you try to handle determination by testing things in
your code, or outside the code in a build system?
I'd respond to this more wrt. correct but implementation-dependent
behavior. If I can genuinely do a test with the preprocessor, relying only
on standardized macros, I do it that way. Otherwise, I do it in code,
eliciting swaths of "condition always true" and "condition always false /
code will never be executed" warnings from gcc. (They mean "condition
always true *on this platform*", in fact.)
For example, how do you check if time_t is an integer type? The
mathematical value of 1/FLT_RADIX can be represented exactly by all
floating point types, and its value is in (0, 1).
(time_t)(1.0 / FLT_RADIX) == (time_t)0
(Or perhaps scalb(1.0, -1.0) should be used instead of the division.)
I allow my "build systems" to rely only on getconf, make, and sh, all of
which are standard. I don't use autoconf/automake or simliar tools. I test
for known bugs only exceptionally, if writing a common solution
(simultaneously for buggy and correct) is not possible. See the getconf
example.
The example that recently came up involves a UNIX extension. While the
details are unportable, the underlying issue is something you could get
wrong in any environment.
There's a function, which takes function pointers as arguments. (For the
UNIX weenies: scandir().) One of the function pointers is declared
differently on different machines I have access to. So in essence, I
have one system which declares:
extern int foo(int (*compare)(struct foomagic **a, struct foomagic **b));
and another which declares
extern int foo(int (*compare)(void *a, void *b));
scandir() has been standardized by the SUSv4.
http://www.opengroup.org/onlinepubs/9699919799/functions/scandir.html
If you code for SUSvX, x <= 3, I don't know where at all the idea of
scandir() came to you from
SUSv4 dictates the prototype precisely.
(Of course, the GNU project has a completely different stance, as
described by Ben, and it is only logical for them, maintaining a huge code
base, to develop entire libraries of workarounds.)
Imagine that you needed to interact with this function, across these two
systems. How would you do it? Assume for the sake of argument that you
can't compel the vendor to fix the broken implementation, and that "we
don't support that" is not one of your options.
What do you mean by interact? Does your code call scandir(), or does some
external library call back into your_compare()? In the former case, you're
free to implement your own scandir() function with opendir(), readdir(),
closedir(), malloc(), strcoll() or strxfrm(), and qsort().
Of course, if you're paid to meet a deadline, you'll reach for gnulib, if
the license permits, or you'll write an autoconf test.
Cheers,
lacos