Re: Null Pointer Considerations
{ please do not quote sigs. -mod }
Matthias Hofmann wrote:
A pointer to memory address zero is in fact a pointer to nothing.
Thank you for clearing that up for me. This certainly explains a lot,
because I was under the impression that it was two different things,
semantically. However, I still think it ought to be two different
things.
By definition, the null pointer
is not an address, so it cannot be the result of pointer arithmetic.
Where did you get this definition?
Well, I just meant that a null pointer is not an address by definition
although it is often (always) implemented as an address.
so an implementation can safely represent the null pointer as a reserved
address (for example address 0), internally.
That's what's already happening, but you have said you don't like it that
way.
It is fine with me to implement a null pointer as a reserved address,
but we should not think of it that way.
By introducing a null pointer keyword, the compiler vendors can leave
any extended semantic of the current null pointer concept unchanged.
What would be the type of that keyword?
I would suggest it got its own type.
Give us an example please.
/*
Okay, I try to give an elaborated and hopefully correct example ;-).
Try to imagine a heap & pointer concept just like the standard
container & iterator concept. Standard heap, below, is an example of
this concept.
*/
#include <new>
#include <cstddef>
class standard_heap
{
public:
template<class T>
struct pointer
{
typedef T* type;
};
static void* allocate(std::size_t size)
{
return ::operator new(size);
}
static void deallocate(void* ptr, std::size_t)
{
deallocate(ptr);
}
static void deallocate(void* ptr)
{
::operator delete(ptr);
}
};
/*
We will now extend the concept with a mixin heap that makes an object
mixin at each allocation. Mixins could be an embedded size, a reference
counter, paddings etc. Below is a generic mixin heap, which can be used
to build these mixin types.
*/
template<class Heap, class Mixin>
class mixin_heap
{
public:
static Mixin* upcast(void* ptr)
{
if (ptr)
{
return static_cast<Mixin*>(ptr) - 1;
}
return 0;
}
static void* downcast(Mixin* ptr)
{
if (ptr)
{
return ptr + 1;
}
return 0;
}
public:
template<class T>
class pointer
{
private:
typename Heap::pointer<Mixin>::type ptr_;
public:
pointer()
{}
pointer(T* ptr) :
ptr_(mixin_heap::upcast(ptr))
{}
pointer& operator=(T* ptr)
{
ptr_ = mixin_heap::upcast(ptr);
return *this;
}
operator T*() const
{
return static_cast<T*>(mixin_heap::downcast(ptr_));
}
T* operator->() const
{
return static_cast<T*>(mixin_heap::downcast(ptr_));
}
public:
typedef pointer type;
};
static void* allocate(std::size_t size)
{
return downcast(new(Heap::allocate(sizeof(Mixin) + size)) Mixin());
}
static void* allocate(std::size_t size, const Mixin& mixin)
{
return downcast(new(Heap::allocate(sizeof(Mixin) + size))
Mixin(mixin));
}
static void deallocate(void* ptr, std::size_t size)
{
upcast(ptr)->~Mixin();
Heap::deallocate(upcast(ptr), size);
}
static void deallocate(void* ptr)
{
upcast(ptr)->~Mixin();
Heap::deallocate(upcast(ptr));
}
};
/*
Now I don't want to show any of the mixin types that I mentioned
above, but below is an example of two silly instantiations of the mixin
heap that will help me show my point.
*/
typedef mixin_heap<mixin_heap<standard_heap, int>, int> heap;
int main(int, int[])
{
heap::pointer<void>::type p = heap::allocate(0);
*heap::upcast(p) = 47;
heap::deallocate(p);
return 0;
}
Below is an assembler listing. Notice all the damn 'add 4' and 'add -4'
followed by 'je'.
_main PROC
; Line 118
push 8
call ??2@YAPAXI@Z ; operator new
add esp, 4
test eax, eax
je SHORT $LN30@main
add eax, 4
mov DWORD PTR [eax-4], 0
je SHORT $LN30@main
add eax, 4
mov DWORD PTR [eax-4], 0
je SHORT $LN30@main
add eax, -4
je SHORT $LN30@main
add eax, -4
; Line 119
test eax, eax
je SHORT $LN43@main
lea ecx, DWORD PTR [eax+4]
test ecx, ecx
je SHORT $LN43@main
add ecx, 4
je SHORT $LN43@main
add ecx, -4
jmp SHORT $LN44@main
; Line 118
$LN30@main:
xor eax, eax
; Line 119
$LN43@main:
xor ecx, ecx
$LN44@main:
; Line 120
test eax, eax
mov DWORD PTR [ecx], 47 ; 0000002fH
je SHORT $LN69@main
add eax, 4
je SHORT $LN69@main
add eax, 4
je SHORT $LN69@main
add eax, -4
je SHORT $LN69@main
add eax, -4
push eax
call ??3@YAXPAX@Z ; operator delete
add esp, 4
; Line 121
xor eax, eax
; Line 122
ret 0
; Line 120
$LN69@main:
xor eax, eax
push eax
call ??3@YAXPAX@Z ; operator delete
add esp, 4
; Line 121
xor eax, eax
; Line 122
ret 0
_main ENDP
Now we try to remove the null pointer check in the upcast and downcast
functions in the mixin heap. This is of course a wrong thing to do, but
it helps me illustrate what would happen if my compiler did null
pointer optimizations as described earlier.
_main PROC
; Line 118
push 8
call ??2@YAPAXI@Z ; operator new
add esp, 4
test eax, eax
je SHORT $LN9@main
mov DWORD PTR [eax], 0
jmp SHORT $LN10@main
$LN9@main:
xor eax, eax
$LN10@main:
add eax, 4
je SHORT $LN5@main
mov DWORD PTR [eax], 0
jmp SHORT $LN6@main
$LN5@main:
xor eax, eax
$LN6@main:
add eax, -4
; Line 120
push eax
mov DWORD PTR [eax+4], 47
call ??3@YAXPAX@Z ; operator delete
add esp, 4
; Line 121
xor eax, eax
; Line 122
ret 0
_main ENDP
I have seen similar problems in a serialization library that I am
working on.
--
Jesper Schmidt
--
Matthias Hofmann
Anvil-Soft, CEO
http://www.anvil-soft.com - The Creators of Toilet Tycoon
http://www.anvil-soft.de - Die Macher des Klomanagers
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]