Указатель (трюк): ссылка на память - PullRequest
0 голосов
/ 07 сентября 2010

Вопрос с подвохом о C-указателе. Прочитайте фрагмент кода ниже и попробуйте объяснить, почему изменилось значение списка (этот вопрос был основан на этом коде):

tail имеет адрес памяти list .

Как можно список изменить ниже?

typedef struct _node {
   struct _node *next;
   int value;
}Node;


 int main(){
   Node *list, *node, *tail;
   int i = 100;

   list = NULL;
   printf("\nFirst . LIST value = %d", list);

  tail =(Node *) &list;
  node = malloc (sizeof (Node));
  node->next = NULL;
  node->value = i;

  //tail in this point contains the memory address of list
  tail->next = node;
  printf("\nFinally. LIST value = %d", list);
  printf("\nLIST->value = %d", (list->value));

return 0;

}

---- Вывод

Первый. Значение списка = 0

почему это значения ??? я не ожидаю этого ...

Наконец. LIST = 16909060

LIST-> value = 100

Ответы [ 8 ]

10 голосов
/ 07 сентября 2010

Давайте посмотрим, что происходит с памятью в вашей программе.Вы начинаете с 3 локальных переменных, все типа Node*.На данный момент все они указывают на мусор, так как они были объявлены, но не инициализированы.

Возможно, диаграмма памяти ascii (макет зависит от реализации)

      list   node   tail
  --------------------------
... | 0xFE | 0x34 | 0xA3 | ...
  --------------------------

Вызатем установите список на NULL, а tail на адрес узла (отбрасывая его тип, плохая идея), давая вам

      list   node   tail
  --------------------------
... | NULL | 0xFE | &list | ...
  --------------------------
        ^             |
        +-------------+

Затем вы malloc новый Node, список настроекна его адрес.

      list    node   tail          next  value
  ---------------------------  ------------------
... | NULL | &next | &list | ... | NULL | 100 | ...
  ---------------------------  ------------------
        ^      |       |             ^
        |      +---------------------+
        +--------------+

Далее вы пытаетесь установить tail->next на узел.Вы сказали, что знаете, что tail указывает на Node, когда вы выполняли приведение типа, поэтому компилятор вам верит.Хвост Node указывает на начало с адреса list, например

     tail                                list
     next    value                       next   value
  ----------------------------------  ------------------
... | NULL | &list->next | &list | ... | NULL | 100 | ...
  ----------------------------------  ------------------

Затем вы устанавливаете tail->next на node, в результате чего list и node указывают наlist структура.

      list    node   tail          next  value
   ---------------------------  ------------------
... | &next | &next | &list | ... | NULL | 100 | ...
   ---------------------------  ------------------
       | ^     |       |             ^
       | |     +---------------------|
       | +-------------+             |
       +-----------------------------+

Вы напечатали list как целое число со знаком ("% d").Это плохая идея - если вы используете 64-битную машину и имеете другие аргументы в выражении printf, они могут быть засорены, вместо этого используйте формат указателя («% p»).list->value совпадает с node->value, поэтому все равно будет 100.

Указатели станут проще, если вы подумаете о том, как они на самом деле представлены в машине - как указатель на огромный массивв котором хранятся все ваши данные (размеры указателей по модулю, виртуальная память и т. д.).

В следующий раз будет проще использовать list = node.

2 голосов
/ 07 сентября 2010

Назначая адрес из list для tail, вы заставляете list и tail->next ссылаться на одно и то же место в памяти; когда вы назначаете одну, вы забиваете другую.

Давайте начнем с рассмотрения гипотетической карты памяти node после выделения и назначения (при условии 4-байтовых указателей и целых чисел):

Object     Address      0x00  0x01  0x02  0x03
------     -------      ----  ----  ----  ----
node       0x08000004   0x10  0x00  0x00  0x00  // points to address 0x10000000
...
node.next  0x10000000   0x00  0x00  0x00  0x00  // points to NULL
node.value 0x10000004   0x00  0x00  0x00  0x64  // value = 100 decimal

