可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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.
- I would like to know whether the code fully conforms with (any of)
the C standards.
- 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;
回答1:
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.
回答2:
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" ) ;
回答3:
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**
).
回答4:
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/).
回答5:
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.