|Minor revision of:||WG21/N2481
|Reply to:||Hans-J. Boehm||Mike Spertus|
Its purpose is to support both garbage collected implementations and reachability-based leak detectors. This is done by giving undefined behavior to programs that "hide a pointer" by, for example, xor-ing it with another value, and then later turn it back into an ordinary pointer and dereference it. Such programs may currently produce incorrect results with conservative garbage collectors, since an object referenced only by such a "hidden pointer" may be prematurely collected. For the same reason, reachability-based leak detectors may erroneously report that such programs leak memory.
Note that for programs using the quick_exit() facility ( N2440, voted into the working paper at the Kona meeting), reachability-based leak detectors are arguably the only viable form of leak detection.
For a more general discussion, and the reasons to support transparent garbage collection, please see N2310.
220.127.116.11 Safely derived Pointers
[Note: Objects allocated by allocation functions must be properly referenced through chains of pointers until they are dereferenced or deallocated. The following makes this requirement precise. This allows, but does not require, implementations to reclaim unreferenced objects. -- end note.]
A traceable pointer location is one of the following:
- An object of pointer type.
- An object of an integral type at least as large as intptr_t
- A sequence of characters in an array of characters, whose size and alignment matches that of a pointer type.
A pointer value contained in a traceable pointer location is a reconstituted pointer if it was computed by:
A pointer value that is not a reconstituted pointer is called a safely derived pointer.
- Copying from other than a traceable pointer location,
- Performing arithmetic or bit-wise operations on integer arguments,
- Copying a reconstituted pointer,
- Arithmetic operations on a reconstituted pointer,
- Taking the address of an object obtained by dereferencing a reconstituted pointer, or
- Reading it from a file.
[Note: Whether or not a pointer value is safely derived is a property of how it was computed, and usually not of its actual representation. In particular, two pointers may be equal, and one of them may be safely derived, while the other is reconstituted. --end note]
A pointer to storage obtained from an allocation function shall be dereferenced or passed to a deallocation function only if it was either safely derived, or the referenced object was previously declared reachable (see [library:declare_reachable]).
T *p = new ...; intptr_t x = reinterpret_cast<intptr_t>(p) ^ 0x555; a: T *q = reinterpret_cast<T *>(x ^ 0x555); T y = *q;The newly allocated object N referenced by p is always reachable by the N2310 definition. But at the label a, p is dead, and is quite likely to no longer be visible to the garbage collector, since the register containing p may well have been reused, possibly to hold x. This means that if a garbage collection occurs at point a, N may not appear to be reachable, and thus may be collected anyway.
Objects constructed by the standard library that may hold a user-supplied pointer value, or an integer of type intptr_t, shall store them in a traceable pointer location (see 18.104.22.168). [Note: Other libraries are strongly encouraged to do the same, since not doing so may result in accidental use of reconstituted pointers. Libraries that store pointers outside the user's address space should make it appear that they are stored and retrieved from a traceable pointer location. --end note]
Add, possibly between 20.6.7 and 20.6.8:
void declare_reachable( void* p ) throw(std::bad_alloc)
- The argument is subsequently declared reachable (see 22.214.171.124). Reconstituted pointers to the same object may be dereferenced while the object is declared reachable.
- May throw std::bad_alloc if the system cannot allocate additional memory that may be required to track objects declared reachable.
- The argument p shall be a safely derived non-null pointer.
template < typename T > T* undeclare_reachable( T* p ) throw()
- A safely derived copy of p. The result will compare equal to p.
- Once the number of calls to undeclare_reachable(p) equals the number of calls to declare_reachable(p), the argument is no longer declared reachable (see [above section]). When this happens, reconstituted pointers to the object referenced by p may not be subsequently dereferenced. [Note: Since the returned pointer is safely derived, it may be used to access the referenced object, even if previously no safely derived pointer existed. -- end note]
- The object referenced by p shall have been previously declared reachable, and shall be live from the time of the call until the last undeclare_reachable(p) call on the object.
[Note: It is expected that calls to declare_reachable(p) will consume a small amount of memory until the matching call to undeclare_reachable(p) is encountered. In addition, the referenced object cannot be deallocated during this period, and garbage collecting implementations will not be able to collect the object while it is declared reachable. Long running programs should arrange that calls are matched. -- end note.]
void declare_no_pointers( char* p, size_t n ) throw()
- The n bytes starting at p no longer contain traceable pointer locations, independent of their type. Hence pointers located there may no longer be dereferenced. [Note: This may be used to inform a garbage collector or leak detector that this region of memory need not be traced.]
- Throws no exceptions. [Note: Under some conditions implementations may need to allocate memory. However the request can be ignored if memory allocation fails. -- end note]
- No bytes in the specified range may have been previously registered with declare_no_pointers(). If the specified range is in an allocated object, then it must be entirely within a single allocated object. The object must be live until the corresponding undeclare_no_pointers() call. [Note: In a garbage-collecting implementation, the fact that a region in an object is registered with declare_no_pointers() should not prevent the object from being collected. --end note]
void undeclare_no_pointers( char* p, size_t n ) throw()
- Prepares an object containing a range registered with declare_no_pointers() for destruction. It must be called before the lifetime of the object ends. It has no other effect. It does not recreate any traceable pointer locations in the object.
- The same range must previously have been passed to declare_no_pointers().
Add to 20.6.8, between paragraphs 4 and 5:
Storage allocated directly with malloc(), calloc(), or realloc() is implicitly declared reachable (see 126.96.36.199) on allocation, ceases to be declared reachable on deallocation, and may not cease to be declared reachable as the result of an undeclare_reachable() call. [Note: This allows existing C libraries to remain unaffected by restrictions on reconstituted pointers, at the expense of providing far fewer garbage collection and leak detection options for malloc()-allocated objects. It also allows malloc() to be implemented with a separate allocation arena, bypassing the normal declare_reachable() implementation. The above functions should never intentionally be used as a replacement for declare_reachable(), and newly written code is strongly encouraged to treat memory allocated with these functions as though it were allocated with operator new. --end note]
Our current implementation strategy is to have declare_no_pointers() simply record its arguments in a data structure that allows efficient lookup of these ranges by address. When an address range is scanned, this data structure can be consulted. For large address ranges the added cost should be minimal, since it is amortized by other scanning overhead. We expect that when scanning small ranges during garbage collection in production code, it may be too expensive to always consult this data structure. In the initial implementation, we will ignore declare_no_pointers calls made on small objects. A better long term strategy would be, for example, to consider the information only on every nth garbage collection. This might cause objects accidentally "referenced" by pointers in such regions to be temporarily retained in spite of the declare_no_pointers calls. But they would not be retained for an unbounded period of time.