关于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
答案 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
,同时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
可能会更容易。
答案 1 :(得分:2)
尾部有列表的内存地址的原因在这一行
tail =(Node *) &list;
表示将list
指向的指针的地址指定给指针变量tail
。
由于tail
和list
都指向同一地址,因此这是设置链表数据结构的基础知识。
修改强>
说到<{>>}, 引用 罢工>修改此项考虑到OP的代码发布,遗漏了Node
....
答案 2 :(得分:2)
以下行错误:
tail =(Node *) &list;
您获取变量列表的地址,该列表实际上是Node **
类型。
然后你把它投到Node *
。虽然您可以在C / C ++中执行此操作,但这可能不是您想要的。
要获得所需行为,tail应为Node **
类型。因此不再需要强制转换,最后,您需要编写(*tail)->next = node
。
答案 3 :(得分:2)
该行
tail =(Node *) &list;
将 list
的地址<{1>}分配给tail
。由于&list
是Node **
,因此默认情况下编译器不喜欢此赋值,因此您添加显式强制转换以使其静音。然后
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
,您可以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
的地址。
请原谅上帝永远不要在生产代码中这样做。
答案 6 :(得分:0)
不是这条线......
tail =(Node *) &list;
指定尾部指向列表的指针的地址,而不是列表的地址?
答案 7 :(得分:0)
如果内存意外更改,追踪问题的最快方法是根据内存更改配置断点,包括感兴趣的内存块的大小 - 在这种情况下为4,假设它是一个32位平台指针。在Windows(Visual Studio IDE或Windbg)中,这很容易做到 - 我没有其他系统的信息。
通常,您会以这种方式快速找到导致问题的原因。