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

5

11

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;
Extine answered 2/6, 2014 at 19:46 Comment(2)
My conclusions: 1. Although my sample is likely to 'work', it is not conforming to any of the C standards, specifically because it depends on casting to a void **. 2. My assumption is that there is no standard conforming approach that would meet the requirement above.Extine
Update: the only way to achieve my requirement while being conforming to the standard, is by implementing a (int **) to (void **) converter. But due to the fact said conversion is implementation defined, it will conform the standard only under a compatible implementation. Therefore my requirement is impossible, I apologize for the goose chase, and very much appreciate your effort.Extine
C
5

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.

Catiline answered 2/6, 2014 at 19:55 Comment(3)
Your answer seems the most informative, and explicitly mentions that you don't know of a way to meet my requirement. Thank youExtine
By 'you can only use a void*, do you mean you can only dereference a void*`? It is perfectly valid to use a void pointer (eg, assign it to another pointer) without casting it.Epanorthosis
@WilliamPursell: s/use/dereference, yes.Catiline
S
6

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" ) ;
Solley answered 2/6, 2014 at 19:55 Comment(12)
I wanted to thank you for your answer and the mentioning of the strict-aliasing concern. My conclusion is that there is no conforming way for achieving my requirement.Extine
@DrorK. Well..., actually you could use a union but I'm not an expert with that. I suggest you use my code and ask a new question if that is conforming to standard. I think it is, but I'm not completely sure.Solley
I was wondering if you'd still consider it has potential to be conforming if the union was: union u { void **pp; void *p; }; ?Extine
The union example is just as non-portable as the original. Writing to a union field "activates" it and deactivates the others: "When a value is stored in a member of an object of union type, the bytes of the object representation that do not correspond to that member but do correspond to other members take unspecified values" (§6.2.6.1.7).Catiline
@larsmans It says there is no trap representation. I see no problem with being unspecified since 6.2.6.1.1 The representations of all types are unspecified except as stated in this subclause. Union is a typical way to use type punning and not break strict aliasing. If that is allowed, why would this not be.Solley
@larsmans But they are the same, assert is there for that, and 6.5.2.3.3(95): If the member used to read the contents of a union object is not the same as the member last used to store a value in the object, the appropriate part of the object representation of the value is reinterpreted as an object representation in the new type as described in 6.2.6 (a process sometimes called ‘‘type punning’’). This might be a trap representation. Actually the member can be a trap representation. :(, but can it be really if both sizes are the same and have the same alignment and are both pointers.Solley
@larsmans What I'm trying to say is that, with a union approach you will not break strict aliasing, but you might have other problems, like of you interpret int as a float, and then use it incorrectly. I think we are safe in this case since the sizes and alignment( using assert and _Alignof ) match. In case they don't you get a compile error.Solley
You're right, trap representation is the only problem remaining. (malloc's guarantees regarding alignment make this extremely unlikely when the pointer sizes are the same, so the implementation would really be playing tricks on you.) Your solution is conforming, just not strictly conforming. +1.Catiline
I'm afraid we've gotten a bit off track. When I was asking for a conforming approach, I actually meant for an approach that will be equivalent to the "implicit function-return void * conversion". The union approach requires the assertion of size and alignment of both pointers, while the "implicit function-return void * conversion" obviously has no such assertion required. So unfortunately I find it to defeat the purpose. I very much appreciate your efforts, thanks a lot. (both of you)Extine
@DrorK. Can you tell me why do you need that specific approach?Solley
@self.: It's merely a self-preference, to be able to replace the common convention of int *pi = malloc(sizeof *pi); with int *pi; malloc(&pi, sizeof *pi); while 'properly' performing the void * conversion to pointer to any object type. Nothing more behind it :)Extine
I've added a comment to my question explaining why it's impossible. Regarding the union approach: it treats (int **) as if it were (void **), even with the assertion of size and alignment- there is no assertion they are the same. It is very likely that they would be identical, but there's no such guaranty.Extine
C
5

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.

Catiline answered 2/6, 2014 at 19:55 Comment(3)
Your answer seems the most informative, and explicitly mentions that you don't know of a way to meet my requirement. Thank youExtine
By 'you can only use a void*, do you mean you can only dereference a void*`? It is perfectly valid to use a void pointer (eg, assign it to another pointer) without casting it.Epanorthosis
@WilliamPursell: s/use/dereference, yes.Catiline
D
2

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**).

