Using POSIX feature test macros with C++

Viewed 260

When I'm building POSIX C programs, I want to be portable and use only POSIX or standard C library functions. So, for example, with gcc or clang, I build like this:

gcc -std=c99 -D_XOPEN_SOURCE=600

Setting the standard to C99 removes all extensions, then _XOPEN_SOURCE exposes POSIX interfaces. I no longer have the environment polluted with extensions from GNU, BSD, etc.

However, the waters seem murkier with C++. I want to do this:

g++ -std=c++14 -D_XOPEN_SOURCE=600

This has worked fine for me on various operating systems: Linux/glibc, Haiku, MinGW, macOS, at least. But apparently, there are problems with POSIX feature test macros and C++. Oracle docs have this to say:

C++ bindings are not defined for POSIX or SUSv4, so specifying feature test macros such as _POSIX_SOURCE, _POSIX_C_SOURCE, and _XOPEN_SOURCE can result in compilation errors due to conflicting requirements of standard C++ and these specifications.

While I don't have a copy of Oracle Solaris, I am seeing issues with FreeBSD and OpenBSD.

On FreeBSD:

#include <iostream>
int main() { }
$ clang++ -std=c++14  -D_POSIX_C_SOURCE=200112L t.cpp
In file included from t.cpp:1:
In file included from /usr/include/c++/v1/iostream:37:
In file included from /usr/include/c++/v1/ios:215:
/usr/include/c++/v1/__locale:631:16: error: use of undeclared identifier 'isascii'
        return isascii(__c) ? (__tab_[static_cast<int>(__c)] & __m) !=0 : false;
...

(This builds fine with _XOPEN_SOURCE=600). C++ headers on FreeBSD use isacii, a non-standard function, so it's not exposed when _POSIX_C_SOURCE is set.

Or on OpenBSD:

#include <fstream>
int main() { }
$ clang++ -std=c++14 -D_XOPEN_SOURCE=600 t.cpp
In file included from t.cpp:1:
In file included from /usr/include/c++/v1/fstream:183:
In file included from /usr/include/c++/v1/ostream:138:
In file included from /usr/include/c++/v1/ios:215:
In file included from /usr/include/c++/v1/__locale:32:
In file included from /usr/include/c++/v1/support/newlib/xlocale.h:25:
/usr/include/c++/v1/support/xlocale/__strtonum_fallback.h:23:64: error: unknown type name 'locale_t'
                                                char **endptr, locale_t) {

Presumably <locale.h> isn't getting included somewhere it “should” be.

The worrisome conclusion I'm drawing is that you can't portably have a POSIX C++ environment that is free of non-POSIX extensions. These examples work fine on OpenBSD and FreeBSD if the feature test macros are removed. That looks to be because the BSD headers expose BSD functions unless in standard C mode, but they do not care about standard C++ mode (they explicitly check whether macros corresponding to C89, C99, or C11 are set). glibc looks to be the same: it still exposes non-standard C functions in standard C++ mode, so perhaps it's only a matter of time before I run into a build error there.

So the actual question is this: can you write portable POSIX C++ code which does not expose platform-specific functionality? Or if I'm targeting POSIX with C++ should I just not set any feature test macros and hope for the best?

EDIT:

I got to thinking about the implications of this (as in, why do I care?), and the following program, I think, illustrates it. This is Linux/glibc:

#include <ctime>
int dysize;
$ g++ -c -std=c++14 t.cpp
t.cpp:2:5: error: ‘int dysize’ redeclared as different kind of entity
    2 | int dysize;
      |     ^~~~~~
In file included from t.cpp:1:
/usr/include/time.h:262:12: note: previous declaration ‘int dysize(int)’
  262 | extern int dysize (int __year) __THROW  __attribute__ ((__const__));

This is the standard <ctime> header, which is does not include anything called dysize. That's an old SunOS function that glibc includes for compatibility. A C program built with -std=c99 won't expose it, but C++ always does. And there's no real way of knowing which non-reserved identifiers will be used by various implementations. If -std=c++14 caused non-standard identifiers to be hidden, that would avoid this problem, but it doesn't, and I can't see a way around that.

Which might imply that the feature test macro is a red herring: the source of the problem is that, on at least some real-world implementations, C++ compilers are exposing symbols they're not supposed to.

1 Answers

My suggestion is to build a toolchain, and work from that with the libraries, includes, the correct compiler (perhaps a stripped version that can only use POSIX libraries, includes, etc).

To make it portable, generally you would build the application using static linkers. Other linker options may be necessary that point specifically or include your toolchain environment paths.

And if you're using POSIX threads, you may need -pthread.

I see that you are using system-wide headers and libraries, when really, you probably want a specific to your POSIX application toolchain, to avoid contamination.

Related