Я думаю, что это можно объяснить таким образом, поскольку картинка стоит тысячи слов ...
Мы начнем с char name[] = "Fortran"
, который является массивом символов, длина известнаво время компиляции 7, если быть точным, верно?Неправильно!это 8, так как '\ 0' является нулевым завершающим символом, все строки должны иметь его.
char name[] = "Fortran";
+======+ +-+-+-+-+-+-+-+--+
|0x1234| |F|o|r|t|r|a|n|\0|
+======+ +-+-+-+-+-+-+-+--+
Во время компиляции и компоновщик дал символу name
адрес памяти0x1234.Используя оператор индекса, например, name[1]
, компилятор знает, как вычислить, где в памяти находится символ со смещением, 0x1234 + 1 = 0x1235, и это действительно 'o'.Это достаточно просто, кроме того, со стандартом ANSI C размер типа данных char
составляет 1 байт, что может объяснить, как среда выполнения может получить значение этой семантики name[cnt++]
, предполагая, что cnt
является int
eger и имеет значение 3, например, время выполнения автоматически увеличивается на единицу, и, считая от нуля, значение смещения равно 't'.Это просто пока все хорошо.
Что произойдет, если name[12]
был выполнен?Ну, код либо вылетит, либо вы получите мусор, поскольку граница массива составляет от индекса / смещения 0 (0x1234) до 8 (0x123B).Все, что после этого не принадлежит переменной name
, это называется переполнением буфера!
Адрес name
в памяти равен 0x1234, как в примере, если вы должны были сделать это:
printf("The address of name is %p\n", &name);
Output would be:
The address of name is 0x00001234
Для краткости и в соответствии с примером адреса памяти 32-битные, поэтому вы видите дополнительные 0.Справедливо?Хорошо, давайте двигаться дальше.
Теперь перейдем к указателям ... char *name
- указатель на тип char
....
Редактировать: Имы инициализируем его в NULL, как показано Спасибо Дэну за указание на небольшую ошибку ...
char *name = (char*)NULL;
+======+ +======+
|0x5678| -> |0x0000| -> NULL
+======+ +======+
Во время компиляции / компоновки name
ни на что не указывает, ноимеет адрес времени компиляции / ссылки для символа name
(0x5678), фактически это NULL
, адрес указателя name
неизвестен, следовательно, 0x0000.
Теперь запомните , это очень важно, адрес символа известен во время компиляции / компоновки, но адрес указателя неизвестен при работе с указателями любого типа
Предположим, что мы делаем это:
name = (char *)malloc((20 * sizeof(char)) + 1);
strcpy(name, "Fortran");
Мы позвонили malloc
, чтобы выделить блок памяти для 20 байтов, нет, это не 21, причина, по которой я добавил 1 к размеру, заключается в нулевом завершающем символе '\ 0',Предположим, что во время выполнения адрес был 0x9876,
char *name;
+======+ +======+ +-+-+-+-+-+-+-+--+
|0x5678| -> |0x9876| -> |F|o|r|t|r|a|n|\0|
+======+ +======+ +-+-+-+-+-+-+-+--+
Так что, когда вы делаете это:
printf("The address of name is %p\n", name);
printf("The address of name is %p\n", &name);
Output would be:
The address of name is 0x00005678
The address of name is 0x00009876
Теперь, это иллюзия, что ' массивы и указателито же самое происходит здесь '
Когда мы делаем это:
char ch = name[1];
Что происходит во время выполнения, это:
- Адрес символа
name
ищется - Получает адрес памяти этого символа, то есть 0x5678.
- По этому адресу содержит другой адрес, адрес указателя на память и извлекает его, то есть 0x9876
- Получить смещение на основе значения индекса 1 и добавить его к адресу указателя, то есть 0x9877, чтобы получить значение по этому адресу памяти, т. Е. 'O' и присвоить
ch
.
Это важно для понимания этого различия, различие между массивами и указателями заключается в том, как среда выполнения извлекает данные, с указателями существует дополнительная косвенная выборка.
Помните , массив типа T всегда будет распадаться на указатель первого элемента типа T .
Когда мы сделаем это:
char ch = *(name + 5);
- Адрес символа
name
ищется - Получает адрес памяти этого символа, то есть 0x5678.
- По этому адресу содержит другой адрес, адрес указателя на память и извлекает его0x9876
- Получите смещение на основе значения 5 и добавьте его к адресу указателя, то есть 0x987A, чтобы получить значение по этому адресу памяти, т. е. 'r' и присвоено
ch
.
Кстати, вы также можете сделать это с массивом символов также ...
Более того, использование операторов индекса в контексте массива, т. Е. char name[] = "...";
и name[subscript_value]
, в действительности совпадает с * (name + subscript_value).
т.е.
name[3] is the same as *(name + 3)
А поскольку выражение *(name + subscript_value)
является коммутативным , то есть наоборот,
*(subscript_value + name) is the same as *(name + subscript_value)
Следовательно, это объясняет, почему в одном из приведенных выше ответов вы можете написать это так ( несмотря на это, практика не рекомендуется, даже если она вполне законна! )
3[name]
Хорошо, как мне получить значение указателя?
Вот для чего используется *
,
Предположим, что указатель name
имеет адрес памяти указателя 0x9878, опять же, ссылаясь на приведенный выше пример, вот как это достигается:
char ch = *name;
Это означает, что получить значение, на которое указывает адрес памяти 0x9878, теперь ch
будет иметь значение 'r'. Это называется разыменованием. Мы просто разыменовали указатель name
, чтобы получить значение и присвоить его ch
.
Кроме того, компилятор знает, что sizeof(char)
равно 1, следовательно, вы можете выполнять операции увеличения / уменьшения указателя, как это
*name++;
*name--;
Указатель автоматически поднимается / опускается в результате на единицу.
Когда мы делаем это, предполагая, что адрес памяти указателя равен 0x9878:
char ch = *name++;
Какое значение * name и каков адрес, ответ: *name
теперь будет содержать 't' и присвоит его ch
, а адрес памяти указателя будет 0x9879.
Здесь вы также должны быть осторожны, в том же принципе и духе, что и то, что было сказано ранее относительно границ памяти в самой первой части (см. «Что произойдет, если имя [12] исполнится» в выше) результаты будут такими же, т.е. код вылетает и горит!
Теперь, что произойдет, если мы освободим блок памяти, на который указывает name
, вызвав функцию C free
с name
в качестве параметра, т.е. free(name)
:
+======+ +======+
|0x5678| -> |0x0000| -> NULL
+======+ +======+
Да, блок памяти освобождается и возвращается в среду выполнения для использования другим предстоящим исполнением кода malloc
.
Теперь, это то место, где вступает в игру общее обозначение Ошибка сегментации , поскольку name
ни на что не указывает, что происходит, когда мы разыменовываем это, т.е.
char ch = *name;
Да, код будет аварийно завершать работу с «ошибкой сегментации», это часто встречается в Unix / Linux. Под окнами появится диалоговое окно в виде строк «Неустранимая ошибка» или «Произошла ошибка с приложением, вы хотите отправить отчет в Microsoft?» .... если указатель не был malloc
d и любая попытка разыменования, гарантированно потерпит крах и сгорит.
Также: запомните, для каждого malloc
есть соответствующий free
, если нет соответствующего free
, у вас есть утечка памяти, в которой память выделяется, но не освобождается.
И вот, у вас это есть, вот как работают указатели и как массивы отличаются от указателей, если вы читаете учебник, в котором говорится, что они одинаковые, оторвите эту страницу и порвите ее! :)
Надеюсь, это поможет вам в понимании указателей.