On 02/17/2012 02:45 PM, pembed2012 wrote: ...
It's absolutely guaranteed, at this point, that sizeof(buf) ==
sizeof(void*). As a result, 2 of your next three lines are pointless,
which suggests that you were not aware of that fact.
Why did you think it was possible for the condition of that if() to not
be met?
Often this function will be called with a dynamic pointer from malloc.
However, it is also possible to call it with a stack array:
char ary[10];
char* ptr=malloc(10);
myfunc((void*)ary); // should work
myfunc((void*)ptr); // should also work
It started off with the buffers being NULL-free strings. However now I
need to work with strings with embedded NULL characters. This means I
can't find the length of the buffer with strlen. And it would be too much
change to add a new argument to pass the length of the buffer. So the
only good solution is for myfunc to work out by it's self 1) if the
buffer is a dynamic pointer or stack array and 2) the size of the buffer.
Since you seem quite interested in deriving how to access the size of
an allocation from just a pointer, one method is to store the size
yourself in your own allocator that wraps 'malloc'.
\code snippet
uintmax_t current_memory;
void* track_malloc( size_t size )
{
void* mem_blk = NULL;
void* p = NULL;
/*
* To track the amount of used memory, each memory allocation is
* prefixed with a size_t object that allows the free function to
* update 'current_memory' correctly when releasing memory.
*/
mem_blk = malloc( sizeof (size_t) + size );
if ( mem_blk )
{
*((size_t*)mem_blk) = size;
p = (unsigned char*)mem_blk + sizeof (size_t);
current_memory += size;
}
return p;
}
void track_free( void* p )
{
void* mem_blk = NULL;
size_t p_size;
if ( p )
{
mem_blk = (unsigned char*)p - sizeof (size_t);
p_size = *((size_t*)mem_blk);
free( mem_blk );
current_memory -= p_size;
}
}
\endcode
In essence, it adds a header to each pointer that stores the size of
the allocation in a 'size_t' prefix.
-------- ----------------------------
| size_t | ... object ... |
-------- ----------------------------
/ /
mem p
Everything seems fine until you decide to allocate an object that has
stricter alignment requirements than a 'size_t'.
-------- --------------
| size_t | double | (xxx not aligned xxx)
-------- --------------
/ /
mem p
One of the requirements for malloc is to provide a memory block that
is aligned for any object. So while 'mem' is aligned properly for a
double, the double object at 'p' is not. For example, on my 32-bit
Windows, 'size_t' has an alignment of 4, while 'double' has an
alignment of 8. The proper method to prefix the allocation pointer
for a 'double' is to insert a 'double' sized object.
4 4 8
-------- -------- --------------
| size_t | pad | double |
-------- -------- --------------
/ /
mem p
Now your 'double' object is guaranteed to be aligned. The general
solution is to prefix each memory allocation with the maximum
alignment detected from the system, referenced as ALIGN_MAX. This can
be defined by the compiler (under the newest C standard), or estimated
using offsetof and a union of a large set of distinct types. Search
for Chris Thomasson who has posted about it before.
So instead of using 'sizeof' to calculate the size of the 'size_t'
prefix, you'll need a different version to pad it to a multiple of
ALIGN_MAX.
\code snippet
uintmax_t current_memory;
size_t gc_maxalign_sizeof( size_t type_sz )
{ return ((type_sz + ALIGN_MAX - 1) / ALIGN_MAX) * ALIGN_MAX; }
#define c_maxalign_sizeof( type ) ( (gc_maxalign_sizeof( sizeof
(type) )) )
void* track_malloc( size_t size )
{
void* mem_blk = NULL;
void* p = NULL;
/*
* To track the amount of used memory, each memory allocation is
* prefixed with a size_t object that allows the free function to
* update 'current_memory' correctly when releasing memory.
*/
mem_blk = malloc( c_maxalign_sizeof (size_t) + size );
if ( mem_blk )
{
*((size_t*)mem_blk) = size;
p = (unsigned char*)mem_blk + c_maxalign_sizeof (size_t);
current_memory += size;
}
return p;
}
void track_free( void* p )
{
void* mem_blk = NULL;
size_t p_size;
if ( p )
{
mem_blk = (unsigned char*)p - c_maxalign_sizeof (size_t);
p_size = *((size_t*)mem_blk);
free( mem_blk );
current_memory -= p_size;
}
}
\endcode
Note that while this prefixes the size, none of the string functions
(strlen) know about this size prefix, so you still have to define your
own interface. It does allow you to get away with passing one
augmented pointer since you pack the allocation size with it.
size_t track_strlen( const char* str )
{
void* mem_blk = (unsigned char*)str - c_maxalign_sizeof (size_t);
return *((size_t*)mem_blk);
}
I'd consider using a special typedef to distinguish these augmented
string pointers to distinguish functions that expect them and those
that do not.
This is the just one possible path. Another path is to define a
string structure that stores the length and buffer size.
struct c_string
{
char* buf;
size_t buf_size;
size_t length;
};
Then you write a string interface that works with 'struct c_string',
which is able to handle embedded nul characters since you store the
length.
\code snippet
size_t c_string_length( const struct c_string* cstr )
{
return cstr->length;
}
\endcode
You can write your own (a lot of time/work), or decide to use one of
the string libraries out there, like bstring. I would strongly
consider using an external string library was created to handle
strings with embedded nulls. If you can walk on someone else's
shoulders to step over a mud pit ...
Best regards,
John D.