Когда вы пишете node->next = NULL, вы назначаете NULL для ячейки памяти 0x10000000. IOW, значение node соответствует адресу, где будет найдено node->next.

Теперь давайте рассмотрим гипотетический макет list, node и tail после того, как вы назначите list и tail

Object     Address      0x00  0x01  0x02  0x03
------     -------      ----  ----  ----  ----
list       0x08000000   0x00  0x00  0x00  0x00  // after list = NULL
node       0x08000004   0x10  0x00  0x00  0x00  // after node = malloc(sizeof *node);
tail       0x08000008   0x08  0x00  0x00  0x00  // after tail = (Node*) &list;

Итак, вот карта памяти tail после того, как вы присвоили tail->next:

Object     Address      0x00  0x01  0x02  0x03
------     -------      ----  ----  ----  ----
tail       0x08000008   0x08  0x00  0x00  0x00  // points to address 0x80000000,
...                                             // which is where list lives
tail.next  0x08000000   0x08  0x00  0x00  0x04  // points to node
tail.value 0x08000004   0x10  0x00  0x00  0x00  // value = some big number

Presto: list теперь содержит адрес node.

Пожалуйста, ради любви Бога, никогда не делайте этого в коде производства.

2 голосов
/ 07 сентября 2010

Проблема в настройке

tail = (Node*) &list

Таким образом, список - это Node *, tail - это Node **, который приводится к Node *.Теперь здесь

tail->next == (*tail)+0 == (*&list)+0

таким образом

tail->next == list

Таким образом, изменение tail-> next аналогично изменению списка.

2 голосов
/ 07 сентября 2010

Линия

tail =(Node *) &list;

присваивает адрес list tail. Поскольку &list является Node **, компилятору это назначение не нравится по умолчанию, поэтому вы добавляете явное приведение, чтобы заставить его замолчать. Тогда

tail->next = node;

изменяет значение члена в структуре, на которую, якобы, указывает tail (по крайней мере, компилятор считает, что это структура, поскольку вы явно указали это). Поскольку next является первым членом структуры, его адрес, скорее всего, такой же, как и у самой структуры. А поскольку tail указывает на адрес list, в действительности это назначение изменяет значение list, которое является указателем на _node. То есть list указывает на node.

Что вы, вероятно, хотите, это

Node** tail;
...
tail = &list;
...
(*tail)->next = node;

То есть объявите tail как указатель на указатель на _node и добавьте дополнительное косвенное указание (*) при назначении значения через него.

2 голосов
/ 07 сентября 2010

Следующая строка неверна:

tail =(Node *) &list; 

Вы берете адрес списка переменных, который на самом деле имеет тип Node **. Затем вы бросаете его на Node *. Хотя вы можете сделать это в C / C ++, вероятно, вы этого не хотите.

Чтобы получить желаемое поведение, tail должен иметь тип Node **. Таким образом, приведение не требуется, и в конце вам нужно написать (*tail)->next = node.

2 голосов
/ 07 сентября 2010

Хвост причины имеет адрес памяти списка в этой строке

tail =(Node *) &list;

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

И так как tail и list оба указывают на один и тот же адрес, то есть основы настройки структуры данных связанного списка.

Edit: Кстати, есть NO ссылка на Node, поскольку у вас есть struct _node , объявленный ... с учетом проводки кода ОП, которая не указана Node ....

0 голосов
/ 07 сентября 2010

Если память неожиданно меняется, самый быстрый способ отследить проблему - это настроить точку останова, зависящую от изменения памяти, включая размер интересующего блока памяти - 4 в этом случае, при условии, что это 32-битный указатель платформы , В Windows (Visual Studio IDE или Windbg) это легко сделать - у меня нет информации о других системах.

Обычно вы очень быстро найдете причину проблемы.

0 голосов
/ 07 сентября 2010

Разве эта строка не ...

tail =(Node *) &list;

присваивает хвосту адрес указателя на список, а не адрес списка?

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