--
Maxim Yegorushkin
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
> Is it somehow guarantied by the standard that size_t has enough bits to
> store a void* (which is guarantied to be able to store a pointer to any
> type)? IOW, are size_t and void* safely interchangeable?
No an std::size_t is an unsigned integral type capable of storing the
result of sizeof. A void * is a pointer that can represent a pointer to
anything. Integral types and pointer types are not interchangable even
their sizes are not guaranteed to be the same.
size_t is probably going to fit the bill on most machines. I can't
think of any popular machine that it's not going to work on and it
would be odd to design a flat addressed machine otherwise. However,
I remember in the old banked machine days that the pointers could end
up being bigger than the size that any one object could be (since they
couldn't span banks).
This is generally true when the implementation uses a flat memory
model. However, if it uses a segmented memory model (e.g. x86 "real
mode") objects might be created within multiple segments but be
constrained to fit wholly within a segment. So size_t would need to
hold a segment offset, whereas void * would need to hold a segment ID
and a segment offset, and so would likely be larger. The standard
does not guarantee that any integer type is large enough to hold a
pointer value; that's implementation-defined.
--
Ben Hutchings
Having problems with C++ templates? Your questions may be answered by
<http://womble.decadentplace.org.uk/c++/template-faq.html>.
> Is it somehow guarantied by the standard that size_t has enough bits to
> store a void* (which is guarantied to be able to store a pointer to any
> type)? IOW, are size_t and void* safely interchangeable?
Thank you all for the answers.
So, what is the proper way of doing byte pointer arithmetics?
Example:
typedef struct { int x, y; } some;
Should one write:
#define FROM_Y_TO_SOME(y_ptr) \
(some*)((size_t)(y_ptr) - offsetof(some, y))
Or:
#define FROM_Y_TO_SOME(y_ptr) \
(some*)((char*)(y_ptr) - offsetof(some, y))
The second form looks more appropriate, since you're dealing with
pointers only, instead of passing through an integer conversion
which stands a minor chance of being wrong.
--
A. Kanawati
NO.anto...@comcast.net
On OS/400 (V5R3) sizeof(void*) == 16 and sizeof(size_t) == 4!
Even if you copy the contents of a pointer into a non-pointer object
that is big enough, and copy it back again, the pointer is not valid.
> Is it somehow guarantied by the standard that size_t has enough bits to
> store a void* (which is guarantied to be able to store a pointer to any
> type)? IOW, are size_t and void* safely interchangeable?
There is no guarantee that any integer type has enough bits to store a
pointer of any type.
Using byte (= char) pointers!
> Example:
>
> typedef struct { int x, y; } some;
>
> Should one write:
>
> #define FROM_Y_TO_SOME(y_ptr) \
> (some*)((size_t)(y_ptr) - offsetof(some, y))
>
> Or:
>
> #define FROM_Y_TO_SOME(y_ptr) \
> (some*)((char*)(y_ptr) - offsetof(some, y))
This latter definition is correct. By the way, note that offsetof is
only specified to be usable with POD class types, so you can't use
this technique with arbitrary classes.
--
Ben Hutchings
Having problems with C++ templates? Your questions may be answered by
<http://womble.decadentplace.org.uk/c++/template-faq.html>.
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
Then the compiler is NOT compliant. If there is an integral type
big enough, you should be allowed (although not advisable perhaps)
to convert from a pointer to that integral type and back to the
same type pointer (look up reinterpret cast).
> Even if you copy the contents of a pointer into a non-pointer object
> that is big enough, and copy it back again, the pointer is not valid.
Are you sure?
5.2.10[expr.reinterpret.cast] says: (emphasis mine)
4 A pointer can be explicitly converted to any integral type large enough
to hold it.
5 A value of integral type or enumeration type can be explicitly
converted to a pointer. A pointer converted to an integer of sufficient
size (if any such exists on the implementation) and back to the same
pointer type *will have its original value*; mappings between pointers
and integers are otherwise implementation-defined.
The way I interpret this is that copying a pointer value to and from an
integer (yes, an integer, not an arbitrary object) that is big enough
does not take away its validity.
--
Seungbeom Kim
> Is it somehow guarantied by the standard that size_t has enough bits to
> store a void* (which is guarantied to be able to store a pointer to any
> type)? IOW, are size_t and void* safely interchangeable?
Actually, I was playing with an idea of stuffing double linked list
pointers with additional data, since the lower bits of the pointers are
not actually used due to alignment requirements. So, on a platform with
32-bit pointers a list node carries 4 bits of data, with 64-bit pointers -
6 bits. After quick optimization my profiler says that the stuffed list
append, prepend and remove operations are no more than 10% slower than
those of a plain list. I'm not sure about the benefits of using such a
list, I did it mostly as an experiment.
Here is what I came up with:
/*
stuffed list
The mechanics here are based on the premise that we can reuse pointer's
lower order bits
up to (log2(alignof(list_node*)) - 1) bit since they are always zero.
list_node has alignment at least as that of a pointer to it.
*/
#if defined __GNUC__
# define ST_LIST_ALIGNOF(x) __alignof__(x)
#elif defined _MSC_VER
# define ST_LIST_ALIGNOF(x) __alignof(x)
#else
# define ST_LIST_ALIGNOF(x) sizeof(x)
#endif
enum { st_list_data_bits = 2 * (
2 == ST_LIST_ALIGNOF(list_node*) ? 1
: 4 == ST_LIST_ALIGNOF(list_node*) ? 2
: 8 == ST_LIST_ALIGNOF(list_node*) ? 3
: 0
)
, st_list_ptr_mask = ~((1 << (st_list_data_bits / 2)) - 1)
};
typedef char st_list_compile_assert_1[st_list_data_bits != 0 ? 1 : -1];
typedef char st_list_compile_assert_2[sizeof(size_t) == sizeof(list_node*)
? 1 : -1];
#undef ST_LIST_ALIGNOF
typedef struct st_list_node
{
union { size_t prev; struct st_list_node
*align_prev_do_not_use_it_directly; };
union { size_t next; struct st_list_node
*align_next_do_not_use_it_directly; };
} st_list_node;
typedef struct { unsigned int data : st_list_data_bits; } st_list_data;
inline void st_list_init(st_list_node* node);
inline void st_list_set_data(st_list_node* node, st_list_data data);
inline st_list_data st_list_get_data(st_list_node const* node);
inline st_list_data st_list_strip_data(st_list_node* node);
inline st_list_node* st_list_prev(st_list_node* node);
inline st_list_node* st_list_next(st_list_node* node);
inline void st_list_append(st_list_node* prev, st_list_node* node);
inline void st_list_prepend(st_list_node* next, st_list_node* node);
inline void st_list_remove(st_list_node* node);
inline void st_list_swap(st_list_node* a, st_list_node* b);
void st_list_init(st_list_node* node)
{
node->align_prev_do_not_use_it_directly = node;
node->align_next_do_not_use_it_directly = node;
}
void st_list_set_data(st_list_node* node, st_list_data data)
{
/* node->prev takes high order data bits, node->next - lower order */
node->next = (node->next & st_list_ptr_mask) | (data.data &
~st_list_ptr_mask);
data.data >>= st_list_data_bits / 2;
node->prev = (node->prev & st_list_ptr_mask) | (data.data &
~st_list_ptr_mask);
}
st_list_data st_list_get_data(st_list_node const* node)
{
st_list_data data;
data.data = node->prev & ~st_list_ptr_mask;
data.data <<= st_list_data_bits / 2;
data.data |= node->next & ~st_list_ptr_mask;
return data;
}
st_list_data st_list_strip_data(st_list_node* node)
{
st_list_data data;
data.data = node->prev & ~st_list_ptr_mask;
node->prev &= st_list_ptr_mask;
data.data <<= st_list_data_bits / 2;
data.data |= node->next & ~st_list_ptr_mask;
node->next &= st_list_ptr_mask;
return data;
}
st_list_node* st_list_next(st_list_node* node)
{
return (st_list_node*)(node->next & st_list_ptr_mask);
}
st_list_node* st_list_prev(st_list_node* node)
{
return (st_list_node*)(node->prev & st_list_ptr_mask);
}
void st_list_append(st_list_node* prev, st_list_node* node)
{
size_t node_prev_data, node_next_data, prev_data, next_data;
st_list_node* next;
node_prev_data = node->prev & ~st_list_ptr_mask;
node_next_data = node->next & ~st_list_ptr_mask;
prev_data = prev->next & ~st_list_ptr_mask;
next = st_list_next(prev);
next_data = next->prev & ~st_list_ptr_mask;
node->prev = (size_t)prev | node_prev_data;
node->next = (size_t)next | node_next_data;
prev->next = (size_t)node | prev_data;
next->prev = (size_t)node | next_data;
}
void st_list_prepend(st_list_node* next, st_list_node* node)
{
size_t node_prev_data, node_next_data, prev_data, next_data;
st_list_node* prev;
node_prev_data = node->prev & ~st_list_ptr_mask;
node_next_data = node->next & ~st_list_ptr_mask;
next_data = next->prev & ~st_list_ptr_mask;
prev = st_list_prev(next);
prev_data = prev->next & ~st_list_ptr_mask;
node->prev = (size_t)prev | node_prev_data;
node->next = (size_t)next | node_next_data;
prev->next = (size_t)node | prev_data;
next->prev = (size_t)node | next_data;
}
void st_list_remove(st_list_node* node)
{
size_t node_prev_data, node_next_data, prev_data, next_data;
st_list_node *next, *prev;
node_prev_data = node->prev & ~st_list_ptr_mask;
prev = st_list_prev(node);
prev_data = prev->next & ~st_list_ptr_mask;
node_next_data = node->next & ~st_list_ptr_mask;
next = st_list_next(node);
next_data = next->prev & ~st_list_ptr_mask;
prev->next = (size_t)next | prev_data;
next->prev = (size_t)prev | next_data;
node->prev = (size_t)node | node_prev_data;
node->next = (size_t)node | node_next_data;
}
void st_list_swap(st_list_node* a, st_list_node* b)
{
st_list_node *a_prev, *b_prev;
a_prev = st_list_prev(a);
b_prev = st_list_prev(b);
st_list_remove(a);
st_list_remove(b);
if(a == b_prev) /* special case A<->B */
{
st_list_append(a_prev, b);
st_list_append(b, a);
}
else if(b == a_prev) /* special case B<->A */
{
st_list_append(b_prev, a);
st_list_append(a, b);
}
else /* general case A<-...->B or B<-...->A */
{
st_list_append(a_prev, b);
st_list_append(b_prev, a);
>Dylan Nicholson wrote:
>
>> Even if you copy the contents of a pointer into a non-pointer object
>> that is big enough, and copy it back again, the pointer is not valid.
>
>Are you sure?
>5.2.10[expr.reinterpret.cast] says: (emphasis mine)
>
>4 A pointer can be explicitly converted to any integral type large enough
>to hold it.
>5 A value of integral type or enumeration type can be explicitly
>converted to a pointer. A pointer converted to an integer of sufficient
>size (if any such exists on the implementation) and back to the same
>pointer type *will have its original value*; mappings between pointers
>and integers are otherwise implementation-defined.
>
>The way I interpret this is that copying a pointer value to and from an
>integer (yes, an integer, not an arbitrary object) that is big enough
>does not take away its validity.
That assumes there *is* an integral type large enough. That is not
the case on all platforms - older mainframe and mini architectures are
frequent offenders.
George
--
for email reply remove "/" from address
George Neuner schrieb:
>That assumes there *is* an integral type large enough. That is not
>the case on all platforms - older mainframe and mini architectures are
>frequent offenders.
>
It might be worth adding that even the new integer types of C99 capable
of holding object pointers,
namely intptr_t and uintptr_t, are **optional** types.
Greetings from Bremen,
Daniel Krügler
>> Is it somehow guarantied by the standard that size_t has
>> enough bits to store a void* (which is guarantied to be able
>> to store a pointer to any type)? IOW, are size_t and void*
>> safely interchangeable?
> Actually, I was playing with an idea of stuffing double linked
> list pointers with additional data, since the lower bits of
> the pointers are not actually used due to alignment
> requirements.
That's an old trick; I think it was used in some of the earliest
implementations of malloc (back in the 70's); the free space
arena was a linked list of blocks, with the low order bit of the
nextBlock pointer being used to indicate whether the block was
allocated or not. I'm sceptical of the value on a modern
machine (with virtual memory), but who knows; maybe you'll end
up with less cache misses because of a smaller memory
footprint:-).
For what it's worth, my usual solution in such cases was to
define something like:
#if HIBYTE1ST
static size_t const lowByteIndex = sizeof( void* ) + 1 ;
#else
static size_t const lowByteIndex = 0 ;
#endif
Then manipulate just the low byte with things like:
void* next ;
if ( ((char*)(&next)[ lowByteIndex ] & 1) != 0 ) ...
(I actually encapsulated the messiness in a few inline
functions: setFlag, resetFlag, testFlag. Or rather, #define's
-- I did this in the early 80's, and I'd not even heard of C++
then.)
Another solution would be to declare the pointers as char*, and
use ++ and -- to set and reset the bits (but you'd still need
the special logic for test).
> So, on a platform with 32-bit pointers a list node carries 4
> bits of data, with 64-bit pointers - 6 bits. After quick
> optimization my profiler says that the stuffed list append,
> prepend and remove operations are no more than 10% slower than
> those of a plain list. I'm not sure about the benefits of
> using such a list, I did it mostly as an experiment.
As I said, I'm sceptical of the benefits myself, but carefully
done, the run-time overhead isn't that much. The implementation
and maintenance overhead, on the other hand...
--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34