这是命名混乱.
由于typedef,node == struct data.
但是node!= struct node.它们是两种不同的类型.并且尚未定义struct节点.
遗憾的是,C标准允许这种令人困惑的结构,甚至没有被编译器标记为警告.但这就是今天定义C的方式,所以我们必须忍受它.
我的建议:不要对struct和typedef使用相同的名称.创建自己的约定,例如struct thing_s和typedef struct thing_s thing_t; .这样可以避免命名这样的混淆.
解决方案现在非常明显.
替换:
typedef struct data {int num; struct node * link; } node;
通过
typedef struct data {int num; struct data * link; } node;
而有问题的printf现在可以工作了.
但你的问题是:为什么你的程序在没有这个printf的情况下完全有效?
现在这是一个有趣的.
让我们回到最初的定义.
正如我们所说,struct node不存在,因此是不完整的.
为了更好地遵循我们要解释的内容,我们将其称为struct incomplete_s.现在,节点变为:
typedef struct data {int num; struct incomplete_s * link; } node;
没有有问题的printf它仍然可以工作.
原因是,节点已正确定义.它是一种已知尺寸和类型的结构.因为struct incomplete_s不完整并不重要,因为链接被定义为指向它的指针.你也可以定义void * link;它仍然有效.
void *或struct incomplete_s *或者其他*具有相同的大小:它们都是指针.因此可以正确地创建托管它们的结构.
在你的主循环中,你的程序会:
while(e!=NULL)
{
printf("%d\n",e->num);
e=e->link;
}
即e,它是一个指针节点*,取e-> link的值,这是一个指针struct incomplete_s *.
请注意,两个指针都应该指向不同的类型.
但它们都是指针,所以是的,这项任务在技术上是可行的并且由标准授权.
现在一个更谨慎的编译器可能会在这里发出警告,因为你不应该混合使用不同类型的指针.这是一个静默类型转换,这是未来错误的一个配方.
我不知道你使用哪个编译器,但是你可以增加它的警告级别(对于Visual使用“警告级别4”,或者对于gcc使用-Wall -Wextra),它可能不会喜欢this = operation.
更明确的类型转换将解决(*):
e =(节点*)(e->链接);
现在这不再是沉默,程序员负责,警告将消失.
e->链接肯定存在,因此编译器可以获取此值.
但是s-> link-> num不存在,因为s-> link是struct incomplete_s *,我们不知道它是什么,所以我们不知道它是否有一个num成员.
(*)过度补充:在更高的优化级别,这仍然不好:strict aliasing可能会妨碍.因此,将指针类型解引用到另一个指针类型仍然是一个危险的操作.