如果没有返回分配的指针,这段代码如何工作?

时间:2017-09-15 19:44:39

标签: c return binary-tree return-value

我正在使用这个特殊代码来创建二叉树,代码本身也在工作,但我似乎缺乏理解代码的关键概念。

节点结构

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。我认为这是相关的,但我不知道为什么会如此。它可能也是无效的,因为没有返回任何值。

4 个答案:

答案 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)

恭喜,您发现内存泄漏。该函数是在堆上分配资源,但它不会将控制权传递给您或释放它。