#c #pointers
#c #указатели
Вопрос:
В настоящее время я изучаю указатели в своем классе алгоритмов C , и хотя я понимаю материал, я не понимаю, почему C заставляет указатели указывать на слот памяти, а не только на определенное значение, которое вы могли бы изменить позже.
Казалось бы, вы хотели бы иметь возможность динамически создавать новую переменную в том самом слоте памяти, который занимает ваш указатель.
Это потому, что операционная система создает все динамические переменные в куче, и нам нужен способ их найти, потому что наш указатель создается при загрузке программы?
Если это так, то я это полностью понимаю, но если это не так, то это кажется пустой тратой места в памяти и дополнительным процессом, который наши программы должны пройти, чтобы получить доступ к целевому значению указателя.
Комментарии:
1. «Определенное значение, которое вы могли бы изменить позже»? Вы имеете в виду переменную в памяти?
2. Поиграйте со сборкой несколько дней, и это будет иметь гораздо больше смысла. Я знал, как использовать указатели до asm, но на самом деле не понимал, почему. Ваша программа также будет загружена в память, и жесткое кодирование всех ячеек памяти также занимает место. Увеличение указателя может быть короче, чем сохранение полного адреса следующей ячейки памяти. Такое маленькое пространство обычно не является проблемой.
Ответ №1:
Память фактически сохраняется в случае, когда у вас есть большие объекты (под большими я подразумеваю больше 4 или 8 байт, т. Е. sizeof(void *)
). Вместо того, чтобы платить за дорогостоящие операции копирования и перезаписи, вы просто передаете указатель, который меньше объекта, и присваиваете его таким образом.
Динамическое распределение предназначено для распределения, которое вы не можете предсказать заранее, например, массивы переменного размера и различные объекты состояния, которые вам могут понадобиться, а могут и не понадобиться в зависимости от условий. Вы действительно не тратите память вообще. Если бы вы передавали их по значению, вы бы потратили больше времени и памяти.
Сам указатель является адресом памяти, но то, на что он указывает, является значением в памяти. Это также относится к чему-то вроде int **
, потому что это указывает на значение указателя в памяти.
Операционная система не создает их динамически, это делаете вы. Вы говорите программе выделить новый объект или блок памяти определенного размера, и ОС просто отдает его вам.
Вы всегда можете повторно использовать указатели. Например, если я выделяю пространство для 100-байтового массива, я всегда могу использовать его повторно, пока я сохраняю указатель на него, при условии, что я не освобождаю его, пока не закончу с этим пространством (на самом деле вам рекомендуется это сделать).
Комментарии:
1. Это безумие, но я действительно это понимаю. Спасибо.
2. У вас могут быть массивы переменной длины без динамического выделения (см.
alloca
и C99 VLAS).3. @FredOverflow Я пытался привести примеры, и это был один из самых простых, которые я мог придумать.
Ответ №2:
Ваш вопрос лучше всего было бы проиллюстрировать примерами кода и контрастами A / B. (Это довольно абстрактно, как указано, и трудно понять, что вы можете иметь в виду.)
C предлагает вам выбор для размещения объектов в «стеке» или «динамически»:
/* foo is an integer on "the stack" */
int foo = 1;
/* bar is a pointer on "the stack", *bar is an integer on "the heap" */
int* bar = new int (1);
Оба bar
и amp;foo
указывают на целые числа, значение которых 1
. Верно сказать, что bar
потребляет память не только для этого целого числа, но и для переменной стека, которая отслеживает указатель на нее.
Проблема в том, что как только область действия функции или блока заканчивается, переменные, объявленные в стеке, исчезают. Тем не менее, иногда вам хотелось бы говорить об объекте или количестве позже и явно управлять его временем жизни. В этих случаях этот «ненужный» описательный термин, который является указателем, становится необходимым.
При прочих равных условиях вам не следует использовать динамическое распределение. Программисты на C часто пытаются побудить программистов на C переосмыслить свой код, чтобы он не нуждался в нем. Однако эта «дополнительная память для хранения указателя» является наименьшей из ваших проблем с точки зрения того, почему следует избегать практики. В основном речь идет о висячей ответственности за то, чья работа заключается в конечном итоге в выполнении delete
…
Комментарии:
1. Таким образом, переменные в стеке исчезают, а переменные в куче остаются? Если вы объявляете свой указатель в функции, как вы собираетесь его найти после того, как функция вышла из области видимости? Есть ли у вас глобальная точка на это?
2. @JeramyRR Даже в языках, собирающих мусор, вы должны заставить результат «нового объекта» передаваться обратно через возвращаемое значение, глобальный параметр или параметр «по ссылке». Единственная разница в том, что в C , если вы
new
что-то делаете, но потом никому это не возвращаете, нет возможной цепочки ответственности за выполнениеdelete
… (вот почемуshared_ptr
и другие «умные указатели» классные). Но вам нужно быть осторожным, чтобы не возвращать адреса переменных, выделенных стеком , из функции ее вызывающему… эти указатели не будут полезны после возврата! :-/3. Если я использую delete, скажем, * DeleteMe, то это приведет только к удалению памяти, которая содержит адрес другой переменной, верно? На самом деле это не приведет к удалению переменной, на которую она указывает, не так ли?
4. В моем примере
delete bar
освобождает блок памяти целочисленного размера, содержащий значение1
. После этого момента вам не следует разыменовывать указатель с помощью*
, чтобы попытаться получить целое число. Но поскольку целочисленный указательbar
все еще находится в стеке, его можно повторно использовать в качестве цели присвоения (или другого выделения). Обратите внимание, что можно создавать указатели на pointers…so вы могли бы написатьint** frotz = new int* (new int (1));
. В таком случае, если вы сказалиdelete frotz
, что это приведет к удалению целочисленного указателя, но не самого int (вам придется сохранить указатель в другом месте, чтобы избежать утечки).
Ответ №3:
Чтобы добавить немного к другим хорошим ответам, я думаю, что понимание того, как работают компьютеры, также может быть полезным для понимания указателей и зачем они нам нужны. По моим собственным словам — каждая логическая операция в компьютере выполняется процессором (в самом простом случае, конечно). Сам процессор имеет чрезвычайно мало памяти. Например, 32-разрядный процессор i386 имеет 8 регистров, с которыми он может работать, где каждый регистр может содержать только 4 байта данных (32-разрядный). Итак, 32 байта — это все, что может поместиться в CPU. Остальные данные хранятся в других цепях, таких как кэш ЦП, ОЗУ и т.д. Итак, когда вы хотите, чтобы процессор что-то сделал, вы должны попросить его загрузить данные из внешних цепей, и именно здесь вам нужно «указать» CPU на то, что именно вы хотите, чтобы он загрузил, и вы должны указать ему «адрес» памяти. А указатели C — это именно те адреса, которые предоставляют вам самый низкий уровень адресации ячеек памяти в таких высокоуровневых языках.
Теперь все в вашей программе размещается где-то в памяти (даже регистры процессоров технически являются памятью, но они адресуются по-разному). Будь то какое-то значение в стеке или динамически выделяемая область памяти (в конце концов, стек также динамически выделяется), и вы всегда можете ссылаться на эти объекты по их адресам в памяти. Сам указатель — это значение (4 байта на i386 или 8 на amd64), которое содержит адрес чего-либо (и да, вы также можете указать на это значение, создав указатель на указатель). Таким образом, указатели есть везде, даже если вы их не видите. Например, когда вы копируете какой-то большой «объект», необходимо выделить память для хранения копии этого объекта, а затем процессору необходимо дать указание копировать байты из области исходной памяти в область целевой памяти, байт за байтом, адресуя эти байты «через указатели» (т. Е. вот как могут выглядеть фактические инструкции процессора в ассемблере).
Даже в языках чрезвычайно высокого уровня, таких как Java, указатели есть везде. Они просто скрыты от разработчиков.
Надеюсь, это поможет!
Комментарии:
1. Это действительно помогает. Я также изучаю язык ассемблера CISS 360 прямо сейчас, но семестр только начался, поэтому мы еще не дошли до всего этого.
Ответ №4:
Я не уверен, что полностью понимаю вопрос, но я подозреваю, что вы делаете некоторые неявные предположения о указателях, которые не являются истинными.
Вы спрашиваете, почему C «заставляет указатели указывать на слот памяти, а не только на определенное значение, которое вы могли бы изменить позже». Но слот в памяти на самом деле является единственным допустимым местом для хранения значения! Разумным приближением будет сказать, что все данные во всей вашей программе хранятся в огромной группе слотов. Это похоже на огромный массив.
Память и значения очень тесно переплетены. У вас не может быть одного без другого, и нет другого способа хранить вещи (кроме, может быть, сохранения на диск — и да, регистры тоже есть, но C / C абстрагирует их).
Я также хочу обратиться к тому, как вы говорите: «Казалось бы, вы хотели бы иметь возможность динамически создавать новую переменную в том самом слоте памяти, который занимает ваш указатель».
Место, которое занимает указатель, заполняется адресом, где находится значение указателя (где оно «указывает на»). Если бы вы создали новую переменную в этом слоте, вы бы потеряли все знания о том, на что указывает указатель! Поэтому важно, чтобы мы сохранили в этом слоте памяти нужное нам значение.
«Это потому, что операционная система создает все динамические переменные в куче, и нам нужен способ их найти, потому что наш указатель создается при загрузке программы?»
Я не совсем уверен, что вы подразумеваете под «динамической переменной», но нет, не все указатели хранятся в куче. Простой пример:
int a = 5;
int *b = amp;a;
Обратите внимание, что b — это указатель, который указывает на a, но оба a
и b
находятся в стеке, а не в куче.
Если под динамическим вы подразумеваете «созданное с помощью new
«, то да, справедливо будет сказать, что все вещи, созданные с помощью new, создаются в куче.
Еще одно важное замечание: операционная система (обычно) не управляет распределением памяти. Максимум, что он сделает, это предоставит вам большой кусок памяти кучи, с которым вы можете делать все, что захотите. Если вам интересно, загляните в вызываемую процедуру C malloc
, которая является способом выделения памяти в куче, очень похожим new
. За этим действительно не так много магии.
Ответ №5:
почему C заставляет указатели указывать на слот памяти, а не только на определенное значение, которое вы могли бы изменить позже.
Если да, то зачем вам указатели? Также обратите внимание, что указатели — это предложение «два в одном». Указывая на ячейку памяти, вы также можете получить доступ к значению в указанной памяти, скажем,
int a;
int *b;
b=amp;a;
b >> memmory location
*b >> value
и вот хороший учебник для вас: Указатели и память