C: не возвращает явно созданную структуру, но она все еще работает - PullRequest
0 голосов
/ 01 ноября 2009

Я пишу функцию конструктора, которая создает узел в C, скомпилированный с Visual Studio 2008, режим ANSI C.

#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 () __ все еще работает__, пока я ничего не помещаю ()!

Что здесь происходит и нормально ли это делать (не возвращать объект, который вы создаете в функции C 'constructor'?)

ПОЧЕМУ это все еще работает? Почему команда put () вызывает сбой программы?

Ответы [ 4 ]

6 голосов
/ 01 ноября 2009

Причина, по которой ничего не возвращается, является предупреждением, а не ошибкой, вероятно, в значительной степени историческая. В «традиционном» C функциям не нужно было объявлять свой тип возвращаемого значения, значение которого по умолчанию int. Некоторые функции были написаны без явного возвращаемого типа и ничего не возвращали, другие решили возвращать что-то значимое, но по-прежнему не объявляли возвращаемый тип. Попытка ужесточить операторы возврата или их отсутствие означало бы сломать много кода старого стиля.

Это может сработать, но то, что вы видите, зависит от того, что не гарантировано.

Вероятно, происходит то, что возвращаемое значение функции попадает в определенный регистр. После того, как вы вызовете malloc, если вы ничего не сделаете, и падение функции завершится, то, что возвращает malloc, похоже, будет возвращено вашей функцией, так как результат все еще находится в регистре возврата после вызова этой функции.

Если вы вызываете какую-то другую функцию, возвращаемое значение malloc теряется, и ваша функция возвращает то, что в итоге оказалось в регистре возврата.

4 голосов
/ 01 ноября 2009

В вашем случае, и почти всегда на x86, возвращаемое значение передается через регистр EAX.

Когда вы комментируете put (), между присваиванием 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 голосов
/ 01 ноября 2009

Вероятно, это связано с последней вещью в стеке. Без 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(), но это спорный вопрос.

1 голос
/ 01 ноября 2009

Я предполагаю, что makeNode() сохраняет адрес выделенного объекта в eax, который является регистром, используемым для возврата указателей на x86.

И вызов puts(), вероятно, изменяет значение eax.

Если вы покажете разборку, мы расскажем вам, что на самом деле происходит.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...