C / C ++ Указатель Вопрос - PullRequest
       6

C / C ++ Указатель Вопрос

5 голосов
/ 08 февраля 2010

tl; dr - Не могли бы вы расширить 4 комментария в первом фрагменте кода ниже? В частности, что означает быть deref

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

   int x, *p, *q;
   p = new int;
   cin >> x;
   if (x > 0) q = &x;
   *q = 3;                 // 1. deref of possibly uninitialized ptr q
   q = p;
   p = new int;            // 2. potential storage leak (if x != 0 this
                           //     memory will not be returned to free storage)
   *p = 5;
   delete q;
   *q = 1;                 // 3. deref of deleted ptr q
   q = p;
   if (x == 0) delete q;
   (*p)++;                 // 4. deref of possibly dangling ptr p (if x is zero)

Хотя мне показалось, что я понимаю, как работают указатели, мне трудно понять комментарии.

Мой дубль;

  1. Мы либо присваиваем x (& * q, конечно) 3 ИЛИ, если q! = & X, тогда q имеет справедливое значение, поскольку оно было неинициализировано, и мы только что присвоили случайный фрагмент памяти значению 3. I я не уверен, как можно разыменовать что-то, что не инициализировано?
  2. Это хорошо
  3. Что не так с разыменованием удаленного указателя? После «Удалить q» * q бессмысленно?
  4. Что не так с висящими указателями? Является ли память пригодной для перераспределения теперь, когда мы удалили ее, хотя у нас еще есть указатель на нее?

Я думаю, что мое основное недоразумение заключается в том, чтобы просто объявить int-указатель, это тоже выделяет память? Это в стеке или в куче?

Кроме того, разыменование означает просто «прочитать значение по адресу указателя»? Я думаю, что моя путаница заключается в том, что я интерпретирую это как потерю ссылки на некоторые данные, как в;

int *x;
x = new int;
*x = 5;
x = new int; // Dereferencing the first bit of memory allocated.

Спасибо за ваше терпение. Надеюсь, что в этом есть какой-то смысл,

Гав

Ответы [ 5 ]

6 голосов
/ 08 февраля 2010

Проще говоря: указатель - это адрес переменной, значения которой могут вас заинтересовать.

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

  1. Если вы не инициализируете указатель на действительный адрес (или специальное значение NULL) - вы не знаете, что содержит эта переменная. Разыменование будет пытаться взять все, что есть, и рассматривать его как адрес. Это неопределенное поведение - все ставки сняты, все может произойти, хотя, если вам повезет, вы получите ловушку / аппаратное исключение.

  2. Точно. Потому что p содержал адрес некоторой памяти, которую вы выделили. Не освобождение системных ресурсов приводит к утечке. Разыменование удаленного указателя аналогично разыменованию неинициализированного указателя - вы не знаете, какое значение оно может содержать.

  3. Снова UB.

  4. True. У вас есть UB для x == 0. Висячие указатели опасны, потому что они подкрадываются в самый неподходящий момент, форматируют ваш жесткий диск и больше никогда не встречаются. Становится невозможным обоснованно обосновать, как ваша программа будет вести себя.

3 голосов
/ 08 февраля 2010

Добавлены дополнительные комментарии ниже:

   int x, *p, *q;
   p = new int;
   cin >> x;
   if (x > 0) q = &x;
   *q = 3;                 // <--- 1. deref of possibly uninitialized ptr q
                           // 
                           // q has never been set or is set to point at a non
                           // allocated memory location (if x > 0)
                           //
                           // If x is zero q is uninitialized (has random value).
                           // Therefore de-referencing it to set what it points at 
                           // to 3 has undefined behavior.

   q = p;
   p = new int;            // <--- 2. potential storage leak (if x != 0 this
                           //     memory will not be returned to free storage)
                           // 
                           // This comment is not true. The value pointed at by p
                           // was transferred to q before p was re-assigned. Thus 
                           // there is no potential memory leak. Both p and q are 
                           // now valid pointers.
   *p = 5;
   delete q;
   *q = 1;                 // <--- 3. deref of deleted ptr q
                           // 
                           // In the line above you returned the memory pointed at 
                           // by q back to memory management routines (via delete). 
                           // Technically q still points at the memory but you no 
                           // longer own that memory and thus writing to it has
                           // undefined behavior (because the memory management 
                           // system could have unloaded that chunk from virtual 
                           // memory or re-used it some other way).
   q = p;
   if (x == 0) delete q;
   (*p)++;                 // <--- 4. deref of possibly dangling ptr p (if x is zero)
                           // 
                           // q is reassinged to have the same pointer value as p.
                           // Then If x is zero we delete the pointer q (thus 
                           // returning it to the memory management pool). Since p 
                           // and q are the same pointer they both become invalid at
                           // point. Thus de-referencing p is undefined behavior if
                           // x is zero (because the memory was returned (via delete)

1 Мы либо присваиваем x (& * q, конечно) 3 ИЛИ, если q! = & X, тогда q имеет справедливое значение, поскольку оно было неинициализировано, и мы только что присвоили случайный фрагмент памяти значению 3. Я не уверен, как вы можете разыменовать то, что не инициализировано?

См. Ниже:

3 Что не так с разыменованием удаленного указателя? «Удалить q» бессмысленно * q?

