Как указать адрес указателя в общем случае в соответствии со стандартом C
Обычно назначаются указатели с выделениями, используя неявное преобразование void * функции return, как и malloc():
void *malloc(size_t size);
int *pi = malloc(sizeof *pi);
Я хотел бы выполнить одно и то же присваивание при передаче адреса целевого указателя и без явного извлечения его типа изнутри функции (не внутри ее тела, ни аргументов).
Следующий код, похоже, достигает именно этого.
- Я хотел бы знать, полностью ли код полностью соответствует (любому)
стандарты C.
- Если это не соответствует, я хотел бы знать, возможно ли
достичь моего требования при соблюдении (любого из) стандартов C.
.
#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;
}
Результат:
pv: 0x800103a8;
pi: 0x800103a8;
Ответы
Ответ 1
Нет, это не соответствует требованиям. Вы передаете int**
как void*
(ok), но затем вы отбрасываете void*
в void**
, у которого не гарантируется одинаковый размер и макет. Вы можете только разыменовать void*
(кроме получаемого из malloc
/calloc
) после того, как вы вернете его обратно к типу указателя, который он изначально был, и это правило не применяется рекурсивно (так что void**
конвертировать автоматически, как void*
).
Я также не вижу способа удовлетворить все ваши требования. Если вы должны передать указатель указателем, вам нужно фактически передать адрес void*
и выполнить все необходимое кастинг в вызывающем, в этом случае main
. Это будет
int *pi;
void *pv;
allocate_memory(&pv, sizeof(int));
pi = pv;
... победить вашу схему.
Ответ 2
Типы int**
и void**
несовместимы
Вы производите p, чей реальный тип int **, void ** и затем разыменовываете его здесь:
*((void **) p) = pv;
который нарушит правила псевдонимов.
Вы можете либо передать указатель void, а затем набросить его правильно:
void *pi = NULL;
int* ipi = NULL ;
allocate_memory(&pi, sizeof *ipi );
ipi = pi ;
или верните указатель на пустоту.
int *pi = allocate_memory(sizeof *pi);
Существует возможность использовать объединение:
#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;
}
Насколько я понимаю, этот пример соответствует стандарту.
Используйте статические утверждения, чтобы гарантировать, что размеры и выравнивание одинаковы.
_Static_assert( sizeof( int** ) == sizeof( void** ) , "warning" ) ;
_Static_assert( _Alignof( int** ) == _Alignof( void** ) , "warning" ) ;
Ответ 3
Я не думаю, что это можно сделать со 100% стандартным образом, потому что не-void указатели не гарантированно имеют тот же размер, что и void*
.
По той же причине стандарт требует явного приведения аргументов printf("%p")
к void*
.
Ответ 4
Я думаю, что ваш код может вызвать некоторые интересные проблемы из-за литья void * void ** и разыменования его. По данным GCC, это не проблема, но иногда GCC лежит. Вы можете попробовать
#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;
}
Обратите внимание, что в вашем исходном коде вам нужно было отбрасывать int**
в void*
(неявный), а затем явно указывать на void**
, что может действительно запутать ваш компилятор. Может все еще быть проблема aliasing из-за того, что к главному int *pi
обращается и назначается указатель void
. Тем не менее, быстрое сканирование стандарта C11 в этом отношении неубедительно (см. http://open-std.org/JTC1/SC22/WG14/).