Почему указатели указывают на память, а не на значение?

#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
  

и вот хороший учебник для вас: Указатели и память