我正在使用这个特殊代码来创建二叉树,代码本身也在工作,但我似乎缺乏理解代码的关键概念。
节点结构
typedef struct node{
int data;
struct node *left,*right;
}* Node;
创建功能
Node create(){
Node root = (Node) malloc(sizeof(struct node));
int x;
scanf("%d",&x);
if(x==-1) //create a null node
return NULL;
root->data=x;
root->left = create();
root->right = create();
}
该功能的调用是
Node root=create();
在create函数中没有指向传递的根节点的指针 在该函数中,正在创建新节点,分配了分配的内存和节点的值。但代码中没有指向传递或返回新创建的节点的指针。
那么这段代码是如何工作的?
PS:返回类型是Node。我认为这是相关的,但我不知道为什么会如此。它可能也是无效的,因为没有返回任何值。
答案 0 :(得分:4)
好的,首先,这个功能有一个明确而直截了当的错误......
Node create(){
Node root = (Node) malloc(sizeof(struct node));
int x;
scanf("%d",&x);
if(x==-1) //create a null node
return NULL;
root->data=x;
root->left = create();
root->right = create();
}
编译器应该指出的:
test.c: In function ‘create’:
test.c:18:1: warning: control reaches end of non-void function [-Wreturn-type]
(如果您的编译器在没有任何投诉的情况下使用此代码,请打开警告。默认情况下,GCC特别过于宽松;如果您使用的是GCC,则应该基本上给它-Wall
选项总是,可能还有更多-Wsomething
选项。)
错误只是缺少最后一行:
return root;
现在,您的实际问题似乎是“我知道有一个错误,但为什么它仍然有用?”在我开始之前,我需要您承认您明白这一点,如果它工作,它只能偶然,在计算机和您正好使用的编译器上工作。 create
具有我们所谓的“未定义行为”,这意味着它允许任何,包括看似正常工作,但也有灾难性的失败。
无论如何 是一个合理的解释:在许多CPU上,用于返回指针的寄存器也是一个方便的临时寄存器,因此在编译语句时
root->right = create();
编译器可以将root
放在返回值寄存器中,以便执行内存访问root->right = ...
。由于在该点之后没有操作,所生成的机器代码“不再结束”,而没有在该寄存器中正式放置返回值,但也没有从删除本地变量root
注册。它似乎有效。
顺便提一下,此功能中存在更多错误:
Node create(){
这有一个主要是无害的语义错误和一个样式错误;它应该写成
Node create(void)
{
在C中,由于历史原因,您必须编写(void)
而不是()
来定义不带参数的函数。这在技术上定义了一个带有未指定数字参数的函数,这意味着如果你用参数调用 create
,编译器就不会抱怨。
在C语言中,与您可能熟悉的其他一些语言不同,函数定义的开头大括号应该始终放在它自己的行上。
Node root = (Node) malloc(sizeof(struct node));
在C语言中(与C ++不同),不转换malloc
的返回值。没有必要,如果不这样做,编译器会在您忘记包含stdlib.h
时提醒您,因此您的指针被截断为int
的宽度。
int x;
scanf("%d",&x);
Never use scanf
in production code。此外,您没有检查输入错误。
if(x==-1) //create a null node
return NULL;
当x等于-1时,这会泄漏刚刚分配的节点。在调用malloc
之前,应该进行此检查。
此外,您的缩进不一致:第一级使用4空格缩进,第二级使用2空格缩进。使用什么缩进样式并不重要,但确实非常重要的是你选择一种风格并在整个程序中坚持。
(并且不要使用制表符,因为使用制表符缩进的程序对于每个人看起来都不一样.8空格缩进很好,但它们实际上应该是8个空格。)
答案 1 :(得分:1)
这里的问题是你在create
末尾省略了return语句。 C编译器在这方面令人惊讶地容易接受并且通常会编译得很好(尽管有警告。你做警告启用了不是你!?)。
很可能函数在寄存器或某个堆栈框架中返回一些任意值,幸运的是root
的地址,所以一切都按预期工作(参见Function with missing return value, behavior at runtime)。
如果在函数末尾添加return root;
,它应该像以前一样继续工作,减去未定义的行为。
答案 2 :(得分:0)
在返回指向动态分配内存的指针之前,不使用create()函数。
答案 3 :(得分:0)
恭喜,您发现内存泄漏。该函数是在堆上分配资源,但它不会将控制权传递给您或释放它。