指针(技巧):内存参考

时间:2010-09-07 11:57:17

标签: c++ c pointers memory-management

关于C指针的技巧问题。请阅读下面的代码段并尝试解释列表值更改的原因(此问题基于this代码):

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

  

为什么这个值???我不期待这个...

  
    

最后。列表值= 16909060

         

LIST-> value = 100

  

8 个答案:

答案 0 :(得分:10)

让我们看一下程序中内存会发生什么。首先是3个局部变量,所有变量都是Node*。目前他们都指向垃圾,因为它们已被宣布但尚未初始化。

内存的ascii art图可能是(布局依赖于实现)

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

然后将列表设置为NULL,将tail设置为节点的地址(丢弃其类型,一个坏主意),给你

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

然后将新的Node设置列表malloc到其地址。

      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,同时listnode指向list结构。

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

您已将list打印为有符号整数(“%d”)。这是个坏主意 - 如果你使用的是64位机器并且在printf语句中有其他参数,那么它们可能会被破坏,请使用指针格式(“%p”)。 list->valuenode->value相同,因此它仍然是100

如果您考虑它们在机器中的实际表现方式,指针会变得更容易 - 作为包含所有数据的大型数组的索引(模数指针大小,虚拟内存等)。

下次使用list = node可能会更容易。

答案 1 :(得分:2)

尾部有列表的内存地址的原因在这一行

tail =(Node *) &list;

表示将list指向的指针地址指定给指针变量tail

由于taillist都指向同一地址,因此这是设置链表数据结构的基础知识。

修改 说到<{>>}, 引用 罢工>修改此项考虑到OP的代码发布,遗漏了Node ....

答案 2 :(得分:2)

以下行错误:

tail =(Node *) &list; 

您获取变量列表的地址,该列表实际上是Node **类型。 然后你把它投到Node *。虽然您可以在C / C ++中执行此操作,但这可能不是您想要的。

要获得所需行为,tail应为Node **类型。因此不再需要强制转换,最后,您需要编写(*tail)->next = node

答案 3 :(得分:2)

该行

tail =(Node *) &list;

list的地址<{1>}分配给tail。由于&listNode **,因此默认情况下编译器不喜欢此赋值,因此您添加显式强制转换以使其静音。然后

tail->next = node;

更改tail所谓的结构中的成员值(至少编译器认为它是一个结构,因为你明确告诉它)。由于next是结构的第一个成员,因此它的地址很可能与结构本身的地址相同。由于tail指向list的地址,实际上此分配会更改list的值,_node是指向list的指针。也就是说,它使node指向Node** tail; ... tail = &list; ... (*tail)->next = node;

你可能想要的是

tail

也就是说,将_node声明为指向*的指针,并在通过它指定值时添加额外的间接({{1}})。

答案 4 :(得分:2)

问题在于设置

tail = (Node*) &list

因此list是Node *,tail是Node **,它被转换为Node *。现在这里

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

从而

tail->next == list

因此改变tail-&gt; next与更改列表相同。

答案 5 :(得分:2)

通过将list地址分配给tail,您可以listtail->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的地址。

现在,让我们在您分配listnode

之后,查看taillisttail的假设布局
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的地址。

请原谅上帝永远不要在生产代码中这样做。

答案 6 :(得分:0)

不是这条线......

tail =(Node *) &list;

指定尾部指向列表的指针的地址,而不是列表的地址?

答案 7 :(得分:0)

如果内存意外更改,追踪问题的最快方法是根据内存更改配置断点,包括感兴趣的内存块的大小 - 在这种情况下为4,假设它是一个32位平台指针。在Windows(Visual Studio IDE或Windbg)中,这很容易做到 - 我没有其他系统的信息。

通常,您会以这种方式快速找到导致问题的原因。