From 89341602bbcb808995932a1f57725a585cd4b1a4 Mon Sep 17 00:00:00 2001
From: Benjamin Kosnik
-template<typename _Tp, typename _Allocator = std::allocator<_Tp> +template<typename _Tp, typename _Allocator = allocator<_Tp> class debug-list : public release-list<_Tp, _Allocator>, public __gnu_debug::_Safe_sequence<debug-list<_Tp, _Allocator> > @@ -309,12 +309,14 @@ template<typename _Tp, typename _Allocator = std::allocator<_Tp>Achieving link- and run-time coexistence is not a trivial implementation task. To achieve this goal we required a small - extension to the GNU C++ compiler (described in the section on - link- and run-time coexistence) and complex - organization of debug- and release-modes. The end result is that we - have achieved per-use recompilation but have had to give up some - checking of the
std::basic_string
class template - (namely, safe iterators). + extension to the GNU C++ compiler (described in the GCC Manual for + C++ Extensions, see strong + using), and a complex organization of debug- and + release-modes. The end result is that we have achieved per-use + recompilation but have had to give up some checking of the +std::basic_string
class template (namely, safe + iterators).Compile-time coexistence of release- and debug-mode components
@@ -322,95 +324,129 @@ template<typename _Tp, typename _Allocator = std::allocator<_Tp> components need to exist within a single translation unit so that the debug versions can wrap the release versions. However, only one of these components should be user-visible at any particular - time with the standard name, e.g.,std::list
. In - release mode, we define only the release-mode version of the - component with its standard name and do not include the debugging - component at all (except, perhaps, in__gnu_debug
, if - requested via the separate debugging headers). This method leaves the - behavior of release mode completely unchanged from its behavior - prior to the introduction of the libstdc++ debug mode. + time with the standard name, e.g.,std::list
. -In debug mode we include the release-mode container into its - natural namespace but perform renaming to an implementation-defined - name using preprocessor macros. Thus the - release-mode
+std::list
will be renamed - tostd::_Release_list
during debug mode, and we will - automatically include the debugging version with the - namestd::list
for users to reference. This method - allows the debug- and release-mode versions of the same component to - coexist at compile-time without causing an unreasonable maintenance - burden.In release mode, we define only the release-mode version of the + component with its standard name and do not include the debugging + component at all. The release mode version is defined within the + namespace
+ +__gnu_nom
, and then associated with namespace +std
via a "strong using" directive. Minus the + namespace associations, this method leaves the behavior of release + mode completely unchanged from its behavior prior to the + introduction of the libstdc++ debug mode. Here's an example of what + this ends up looking like, in C++.+namespace __gnu_norm +{ + using namespace std; + + template<typename _Tp, typename _Alloc = allocator<_Tp> > + class list + { + // ... + }; +} // namespace __gnu_norm + +namespace std +{ + using namespace __gnu_norm __attribute__ ((strong)); +} ++ +In debug mode we include the release-mode container and also the +debug-mode container. The release mode version is defined exactly as +before, and the debug-mode container is defined within the namespace +
+ +__gnu_debug
, which is associated with namespace +std
via a "strong using" directive. This method allows +the debug- and release-mode versions of the same component to coexist +at compile-time without causing an unreasonable maintenance burden, +while minimizing confusion. Again, this boils down to C++ code as +follows:+namespace __gnu_norm +{ + using namespace std; + + template<typename _Tp, typename _Alloc = allocator<_Tp> > + class list + { + // ... + }; +} // namespace __gnu_norm + +namespace __gnu_debug +{ + using namespace std; + + template<typename _Tp, typename _Alloc = allocator<_Tp> > + class list + : public __gnu_norm::list<_Tp, _Alloc>, + public __gnu_debug::_Safe_sequence<list<_Tp, _Alloc> > + { + // ... + }; +} // namespace __gnu_norm + +namespace std +{ + using namespace __gnu_debug __attribute__ ((strong)); +} +Link- and run-time coexistence of release- and debug-mode components
-There is a problem with the simple compile-time coexistence - mechanism: if a user compiles some modules with release mode and - some modules with debug mode, the debuggable components will differ - in different translation units, violating the C++ One Definition - Rule (ODR). This violation will likely be detected at link time, - because the sizes of debug-mode containers will differ from the - sizes of release-mode containers, although in some cases (such as - dynamic linking) the error may be detected much later (or not at - all!).
-Unfortunately, it is not possible to avoid violating the ODR with - most debug mode designs (see the section on alternatives for coexistence), so the - philosophy of the libstdc++ debug mode is to acknowledge that there - is an unavoidable ODR violation in this case but to ensure that the - ODR violation does not affect execution. To accomplish this, the - libstdc++ debug mode uses the aforementioned preprocessor renaming - scheme but includes an additional renaming scheme that happens at - compile-time that essentially reverses the preprocessor - renaming from the linker's point of view. Thus, in debug - mode, the release-mode
+list
container is - namedstd::_Release_list
but will be mangled with the - namestd::list
(as it was in release mode). Similarly, - the debug-modelist
is namedstd::list
- (in debug mode) but will be mangled - asstd::_Debug_list
. Thus the - release-modelist
always compiles down to code that - uses the namestd::list
, and the - debug-modelist
always compiles down to code that uses - the namestd::_Debug_list
, independent of the use of - debug mode. This has several positive effects:Because each component has a distinct and separate release and +debug implementation, there are are no issues with link-time +coexistence: the separate namespaces result in different mangled +names, and thus unique linkage.
-
However, components that are defined and used within the C++
+standard library itself face additional constraints. For instance,
+some of the member functions of std::moneypunct
return
+std::basic_string
. Normally, this is not a problem, but
+with a mixed mode standard library that could be using either
+debug-mode or release-mode basic_string
objects, things
+get more complicated. As the return value of a function is not
+encoded into the mangled name, there is no way to specify a
+release-mode or a debug-mode string. In practice, this results in
+runtime errors. A simplified example of this problem is as follows.
+
Take this translation unit, compiled in debug-mode:
+
+// -D_GLIBCXX_DEBUG +#include-- Able to catch most invalid debug/release combinations: - because the names of debug- and release-mode containers are - different in the compiled object files, if a debug/release - interaction cannot occur (e.g., because a container a translation - unit compiled in debug mode is passed to a routine in a translation - unit compiled in release mode) the result will be an undefined - symbol at link time. The undefined symbol occurs because the mangled - name of the definition will contain the release-mode container type - and the mangled name of the reference will contain the debug-mode - container type. However, we cannot detect these collisions if the - only use of the container is in the return type, because the return - type is not part of the mangled name of a function. - +std::string test02(); + +std::string test01() +{ + return test02(); +} + +int main() +{ + test01(); + return 0; +} +
The new link_name
class attribute facilities
- renaming. It may be attached to any class type (or any class
- template) to override the name of the class used for name
- mangling. For instance, a class named bar
would
- generally mangle as 3bar
; if the class has
- a link_name
attribute that specifies the string
- "wibble", then it would mangle as 6wibble
.
... and linked to this translation unit, compiled in release mode:
-Note that although we have hidden the ODR violation, it still - exists. For this reason we cannot easily provide safe iterators for +
+#include+ ++ +std::string +test02() +{ + return std::string("toast"); +} +
For this reason we cannot easily provide safe iterators for
the std::basic_string
class template, as it is present
throughout the C++ standard library. For instance, locale facets
define typedefs that include basic_string
: in a mixed
@@ -445,7 +481,7 @@ template<typename _Tp, typename _Allocator = std::allocator<_Tp>
release-compiled translation units is enormous.
The coexistence scheme was chosen over many alternatives, +
The coexistence scheme above was chosen over many alternatives, including language-only solutions and solutions that also required extensions to the C++ front end. The following is a partial list of solutions, with justifications for our rejection of each.
@@ -491,19 +527,12 @@ template<typename _Tp, typename _Allocator = std::allocator<_Tp> declarations disallow specialization. This method fails the correctness criteria. -link_name
solution, eliminated
- only because it requires more extensive compiler changes
- than link_name
. In this model, we would define the
- debug containers in a different namespace
- (e.g., __gnu_debug
) and then import them (e.g., with
- an extended using
declaration that aliases templates,
- such as that of template
- aliases proposal). This solution is workable, and in fact
- would be desirable in the long run, but requires a sizeable change
- to the C++ compiler front-end that is not within the scope of
- this project. vector::push_back()
being
+ one example.
+ See link
+ name Other options may exist for implementing the debug mode, many of