C:没有显式返回构造的结构,但它仍然有效

时间:2009-10-31 23:52:29

标签: c memory-management

我编写了一个'构造函数'函数,它使用Visual Studio 2008,ANSI C模式编译C中的Node。

#include <stdio.h>
#include <stdlib.h>

typedef struct _node
{
  struct _node* next ;
  char* data ;
} Node ;

Node * makeNode()
{
  Node * newNode = (Node*)malloc( sizeof(Node) ) ;

  // uncommenting this causes the program to fail.
  //puts( "I DIDN'T RETURN ANYTHING!!" ) ;
}

int main()
{
  Node * myNode = makeNode() ;
  myNode->data = "Hello there" ;

  // elaborate program, still works

  puts( myNode->data ) ;

  return 0 ;
}

令我惊讶的是:

      
  • *不从makeNode()返回值只是一个警告,
  •   
  • *更令人惊讶的是makeNode()_ _ ttill是有效的_只要我不放()任何东西!

这里发生了什么,这样做是否“好”(不返回你在C'构造函数'函数中创建的对象?)

为什么它仍然有效?为什么puts()命令导致程序失败?

4 个答案:

答案 0 :(得分:6)

不返回任何内容的原因是警告,而不是错误可能主要是历史性的。在'传统'C中,函数不需要声明它们的默认返回类型int。有些函数是在没有显式返回类型的情况下编写的,并且没有返回任何内容,其他函数选择返回有意义的内容,但仍未声明返回类型。试图收紧退货声明或缺乏退货声明意味着打破了许多旧式代码。

它可能会发生作用,但你所看到的是依赖于无法保证的事情。

可能发生的是函数的返回值进入特定寄存器。在你调用malloc之后,如果你什么也没做,并且在函数结束时,malloc返回的内容似乎是由你的函数返回的,因为结果仍然在返回寄存器中那个函数调用。

如果你调用其他函数,malloc的返回值将丢失,函数返回的是返回寄存器中发生的任何事情。

答案 1 :(得分:4)

在您的情况下,几乎总是在x86上,返回值通过寄存器EAX传递。

当你注释掉puts()时,没有代码介入newNode的赋值和 返回你的功能。这会产生伪汇编代码,例如:

push sizeof(Node)
call malloc              ; allocate memory, places return value in eax
move [newNode], eax      ; make use of return value from malloc
ret                      ; return from your function -- 
                         ; eax still contains malloc() return value

因此,eax寄存器不会更改,您的函数似乎会返回指针。

与:比较:

push sizeof(Node)
call malloc              ; allocate memory, places return value in eax
move [newNode], eax      ; make use of return value from malloc
push DWORD PTR ["I DIDN'T RETURN ANYTHING!!"]
call puts                ; during this subroutine, eax will be changed
ret                      ; return from your function -- 
                         ; eax contains garbage value left over from puts()

答案 2 :(得分:2)

它可能与堆栈中的最后一件事有关。如果没有puts(),堆栈中的最后一件事就是您分配的节点,并返回它。对于puts(),堆栈上的最后一件事是puts()的返回值,它是int,并且被返回并用作指针,这可能是坏的。

请注意,无论哪种方式,您的程序都是错误的。这是不确定的行为(或一些令人难以置信的可怕声音标准),不应该依赖。让你的功能正常工作所有时间 - 不要依赖未定义的行为。

如果你想知道这是否属实,你可以这样做:

Node * makeNode()
{
  Node * newNode;

  puts( "I DIDN'T RETURN ANYTHING!!" ) ;

  newNode = (Node*)malloc( sizeof(Node) ) ;
}

可能与没有puts()的那个相同。这也可能有效:

Node * makeNode()
{
  malloc( sizeof(Node) ) ;
}

但你真的不应该使用任何这些。他们在偶然的情况下工作,如果你真的很幸运没有他们会工作。

另请注意,有些人(包括我)认为malloc()的返回值的类型转换是一个坏主意,但这是一个值得商榷的主题。

答案 3 :(得分:1)

我的猜测是makeNode()将分配的对象的地址存储在eax中,这恰好是用于在x86上返回指针的寄存器。

调用puts()可能会修改eax的值。

如果您显示反汇编,我们可以告诉您实际发生了什么。