Bug 100128 - Behavior and performance depends on order of ctype.h and stdlib.h include
Summary: Behavior and performance depends on order of ctype.h and stdlib.h include
Status: NEW
Alias: None
Product: gcc
Classification: Unclassified
Component: libstdc++ (show other bugs)
Version: 10.3.0
: P3 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2021-04-17 00:40 UTC by Travis Downs
Modified: 2021-04-22 14:26 UTC (History)
1 user (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2021-04-22 00:00:00


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Travis Downs 2021-04-17 00:40:25 UTC
When ctype.h is included as the first header in a file, it will be processed without __NO_CTYPE being defined. This results in several differences versus the case where __NO_CTYPE is defined.

For example, toupper() is defined as extern inline or as a macro if __NO_CTYPE is undefed, but is not defined (only declared), otherwise. 

As another example, is_alnum_l and many similar methods will be defined as macros if __NO_CTYPE is undefined, but otherwise will not.

On the other hand, if you include stdlib.h (or many other files such as <algorithm>) in a C++ compile, the C++ "override" file include/c++/10.3.0/stdlib.h gets included, which ultimately ends up including x86_64-linux-gnu/bits/os_defines.h which defines __NO_CTYPE.

If <ctype.h> is subsequently included, its effect is different as described above.

I suppose this is an ODR violation in one way or another (e.g., if two files are included in the same program with and without __NO_CTYPE), and it can also have a significant impact on performance as described here:

https://travisdowns.github.io/blog/2019/11/19/toupper.html

Evidently, the behavior and definitions exposed by these headers should not depend on the order of include. I suspect there are other cases besides the __NO_CTYPE as long as files that don't trigger the C++ header include chain like ctype.h exist.

You can play with this example on godbolt:

https://godbolt.org/z/vY4EnE51z

Try swapping the order of ctype and stdlib includes to see the effect. The int variables are canaries so you can see which macros were defined in the preprocessed output.

This is the same as glibc bug #25214, but I was advised over there than this should be filed against libstdc++ instead.

https://sourceware.org/bugzilla/show_bug.cgi?id=25214
Comment 1 Jonathan Wakely 2021-04-17 21:24:25 UTC
(In reply to Travis Downs from comment #0)
> Evidently, the behavior and definitions exposed by these headers should not
> depend on the order of include. I suspect there are other cases besides the
> __NO_CTYPE as long as files that don't trigger the C++ header include chain
> like ctype.h exist.

I think that's the only one.

Glibc should probably act as though __NO_CTYPE is implicitly defined by __cplusplus, so that the effect is independent of the order of includes.

Alternatively, libstdc++ could add its own <ctype.h> wrapper to ensure that we always include os_defines.h before the libc <ctype.h>.
Comment 2 Jonathan Wakely 2021-04-17 21:26:47 UTC
(In reply to Jonathan Wakely from comment #1)
> Glibc should probably act as though __NO_CTYPE is implicitly defined by
> __cplusplus, so that the effect is independent of the order of includes.

Or it should define the ctype functions as C++ inline functions (without macros hiding their names) so they can still be inlined.
Comment 3 Jonathan Wakely 2021-04-22 14:26:31 UTC
(In reply to Jonathan Wakely from comment #1)
> Alternatively, libstdc++ could add its own <ctype.h> wrapper to ensure that
> we always include os_defines.h before the libc <ctype.h>.

Hmm, we already have this, but it's not installed.