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
+ 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.
-
-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
- to std::_Release_list during debug mode, and we will
- automatically include the debugging version with the
- name std::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.
+ 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
- named std::_Release_list but will be mangled with the
- name std::list (as it was in release mode). Similarly,
- the debug-mode list is named std::list
- (in debug mode) but will be mangled
- as std::_Debug_list. Thus the
- release-mode list always compiles down to code that
- uses the name std::list, and the
- debug-mode list always compiles down to code that uses
- the name std::_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-+ +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 -- 2.30.2