Память принадлежит кому-то другому (управление памятью (или в поточном приложении она, возможно, уже была перераспределена в другой поток). Запись в память, которая не принадлежит вам, имеет неопределенное поведение.

4 Что не так с висящими указателями? Является ли память пригодной для перераспределения теперь, когда мы удалили ее, хотя у нас еще есть указатель на нее?

Тот же комментарий, что и 3. (только потому, что вы сохраняете недопустимый указатель, ничего не значит)

Также разыменование означает просто «прочитать значение по адресу указателя»? Я думаю, что моя путаница заключается в том, что я интерпретирую это как потерю ссылки на некоторые данные, как в;

Да: это все, что это значит.
Но если у вас нет этой памяти, могут произойти неприятные вещи.

  • Если указатель указывает на то место в памяти, которое не сопоставлено с физической памятью, вы получите некоторую форму ошибки сегментации.
  • Если память принадлежит подпрограммам управления памятью и вы начинаете записывать в нее случайные значения (как 3 выше), то вы можете повредить структуры управления памятью, что приведет к сбою программы при любом управлении памятью.
  • Если память принадлежит стеку, и вы начинаете записывать случайные значения (как 3 выше), то вы можете потенциально перезаписать другую случайную переменную или адрес возврата, используемый при выходе из функции.
2 голосов
/ 08 февраля 2010

Это сложный предмет, если вы не знаете, что происходит «под капотом». C ++, скорее, «голый металлический» язык программирования и редко делает что-либо безопасным.

Глядя на код, который вы предоставляете, и отмеченные достопримечательности:

Первая строка имеет: int x, *p, *q;, который объявляет и определяет некоторые переменные в стеке, но не инициализирует их. Также C ++ не помнит во время выполнения, что это произошло. Вы можете получить некоторые предупреждения во время компиляции, но в противном случае вам придется отслеживать вещи в своей голове.

Итак, в строке 1. мы не знаем, равно ли q &x или нет. Если q не инициализирован, то он может указывать куда угодно, но C ++ не знает об этом и попытается записать значение 3 в какой-то фрагмент памяти. Ответ на этот вопрос заключается в том, что C ++ не отслеживает значения указателей.

В строке 3. снова C ++ пытается записать целочисленное значение в некоторый фрагмент памяти, но на этот раз его в куче и в многопоточной программе, возможно, было перераспределено к моменту выполнения этой строки. Так что, к сожалению, q и *q сохраняют значение, но вряд ли оно будет означать то, что мы хотим.

В строке 4. это та же проблема, что и 3., но только если (x==0), и, как вы говорите, да, память могла быть перераспределена, даже если мы ее удалили.

Объявление указателя типа int выделяет память только для указателя в стеке.

Разыменование указателя означает доступ к памяти для чтения или записи, на которую указывает указатель.

Что касается вашего второго куска кода:

int *x;         <-- Declares a pointer and allocates it on the stack
x = new int;    <-- Allocate a new int on the heap and remember its address in x
*x = 5;         <-- Overwrite the new int on the heap.
x = new int;    <-- Another allocation and remember its address in x
                    Now we have forgotten where the first allocation was

Разыменование означает чтение или запись в память, на которую указывает указатель. Если вы просто читаете или пишете в сам указатель, то это всегда безопасно. Вот почему вы можете обойти указатель null и попадать в неприятности только тогда, когда он разыменовывается в первый раз.

2 голосов
/ 08 февраля 2010

В:

  1. Вы присваиваете значение для q указателя путем разыменования его из адреса в конкретную память см. Документ разыменования указателя. . Однако из-за условия if, устанавливающего его на адрес x , он может указывать на случайную ячейку памяти (не инициализирован), если x <= 0.
  2. В строке выше вы устанавливаете q для указания того же адреса памяти, что и p. Затем для p вы выделяете новую память. А затем вы выделяете новую память, на которую указывает p.
  3. В строке выше вы удалили выделение памяти, обозначенное q. Этот адрес памяти теперь доступен для использования системой. И после этого вы присваиваете памяти значение, которое вам не «принадлежит».
  4. Если x == 0, то вы возвращаетесь в системную память, указанную как q и p. И снова пытаюсь использовать память, которая не «принадлежит» вам. Также нет delete p, если x != 0 - таким образом, память не возвращается в систему.
1 голос
/ 08 февраля 2010

Ответов на ваши дубли:

  1. Значение x читается из потока. Это может привести к сбою, оставляя x неинициализированным (сохраняя ненужное значение). Таким образом, поведение if ненадежно, и, следовательно, он может не назначать адрес q. Таким образом, присваивание *q = 3 будет записывать в случайный адрес памяти.

  2. Это не хорошо! Там нет сборки мусора. Первый объект был размещен в куче, а его адрес был назначен p. Теперь вы выделяете второй и перезаписываете указатель для хранения нового адреса. Старый адрес потерян, поэтому никто не может удалить его через p. Позже есть delete q, который является копией адреса из p, но это утверждение зависит от значения x (которое ненадежно).

  3. Это не указатель, который был удален. Это объект, на который указывает. Указатель по-прежнему содержит значение, которое указывает на то, что раньше было допустимым объектом, но теперь является ненужным. Если вы разыменовываете его, ваша программа будет иметь «неопределенное поведение».

  4. Да. Во время выполнения система (такая, как она) не отслеживает указатели на объекты.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...