Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to cast the address of a pointer generically while conforming to the C standard

It is common to assign pointers with allocations using an implicit function-return void * conversion, just like malloc()'s:

void *malloc(size_t size);
int *pi = malloc(sizeof *pi);

I would like to perform the same assignment while passing the address of the target pointer, and without explicitly casting its type from within the function (not within its body, nor arguments).

The following code seems to achieve just that.

  1. I would like to know whether the code fully conforms with (any of) the C standards.
  2. If it doesn't conform, I would like to know if it's possible to achieve my requirement while conforming to (any of) the C standards.

.

#include <stdio.h>
#include <stdlib.h>

int allocate_memory(void *p, size_t s) {
    void *pv;
    if ( ( pv = malloc(s) ) == NULL ) {
        fprintf(stderr, "Error: malloc();");
        return -1;
    }
    printf("pv: %p;\n", pv);
    *((void **) p) = pv;
    return 0;
}

int main(void) {
    int *pi = NULL;
    allocate_memory(&pi, sizeof *pi);
    printf("pi: %p;\n", (void *) pi);
    return 0;
}

Result:

pv: 0x800103a8;
pi: 0x800103a8;
like image 434
Dror K. Avatar asked Jun 02 '14 19:06

Dror K.


People also ask

How does void* work?

a void* is a pointer, but the type of what it points to is unspecified. When you pass a void pointer to a function you will need to know what its type was in order to cast it back to that correct type later in the function to use it.

What is cast type in malloc in C?

In C, you don't need to cast the return value of malloc . The pointer to void returned by malloc is automagically converted to the correct type. However, if you want your code to compile with a C++ compiler, a cast is needed.

Can a pointer be any type in C?

void pointer in C / C++ A void pointer can hold address of any type and can be typecasted to any type.

What is the purpose of a void pointer?

The void pointer in C is a pointer that is not associated with any data types. It points to some data location in the storage. This means that it points to the address of variables. It is also called the general purpose pointer.


3 Answers

Types int** and void** are not compatible You are casting p, whose real type is int**, to void** and then dereferencing it here:

*((void **) p) = pv;

which will break aliasing rules.

You can either pass a void pointer and then cast it correctly:

void *pi = NULL;
int* ipi = NULL ;
allocate_memory(&pi, sizeof *ipi );
ipi = pi ;

or return a void pointer.

int *pi = allocate_memory(sizeof *pi);


There is an option to use a union:

#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>

union Pass
{
    void** p ;
    int** pi ;
} ;

int allocate_memory(union Pass u , size_t s) {
    void *pv;
    if ( ( pv = malloc(s) ) == NULL ) {
        fprintf(stderr, "Error: malloc();");
        return -1;
    }
    printf("pv: %p;\n", pv);
    *(u.p) = pv;

    return 0;
}

int main() 
{
    int* pi = NULL ;
    printf("%p\n" , pi ) ;
    allocate_memory( ( union Pass ){ .pi = &pi } , sizeof( *pi ) ) ;
    printf("%p\n" , pi ) ;

    return 0;
}

As far as I understand it, this example conforms to standard.

Use static asserts to guarantee that the sizes and alignment are the same.

_Static_assert( sizeof( int** ) == sizeof( void** ) , "warning" ) ;
_Static_assert( _Alignof( int** ) == _Alignof( void** ) , "warning" ) ;
like image 184
this Avatar answered Oct 03 '22 23:10

this


No, this is not compliant. You're passing an int** as void* (ok), but then you cast the void* to a void** which is not guaranteed to have the same size and layout. You can only dereference a void* (except one gotten from malloc/calloc) after you cast it back to the pointer type that it originally was, and this rule does not apply recursively (so a void** does not convert automatically, like a void*).

I also don't see a way to meet all your requirements. If you must pass a pointer by pointer, then you need to actually pass the address of a void* and do all the necessary casting in the caller, in this case main. That would be

int *pi;
void *pv;
allocate_memory(&pv, sizeof(int));
pi = pv;

... defeating your scheme.

like image 37
Fred Foo Avatar answered Oct 03 '22 23:10

Fred Foo


I don't think it's possible to do it in a 100% standard-compliant manner, because non-void pointers are not guaranteed to have the strictly same size as a void*.

It's the same reason the standard demands explicitly casting printf("%p") arguments to void*.

Added: On the other hand, some implementations mandate that this work, such as Windows (which happily casts IUnknown** to void**).

like image 36
Medinoc Avatar answered Oct 04 '22 00:10

Medinoc