Как указать адрес указателя в общем случае в соответствии со стандартом 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/).