Почему (void **) type может быть присвоен (void *) или (int *)?

это мой код C:

int main()
{
    void * ptr_void;
    void ** ptr_2void;
    ptr_void = ptr_2void;
    return 0;
}

Мне просто интересно, почему этот код действителен? Я назначил (void *) to (void **), компилятор передает его даже без предупреждения. тип выглядит несоответствующим. и также работает следующий код, который назначает (void **) - (int *).

int main()
{
    int * ptr_int;
    void ** ptr_2void;
    ptr_int = ptr_2void;
    return 0;
}

кто-нибудь может понять, что именно находится в (void *)?

Ответ 1

Указатели void типа неявно преобразуются в указатели на любой другой тип данных. Компилятор не будет показывать никаких предупреждений. Аналогично преобразование типа из указателя любого типа в void * также будет работать без предупреждения.

Кроме указателей void, если вы попытаетесь преобразовать из одного типа указателя в другой тип указателя, неявно будет выдано предупреждение компилятором.

Например, рассмотрим приведенный ниже код. Он даст вам предупреждение "assignment from incompatible pointer type".

  int *intptr;
  void *voidptr;
  void **vvptr;
  int intval=123;
  voidptr=&intval;
  vvptr=voidptr;
  intptr=vvptr; 

Строка кода, вызывающая предупреждение, intptr=vvptr;, потому что intptr является integer pointer и vvptr является указателем типа void **. Ни один из них не является указателями void * и, следовательно, предупреждением.

Чтобы избежать этого предупреждения, вы должны явно ввести тип void ** в тип int *. Если вы измените строку intptr=vvptr; на intptr=(int *)vvptr;, предупреждение не будет показано компилятором.

Ответ 2

Важно различать преобразование и литье.

Преобразование преобразует значение одного типа в значение другого типа. Листинг - это оператор (состоящий из имени типа в круглых скобках), который явно указывает преобразование. Преобразование может быть либо явным (задано оператором литья), либо неявным. Для большинства преобразований указателей требуется оператор трансляции; исключения указателей, включающие void*, являются исключением.

Значение любого типа указатель-объект (или тип от указателя к неполному) может быть преобразовано в void* и обратно в исходный тип; результирующий указатель гарантированно сравним с исходным указателем.

В присваивании (или при передаче аргумента функции или в инструкции return) преобразование в или из void* может выполняться неявно, без оператора трансляции.

В первом примере кода:

void * ptr_void;
void ** ptr_2void;
ptr_void = ptr_2void;

присвоение разрешено, поскольку void** может быть преобразовано в void* без трансляции. Здесь нет ничего особенного в void**; указатель на что-либо может быть преобразован в void* без трансляции. (void* - общий тип указателя; void** не является общим типом указателя на указатель, и на самом деле не существует общего типа указателя на указатель.)

В вашем втором примере кода:

int * ptr_int;
void ** ptr_2void;
ptr_int = ptr_2void;

присвоение недействительно; это нарушение ограничения. Нет никакого неявного преобразования между int* и void**, так как ни один из них не является void*. Любой соответствующий компилятор C должен выдать диагностическое сообщение для назначения. В некоторых случаях диагностика может быть предупреждением, и компилятор, вероятно, генерирует неявное преобразование, как если бы вы написали акт. В других случаях компилятор может потребовать дополнительных параметров, чтобы заставить его диагностировать это нарушение.

Обратите внимание, что указанное выше не относится к указателям на функции. Любой тип указателя функции может быть преобразован (с актом) в любой другой тип указателя функции, преобразование указателя функции в void* или наоборот имеет поведение undefined (хотя оно может поддерживаться некоторыми компиляторами).

Ответ 3

void** и void* - разные типы. int* и void** тоже разные типы. Но, как говорит Бармар, any data pointer type can be cast to/from void*. Это означает, что вы можете использовать int* для void*, но вы не можете использовать int* для void**, так как void** не имеет этого же специального свойства.

gcc должно выдать предупреждение:

warning: assignment from incompatible pointer type [enabled by default]

     ptr_int = ptr_2void;

Смотрите этот вопрос: Переход к void ** вместо void * заставляет компилятор жаловаться на типы, почему?

void * - это тип, который неявно конвертируется в любой объект и из него тип указателя. void ** - нет, поэтому, если вы можете назначить char * void *, вы не можете сделать то же самое с char ** и void **.

Причина в том, что они являются несовместимыми типами: char ** указывает на char *, void ** указывает на пустоту *, поэтому их базовые типы не совпадают.