Decoction answered 2/6, 2014 at 19:54 Comment(7)
all pointers to data members have the same size, pointers to functions aren't required to be the same size.Rabassa
@GradyPlayer Reference? 6.2.5(28): Pointers to other types need not have the same representation or alignment requirements.Solley
@GradyPlayer Representation refers directly to size.Solley
@GradyPlayer 6.3.2.3p1: "A pointer to void may be converted to or from a pointer to any object type." ... are you mixing what void * may contain, with the size of the pointers?Extine
my point is void ** is a pointer to a void pointer which would have the same size as pointer to an int pointerRabassa
@GradyPlayer Are you saying that based on your personal experience, or do you happen to know a specific standard which conforms that "the size of void ** is the same size as int *"?Extine
I may just be totally wrong, which seems to be consensus... so I will just be resigned to that...Rabassa
B
0

I think your code might provide some interesting problems due to casting void* to void** and dereferencing it. According to GCC this is not a problem but sometimes GCC lies. You can try

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

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

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

Note that in your original code you had to cast int** to void* (implicit) and then explicitly cast to void** which could really confuse your compiler. There might still be an aliasing problem due to the fact that main's int *pi is accessed as and assigned a void pointer. However, a quick scan of the C11 standard is inconclusive in that regard (see http://open-std.org/JTC1/SC22/WG14/).

Bilection answered 2/6, 2014 at 20:31 Comment(8)
You are casting int** to void**, which is illegal.Solley
@self.: It's not illegal (i.e., it doesn't violate a constraint), but it can have undefined behavior.Adze
@KeithThompson This seems pretty concrete: 6.2.5(28): Pointers to other types need not have the same representation or alignment requirements.Solley
@self.: Sure. My quibble was over the word "illegal" -- a term the C standard doesn't actually use. I tend to use the word only for violations of syntax errors and constraints, i.e,. things that must be diagnosed (though even for those a compiler can legally accept the program after issuing a warning).Adze
@KeithThompson What word would should be used for breaking c standard rules?Solley
@self.: In this case, undefined behavior. It doesn't actually break any rules; the standard specifically permits casts between any scalar types, and such a cast could even be valid if you happen to know that it works on the current system. It's non-portable, not illegal -- and one of C's greatest strengths is the ability to write non-portable code. (Having said that, converting int** to void** probably isn't a great idea in any context.)Adze
However C11 says: 6.2.5 (28) A pointer to void shall have the same representation and alignment requirements as a pointer to a character type. ... 6.3.2.3 (1) A pointer to void may be converted to or from a pointer to any object type. A pointer to any object type may be converted to a pointer to void and back again; the result shall compare equal to the original pointer 6.3.2.3 (7) ... When a pointer to an object is converted to a pointer to a character type, the result points to the lowest addressed byte of the object. ...Bilection
@merlin: But your sample, and my sample, are actually using a "pointer to pointer to void" (void **), which is different than "a pointer to void" (void *).Extine
P
0

Some platforms are capable of storing pointers that can only identify coarsely-aligned objects (e.g. those of type int*) more compactly than pointers that can access arbitrary bytes (e.g. those of type void* or char*). The Standard allows implementations targeting such platforms to reserve less space for int* than for void*. On implementations that do that, would generally be impractical to allow a void** to be capable of updating either an int* or a char* interchangeably; consequently, the Standard does not require that implementations support such usage.

On the other hand, the vast majority of implementations target platforms where int* and char* have the same size and representation, and where it would cost essentially nothing to regard a void* as being capable of manipulating both types interchangeably. According to the published Rationale document, the Spirit of C indicates that implementations should not "prevent programmers from doing what needs to be done". Consequently, if an implementation claims to be suitable for purposes like low-level programming that may involve processing pointers to different kinds of objects interchangeably, it should support such constructs whether or not the Standard would require it to do so; those that don't support such constructs on platforms where they would cost essentially nothing should be recognized as unsuitable for any purposes that would benefit from them.

Compilers like gcc and clang would require using -fno-strict-aliasing to make them support such constructs; getting good performance would then likely require using restrict in many cases when appropriate. On the other hand, since code which exploits the semantics available via -nno-strict-aliasing and properly uses restrict may achieve better performance than would be possible with strictly conforming code, and support for such code should be viewed as one of the "popular extension" alluded to on line 27 of page 11 of the published Rationale.

Purpure answered 29/10, 2018 at 20:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.