Синтаксис указателя C

В чем разница между этими двумя строками кода?

int *ptr = &x; 

а также

 void* q = &x; int* p = q; 

Я очень новичок в C и концепции указателей, которые были преподаны в основном на Java, поэтому немного запутались.

Заранее спасибо.

void * используется для обозначения общего указателя в C.
Это означает, что он может указывать на любой тип.

Итак, в первом случае int *ptr = &x; вы используете указатель на int поэтому любой, кто его использует, знает, что он манипулирует целым числом.

Во втором случае void* q = &x; вы указываете на целочисленный адрес с помощью указателя gereric.
Проблема в том, что неясно, к какому типу относится этот указатель.

Таким образом, первый и два примера имеют одинаковый эффект (в вашем конкретном примере), но void * небезопасно для использования таким образом.

void * q = & x;

Вероятно, стоит понять, как вещи представлены в памяти, поэтому вы понимаете значение void* . Указатель – это позиция в памяти, содержащая другую позицию в памяти. Для примера в реальном мире рассмотрим следующий код:

 int x = 4; int* y = &x; void* z = &x; 

В моей 64-битной системе я могу использовать gdb для изучения памяти. Результаты приведены в следующей таблице:

 Address | Memory Value | C expression ================================================================================ 0x7fffffffdf8c | 0x04 0x00 0x00 0x00 | int x = 4; 0x7fffffffdf98 | 0x8c 0xdf 0xff 0xff 0xff 0x7f 0x00 0x00 | int* y = &x; 0x7fffffffdf90 | 0x8c 0xdf 0xff 0xff 0xff 0x7f 0x00 0x00 | void* z = &x; 

Что мы видим здесь? Ну, 0x7fffffffdf8c до 0x7fffffffdf90 занято значением 0x04 а все нули-целые числа на моей платформе имеют ширину 4 байта, а порядок немногочисленный, поэтому байты противоположны тому, что человек ожидал бы прочитать. Затем мы видим, что следующие 8-байты заняты не чем иным, как адресом x, а также снова для второго указателя.

Таким образом, процесс использования указателя состоит в том, чтобы прочитать адрес по адресу и работать с ним. Похоже, вы вполне довольны этой концепцией, как есть, поэтому, двигаясь дальше:

Тип указателя не влияет на размер указателя . Это ключ. Рассматривая оба значения указателя выше, они фактически имеют одинаковый размер. Значение размера здесь говорит о целевой памяти – он инструктирует компилятор загружать и работать с определенной суммой (количество байтов – размер / ширина типа) памяти.

void* , как говорили другие, является «беспричинным». Скорее, это всего лишь указатель, а язык компилятора / языка не сможет поддерживать вас, разыменовывая его, поскольку информация о типе отсутствует – нет способа безопасно рассказать, сколько памяти на целевом адресе вы хотите прочитать.

Однако этот факт иногда полезен. Идея использования типов заключается в обеспечении согласованности кода – если функция ожидает 64-битного целого числа, использование типов обеспечивает выполнение этого требования, чтобы вы не вводили ошибки. Иногда, однако, вы не возражаете, какой тип вы получаете. В этих случаях ваши требования – «какая-то память, любая память!» – лучший пример этого, о котором я могу думать, это memcpy – который может немного походить на это:

 void *memcpy(void * s1, const void* s2, size_t n) { char *r1 = s1; const char *r2 = s2; while (n) { *r1++ = *r2++; -n; } return s1; } 

Адаптировано из uclibc . Здесь типы переменных вообще не имеют значения – внутренне функция решает манипулировать памятью в типах sizeof(char) ( char обычно, но не всегда байтовый), но он мог бы также работать на uint64_t или какое-то другое значение. Все типы здесь управляют тем, сколько байтов рассматривается с начального адреса как часть этого типа.

Чтобы дать вам другой стол, сравните некоторые размеры:

 Address of ptr | Type in code | Memory it "sees" when dereferenced =========================================================================== 0x7fffffffdf90 | unsigned 64-bit | 0x8c 0xdf 0xff 0xff 0xff 0x7f 0x00 0x00 0x7fffffffdf90 | unsigned 32-bit | 0x8c 0xdf 0xff 0xff 0x7fffffffdf90 | unsigned 16-bit | 0x8c 0xdf 0x7fffffffdf90 | unsigned 8-bit | 0x8c 0x7fffffffdf90 | void* | Doesn't know how wide it is. 

Вы можете удивиться, почему в этой функции libc нет никаких бросков – ну, в этом нет необходимости. Все указатели имеют одинаковый размер, поэтому ничего больше не нужно делать.

Эффект для ptr и p в обоих случаях одинаковый, так как указатель void * гарантированно может быть конвертирован между литьями.

void *q = &x просто сохраняет адрес x в q , не заботясь о том, на что указывает x . int *p = q затем присваивает этот адрес сущности, семантика которой описывает, что она указывает на int .

Обратите внимание, что в этом случае операция безопасна, поскольку x имеет тип int и p имеет тип int * . Это было бы не так, если бы вы назначили q указателю типа, скажем, double * .

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

Основная цель типов заключается в том, что они не позволяют вам присваивать переменные, которые несовместимы, например, яблоки и автомобили.

Таким образом,

void* q = &x;

избавляется от типа х

 int* p = q; 

и это приводит к неизвестному типу указателям на целые числа. Лучше всего использовать q для (int *), чтобы обозначить, что вы знаете об опасности.