Home > Enterprise >  Storing a void pointer inside another pointer via memcpy
Storing a void pointer inside another pointer via memcpy

Time:11-06

The following code attempts to store a void pointer inside a pointer of a different type using memcpy (i.e. not casting) and then recover the original void pointer. Does it invoke undefined behavior?

#include <assert.h>
#include <string.h>
#include <stdio.h>

int main()
{
    // Ensure that an int pointer is large enough to store a void pointer
    _Static_assert( sizeof( int* ) >= sizeof( void* ) );
    
    // Create a void pointer pointing to a dummy value
    void *a = &(short){ 10 };
    
    // Stash the void pointer inside an int pointer via memcpy
    int *b = NULL;
    memcpy( &b, &a, sizeof( void *) );
    
    // Duplicate the int pointer via assignment
    // Maybe undefined behavior as b could be an invalid int pointer
    // or the assignment may not copy padding bits that are actually significant to us?
    int *c = b;
    
    // Extract the void pointer from inside the int pointer duplicate
    void *d;
    memcpy( &d, &c, sizeof( void *) );
    
    // The extracted void pointer should now equal the original void pointer
    // Hence, this line should print 10
    printf( "%d", *(short *)d );

    return 0;
}

Someone is bound to ask why we might want to do this. The (crazy?) idea is to store a pointer to malloced memory inside a pointer to another type and then recover the original pointer inside a function that doesn’t know the other pointer type:

#include <stdlib.h>
#include <string.h>

void alloc_mem( void *p )
{
    void *mem = malloc( 100 );
    memcpy( p, &mem, sizeof( void * ) );
}

void do_something_with_mem( void *p )
{
    void *mem;
    memcpy( &mem, p, sizeof( void * ) );
    if( mem )
    {
        // Do something with mem
    }
}

int main()
{
    // int for the purpose of demonstration, but it could be any type
    _Static_assert( sizeof( int* ) >= sizeof( void* ) );
    int *foo = NULL;
    
    alloc_mem( &foo );
    
    int *bar = foo;
    do_something_with_mem( &bar );

    return 0;
}

CodePudding user response:

Not sure what the language lawyers will say, but you don't need memcpy. You can just cast:

void alloc_mem(void* p)
{
    void* mem = malloc(100);
    strcpy(mem, "hello world\n");
    *(void**)p = mem;
}

void do_something_with_mem(void* p)
{
    void* mem = *(void**)p;
    if (mem)
    {
        // Do something with mem
        printf("%s", (char*)mem);
    }
}

CodePudding user response:

I don't think there's any guarantee an int* is large enough to hold a void*.

The other way around is fine, though. You could store an int* in a void*. So use void* instead of int*. And you can use a simple assignment instead of memcpy.

#include <stdlib.h>
#include <string.h>

void alloc_mem( void **p )
{
    *p = malloc( 100 );
}

void do_something_with_mem( void *p )
{
    ActualType *mem = p;
    // ...
}

int main( void )
{
    void *foo = NULL;
    alloc_mem( &foo );
    
    void *bar = foo;
    do_something_with_mem( bar );

    return 0;
}

CodePudding user response:

My copy of ANSI C says:

3.2.2.3 Pointers

A pointer to void may be converted to or from a pointer to any incomplete or object type. A pointer to any incomplete or object type may be converted to a pointer to void and back again; the result shall compare equal to the original pointer.

And "convert" means:

3.2 CONVERSIONS

Several operators convert operand values from one type to another automatically. This section specifies the result required from such an implicit conversion, as well as those that result from a cast operation (an explicit conversion). The list in $3.2.1.5 summarizes the conversions performed by most ordinary operators; it is supplemented as required by the discussion of each operator in $3.3.

Conversion of an operand value to a compatible type causes no change.

So using operators, including casts, would count as a conversion that keeps the pointer intact.

Notably, memcpy() doesn't count as a conversion. That's because implementation can do whatever it wants with pointers. For example, it could implement pointers to the stack as relative to the stack base.
That means that every conversion taking place in another thread (with another stack) has to have some logic to "rebase" the pointer when converted.
But memcpy() isn't aware of the contents of the copy, so it can't implement that logic when copying a pointer.

Another example: A C implementation could move objects around (e.g. in order to defragment the memory), as long as all the pointers to the moved object are updated.
But if a pointer is created in a way that's unknown to the compiler, e.g. by memcpy(), then that pointer will not be updated.

memcpy() is strictly a copy of bytes, not a conversion.

So the answer to your question is: No, you can't use memcpy() to copy pointers in portable C code.

That said, you do not need memcpy() in order to copy a pointer or to lose the type information, as pointed out in other answers.

  • Related