Updated in 2024-01.
In ISO C++ standards, [support.c.headers.general] says:
Source files that are not intended to also be valid ISO C should not use any of the C headers.
Then, [depr.c.headers] describes how a C header name.h
is transformed to the corresponding C++ cname
header. There
is a helpful example:
[ Example: The header
assuredly provides its declarations and definitions within the namespace std. It may also provide these names within the global namespace. The header <stdlib.h> assuredly provides the same declarations and definitions within the global namespace, much as in the C Standard. It may also provide these names within the namespace std. — end example ]
"may also" in the wording allows implementations to provide
mix-and-match, e.g. #include <stdlib.h>
may provide
std::exit
and #include <cstdlib>
may
provide ::exit
.
libstdc++ chooses to enable global namespace declarations with C++
cname
header. For example,
#include <cstdlib>
also includes the corresponding C
header stdlib.h
and we get declarations in both the global
namespace and the namespace std. 1
2. /usr/include/c++/12/cstdlib
.. /usr/include/stdlib.h
The preprocessed output looks like: 1
2
3
4
5
6
7
8
9extern void exit (int __status) noexcept (true) __attribute__ ((__noreturn__));
extern "C++"
{
namespace std __attribute__ ((__visibility__ ("default")))
{
using ::exit;
}
}
The compiler knows that the declarations in the namespace
std
are identical to the ones in the global namespace. The
compiler recognizes some library functions and can optimize them. By
using
the compiler can optimize some C library functions in
the namespace std
(e.g. many std::mem*
and
std::str*
functions).
For some C standard library headers, libstdc++ provides wrappers
(libstdc++-v3/include/c_compatibility/
) which take
precedence over the glibc headers. The configuration of libstdc++ uses
--enable-cheaders=c_global
by default. if GLIBCXX_C_HEADERS_C_GLOBAL
in
libstdc++-v3/include/Makefile.am
describes that the 6
wrappers
(complex.h, fenv.h, tgmath.h, math.h, stdatomic.h, stdlib.h
)
shadow the C library headers of the same name. For example,
#include <stdlib.h>
includes the wrapper
stdlib.h
which includes cstdlib
, therefore
bringing exit
into the namespace std. 1
2
3. /usr/include/c++/12/stdlib.h
.. /usr/include/c++/12/cstdlib
... /usr/include/stdlib.h
In all --enable-cheaders
modes,
#include <cstdlib>
also includes the corresponding C
header, therefore we get declarations in both the global namespace and
the namespace std. 1
2. /usr/include/c++/12/cstdlib
.. /usr/include/stdlib.h
The mix-and-match mechanism looks gross, but it has been needed for compatibility in the ancient days. Nowadays, I can imagine widespread breakage if we drop the mix-and-match.
string.h and cstring
In ISO C standards before N3020, [String handling] lists:
1 | void *memchr(const void *s, int c, size_t n); |
Note that the return types do not have the const qualifier, e.g.
memchr
would better return a const void *
.
Unfortunately that is not the case likely due to two reasons:
First, there hadn't been the const
qualifier in The C
Programming Language (1st edition, 1978). The second edition of the C
Programming Language (1988) introduced the const qualifier, added the
const qualifier to some arguments, but did not change the return type,
probably because that would break code.
Second, the const input non-const output signature provides the best
usability in the absence of overloading. strchr
works with
both examples below: 1
2
3
4
5char *str = ...;
char *pos = strchr(str, 'a');
const char *str = ...;
const char *pos = strchr(str, 'a');
Anyhow, C++ decided to "correct" the signatures. I do not know when that decision was made. In the current ISO C++ standard, [library.c] says
The descriptions of many library functions rely on the C standard library for the semantics of those functions. In some cases, the signatures specified in this document may be different from the signatures in the C standard library, and additional overloads may be declared in this document, but the behavior and the preconditions (including any preconditions implied by the use of an ISO C restrict qualifier) are the same unless otherwise stated.
[cstring.syn] lists these functions with different signatures:
1 | namespace std { |
There is an apparent attempt to correct the mistakes as evidenced by the note:
[Note 1: The functions strchr, strpbrk, strrchr, strstr, and memchr, have different signatures in this document, but they have the same behavior as in the C standard library. — end note]
Qualifier-preserving standard library functions (N3020) has changed these functions to be generic functions to preserve the qualifier.
In glibc string.h
, some extensions have similar
overloads: rawmemchr, strchrnul, strcasestr, basename
.
wchar.h and cwchar
Similar differences apply to wchar.h
vs
cwchar
. C++ [cwchar.syn] lists the following functions with
different signatures:
1 | namespace std { |
[Note 1: The functions wcschr, wcspbrk, wcsrchr, wcsstr, and wmemchr have different signatures in this document, but they have the same behavior as in the C standard library. — end note]
glibc
__CORRECT_ISO_CPP_STRING_H_PROTO
To fix string.h
signatures for C++, in 2009,
__CORRECT_ISO_CPP_STRING_H_PROTO
was
added to provide the correct overloads. Nowadays
string.h
looks like:
1 | /* Tell the caller that we provide correct C++ prototypes. */ |
In C++ mode, there are two extern "C++"
overloads for
memchr
. To prevent C++ name mangling and redirect them to
the symbol memchr
, asm labels
(__asm ("memchr")
) are used.
(__glibc_clang_prereq (3, 5)
was added in 2019 as the
resolution to https://sourceware.org/bugzilla/show_bug.cgi?id=25232.)
The code fragment is used together with the following fragment in
libstdc++ cstring
: 1
2
3
4
5
6
7extern "C++"
{
namespace std __attribute__ ((__visibility__ ("default")))
{
using ::memchr;
}
}
With the glibc and libstdc++ cooperation, the following code (adapted
from
libstdc++-v3/testsuite/21_strings/c_strings/char/3_neg.cc
)
will get a compile error. 1
2
3
4char *v1;
const char *cv2;
v1 = std::memchr (cv2, '/', 3); // { dg-error "invalid conversion" }
If we modify glibc string.h
by undefining
__CORRECT_ISO_CPP_STRING_H_PROTO
, the compile error will go
away.
IMO it would look better if libstdc++ removed
using ::memchr
and declared the overloads in the namespace
std. libc++ took this approach in 2016. Actually it can use asm labels
to avoid __libcpp_memchr
: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
extern "C++" {
inline _LIBCPP_INLINE_VISIBILITY
void* __libcpp_memchr(const void* __s, int __c, size_t __n) {return (void*)memchr(__s, __c, __n);}
inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_PREFERRED_OVERLOAD
const void* memchr(const void* __s, int __c, size_t __n) {return __libcpp_memchr(__s, __c, __n);}
inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_PREFERRED_OVERLOAD
void* memchr( void* __s, int __c, size_t __n) {return __libcpp_memchr(__s, __c, __n);}
glibc
__CORRECT_ISO_CPP_WCHAR_H_PROTO
There is a similar macro __CORRECT_ISO_CPP_WCHAR_H_PROTO
for wchar.h
.
A libc++ change https://reviews.llvm.org/D148542 accidentally made
__CORRECT_ISO_CPP_WCHAR_H_PROTO
undefined when
iosfwd
is included first. glibc before 2.26 did not provide
bits/types/mbstate_t.h
.
If we include iosfwd
before cwchar
, we will
include glibc wchar.h
without defining
__CORRECT_ISO_CPP_WCHAR_H_PROTO
and will get the
"incorrect" declarations.
1 |
|
1 | % clang --sysroot=glibc-2.25 -stdlib=libc++ -c test.cc # expected error |
If we change the libc++ module.modulemap
file (in my
build, /tmp/Rel/include/c++/v1/module.modulemap
) to be a
textual header, or just remove the entry from the modulemap file,
1
module __mbstate_t { private textual header "__mbstate_t.h" export * }
the following command will fail. (Yes, we use -x c++
even when compiling a modulemap file, otherwise Clang will consider the
modulemap file an object file for linking.) 1
2
3
4
5
6
7
8
9% clang --sysroot=glibc-2.25 -stdlib=libc++ -c -Xclang -emit-module -fmodules -fmodule-name=std -Xclang=-fmodules-local-submodule-visibility -xc++ /tmp/Rel/include/c++/v1/module.modulemap -o std.pcm
...
In file included from /tmp/Rel/bin/../include/c++/v1/__mbstate_t.h:35:
glibc-2.25/include/wchar.h:227:17: error: functions that differ only in their return type cannot be overloaded
extern wchar_t *wcschr (const wchar_t *__wcs, wchar_t __wc)
~~~~~~~~~^
glibc-2.25/include/wchar.h:224:29: note: previous declaration is here
extern "C++" const wchar_t *wcschr (const wchar_t *__wcs, wchar_t __wc)
~~~~~~~~~^
We can write a modulemap file that compiles just two headers.
1
2
3
4
5
6
7
8
9
10mkdir stl
echo '#include_next <cwchar>' > stl/cwchar
echo '#include_next <iosfwd>' > stl/iosfwd
cat > stl.modulemap <<e
module "stl" {
export *
module "stl/cwchar" { export * header "stl/cwchar" }
module "stl/iosfwd" { export * header "stl/iosfwd" }
}
e
1 | % clang --sysroot=glibc-2.25 -stdlib=libc++ -c -Xclang -emit-module -fmodules -fmodule-name=stl -Xclang=-fmodules-local-submodule-visibility -xc++ stl.modulemap -o stl.pcm |
stdatomic.h
C11 defines stdatomic.h
which defines several macros and
declares several types and functions for performing atomic operations on
data shared between threads. The header is shipped with the compiler
(GCC, Clang).
https://wg21.link/P0943 (C++23) defines the semantics
including stdatomic.h
in C++. Implementations:
- libstdc++: https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=d7f2a09e98520c4e4927f460ad96803b05a205b1
- libc++: https://reviews.llvm.org/D97044
1 |
This above code works with C++23. In older language modes, all of
g++
, clang++ -stdlib=libstdc++
, and
clang++ -stdlib=libc++
report errors.
1 |
This above code works with C++23. In older language modes, both
g++
and clang++ -stdlib=libstdc++
report
errors; clang++ -stdlib=libc++
accepts the code.
using_if_exists
libc++ utilizes a Clang
extension using_if_exists
to import a declaration only
when the global namespace has such a declaration, avoiding an error in
case the declaration does not exist.
1 | using ::sqrt _LIBCPP_USING_IF_EXISTS; |