Какво прави следният ред код с malloc?

Имам следната реализация за отразяване на двоичното дърво.

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

/* A binary tree node has data, pointer to left child
   and a pointer to right child */
struct node
{
    int data;
    struct node* left;
    struct node* right;
};

/* Helper function that allocates a new node with the
   given data and NULL left and right pointers. */
struct node* newNode(int data)

{
  struct node* node = (struct node*)
                       malloc(sizeof(struct node));
  node->data = data;
  node->left = NULL;
  node->right = NULL;

  return(node);
}


/* Change a tree so that the roles of the  left and
    right pointers are swapped at every node.

 So the tree...
       4
      / \
     2   5
    / \
   1   3

 is changed to...
       4
      / \
     5   2
        / \
       3   1
*/
void mirror(struct node* node)
{
  if (node==NULL)
    return; 
  else
  {
    struct node* temp;

    /* do the subtrees */
    mirror(node->left);
    mirror(node->right);

    /* swap the pointers in this node */
    temp        = node->left;
    node->left  = node->right;
    node->right = temp;
  }
}


/* Helper function to test mirror(). Given a binary
   search tree, print out its data elements in
   increasing sorted order.*/
void inOrder(struct node* node)
{
  if (node == NULL)
    return;

  inOrder(node->left);
  printf("%d ", node->data);

  inOrder(node->right);
} 


/* Driver program to test mirror() */
int main()
{
  struct node *root = newNode(1);
  root->left        = newNode(2);
  root->right       = newNode(3);
  root->left->left  = newNode(4);
  root->left->right = newNode(5);

  /* Print inorder traversal of the input tree */
  printf("\n Inorder traversal of the constructed tree is \n");
  inOrder(root);

  /* Convert tree to its mirror */
  mirror(root);

  /* Print inorder traversal of the mirror tree */
  printf("\n Inorder traversal of the mirror tree is \n"); 
  inOrder(root);

  getchar();
  return 0; 
}

Говоря за следния ред:

  struct node* node = (struct node*)
                       malloc(sizeof(struct node));

Имам междинни познания по c/c++, но доста ме е страх от указатели. Дори след няколко опита никога не успях да получа указатели. Избягвам ги, доколкото е възможно, но когато се стигне до внедряване на структури от данни като дървета, няма други опции. Защо използваме malloc и sizeof тук? Също така защо кастинг (struct node*)?


person rishiag    schedule 21.09.2013    source източник
comment
проверете отговора. Ако откриете, че нещо липсва, не се колебайте да попитате.   -  person halkujabra    schedule 21.09.2013


Отговори (6)


Първо, кастингът при използване на malloc в C не е необходим. (вижте тук)

Вие извършвате malloc-ing, защото разпределяте памет на стек с размер на структура на възел. Виждате, че в C трябва да имате предвид къде се съхраняват всички променливи. А именно stack и heap (вижте тук)

Вътре във функция вашите променливи се наричат ​​локални променливи, които се съхраняват в stack. След като напуснете функцията, променливите в стека се изчистват.

За да можете да препращате или използвате локални променливи извън функцията, трябва да разпределите памет в heap, което правите тук. Вие разпределяте памет в купчината, така че да можете да използвате повторно същата променлива и в други функции.

В обобщение:

  • Променливите във функция са локални за функцията, оттук и терминът локална променлива
  • За други функции за достъп до локалната променлива, трябва да разпределите памет в купчината, за да споделите тази променлива с други функции.

За да ви дадем пример защо, разгледайте следния код:

#include <stdio.h>
#include <string.h>

char *some_string_func()
{
    char some_str[13]; /* 12 chars (for "Hello World!") + 1 null '\0' char */

    strcpy(some_str, "Hello World!");

    return some_str;
}

int main()
{
    printf("%s\n", some_string_func());
    return 0;
}

Това е доста просто, main просто извиква функция some_str_func, която връща локална променлива some_str, компилирането на горния код ще работи, но не и без предупреждения:

test.c: In function ‘some_string_func’:
test.c:11:9: warning: function returns address of local variable [enabled by default]

Въпреки че компилира имайте предвид, че some_str в some_str_func() връща локална променлива към функцията (т.е. в стека на функцията). Тъй като стекът се изчиства, след като напуснете функцията some_str_func(), в main() не е възможно да получите съдържанието на some_str, което е „Hello World“.

Ако се опитате да го стартирате, получавате:

$ gcc test.c
$ ./a.out

$

Не отпечатва нищо, защото няма достъп до some_str. За да го коригирате, вместо това отделяте малко място в паметта за низа "Hello World". така:

#include <stdio.h>
#include <string.h>

char *some_string_func()
{
    char *some_str;

    /* allocate 12 chars (for "Hello World!") + 1 null '\0' char */
    some_str = calloc(13, sizeof(char));

    strcpy(some_str, "Hello World!");

    return some_str;
}

int main()
{
    char *str = some_string_func();
    printf("%s\n", str);

    free(str);  /* remember to free the allocated memory */
    return 0;
}

Сега, когато го компилирате и стартирате, получавате:

$ gcc test.c
$ ./a.out
Hello World!
$

Ако ви е трудно да разберете C, знам, че много хора намират „Езикът за програмиране C“ от Brian W. Kernighan и Dennis Ritchie за наистина добра справка, но по-модерна и графична (дори забавна за четене! сериозно) книга е Head First C От Дейвид и Доун Грифитс, те обясняват много важни C концепции като Heap и Stack, разлика между динамични и статични C библиотеки, защо използването на Makefiles е добра идея, как работи Make и много други концепции, които не са обяснени преди в общи книги за C, определено си струва да се разгледат.

Друг добър онлайн ресурс е Zed Shaws Научете C по трудния начин, в които той предоставя добри примери за код и бележки.

person chutsu    schedule 21.09.2013
comment
И как е по-различно от това, когато използваме new в C++ или Java? - person rishiag; 21.09.2013
comment
@user1425223 Резултатът от malloc е указател към неинициализирана памет. за разлика от new. можете да кажете, че new T(args) ~= malloc(sizeof(T)) + T(args). - person Elazar; 21.09.2013
comment
Би било полезно да се спомене, че всичко това за купчината и стека не е нещо, споменато в Стандарта. Например, на някои вградени платформи няма купчина и malloc() simplu връща указател към някаква памет, която не е стека (вижте реализацията на malloc() в AVR-libc например). - person ; 21.09.2013
comment
@H2CO3 Нещо като freestore.Правилен улов - person Deepak Ingole; 21.09.2013
comment
@H2CO3 купчина и стек е общата терминология и също така е точна за повечето реализации и разработчици. Нищо лошо в това. Все едно NULL е адрес 0x0. - person Elazar; 21.09.2013
comment
@Elazar Не съм казал, че не е често срещано. Казах, че не е универсален и не е в стандарта. Официалното описание на изпълнение на C няма понятие за стек или купчина или каквото и да било. Да, това са популярни видове внедряване, но както току-що споменах, има контрапримери. Освен това NULL не е необходимо да бъде адресът 0x0, въпреки че обикновено е такъв. Попитайте някои стари ездачи тук, те ще ви изброят някои от машините, с които са работили, на които NULL не е числова нула. - person ; 21.09.2013
comment
Но новодошлите не трябва да се занимават с подробности за стандартната/официална терминология. (И аз знам за нещото NULL - затова го дадох като пример: То е рядко). - person Elazar; 21.09.2013
comment
@user1425223 имате ли още въпроси? Ако не, моля, помислете дали да приемете моя отговор :) - person chutsu; 22.09.2013
comment
Chutsu Малък проблем, ако използвате strcpy(), не е необходимо изрично да прекъсвате низа, така че премахнете some_str[12] = '\0'; /* remember to null terminate */ от кода. Във всеки случай добър отговор. - person Grijesh Chauhan; 22.09.2013
comment
Актуализира отговора, @GrijeshChauhan благодаря за вниманието :) - person chutsu; 22.09.2013

Прочетете: void *malloc(size_t size);

Функцията malloc() разпределя size байта и връща указател към разпределената памет. Паметта не е инициализирана. Ако размерът е 0, тогава malloc() връща или NULL, или уникална стойност на указател, която по-късно може да бъде успешно предадена на free().

Съответно в

struct node* node = (struct node*)malloc(sizeof(struct node));
  //                                     ^----size---------^

вие разпределяте част от паметта от size = sizeof naode байта и връщане на адрес от malloc, съхранен в nodepointer.

Забележка Имате грешка името на променливата не трябва да бъде node, тъй като е име на структура. Можеш! но не е добра практика. Освен това sizeof(*pointer) се предпочита пред sizeof(Type) в случай, че типът някога бъде променен

Странична бележка: Безопасно е да избягвате да не прехвърляте адрес за връщане чрез функции malloc и calloc. прочетете: Да прехвърлям ли резултата от malloc?

Така че правилната предпочитана форма на горното твърдение е:

struct node* nd = malloc(sizeof *nd);
  //                     ^----size-^

Две поправки: (1) Премахване на typecast и (2) промяна на името на променливата на nd.

person Grijesh Chauhan    schedule 21.09.2013
comment
Имате грешка името на променливата не може да бъде възел, тъй като е име на структура. - той може. Не е добра практика обаче. Освен това sizeof(*pointer) се предпочита пред sizeof(Type) в случай, че типът някога бъде променен. - person ; 21.09.2013
comment
@H2CO3 така ли е?? ... интересно. Пак копирам от теб :) Благодаря! - person Grijesh Chauhan; 21.09.2013
comment
@H2CO3 Благодаря ти много H2co3 човече. След това не трябва да е правилно в C++, защото в C++ можем да използваме име на структура без ключова дума struct. Прав ли съм? - person Grijesh Chauhan; 21.09.2013
comment
@H2CO3 Разбрах в C++ пространството от имена на клас и пространството от имена на променливи са различни. Страхотен човек! - person Grijesh Chauhan; 21.09.2013
comment
@Elazar съжалявам, че не опитах, научих от тук моля, проверете и предложете корекция. - person Grijesh Chauhan; 21.09.2013
comment
@GrijeshChauhan: В C++ няма имплицитно преобразуване от void* в object_type*, така че ще е необходимо преобразуване. Но рядко е добра идея да се извиква malloc в C++; или използвайте new или някакъв клас контейнер, който управлява разпределението вместо вас. - person Keith Thompson; 22.09.2013
comment
@KeithThompson Да, това го разбрах. Объркването ми е в този работен код, тъй като в C++ не е необходимо да използваме структура след декларацията, така че да предположим, че аз имам същото struct ABC {}; тогава мога просто да декларирам ABC a;, че няма нужда да използвам struct (но използвам в C) Сега как ABC ABC; става правилен C++ използва ли различни пространства от имена за променливи и типове ? Знам, че много функции с еднакви имена са възможно (претоварване) с различни компилатори на сигнатури да променят името си. - person Grijesh Chauhan; 22.09.2013

Използване на sizeof-

sizeof(T) ще каже броя на байтовете, необходими за съхраняване на променлива от тип T

Използване на malloc-

Malloc разпределя паметта динамично, тоест по време на изпълнение (когато вашата програма действително се изпълнява от CPU и е в паметта). Използваме това най-вече когато не сме сигурни за количеството памет, което се изисква по време на изпълнение. Така че ние динамично го разпределяме по време на изпълнение, използвайки malloc.

Използване на (struct node*)-

Malloc връща указател към блок памет с количеството пространство, което сте поискали (в неговите параметри). Това пространство е просто място в паметта. Следователно този указател няма тип, свързан с него. Прехвърляме този указател към (struct node*), защото той ще уведоми машината, че променливите от тип (struct node) ще бъдат записани в тази памет.

person halkujabra    schedule 21.09.2013

void* malloc (size_t size);

Разпределяне на блок памет

Заделя блок с размер байтове памет, връщайки указател към началото на блока. Съдържанието на новоразпределения блок памет не се инициализира и остава с неопределени стойности. Ако размерът е нула, върнатата стойност зависи от конкретната реализация на библиотеката (може или не може да бъде нулев указател), но върнатият указател не трябва да се дереферира.

И не предавайте резултата от malloc.

Трябва също да освободите тази памет:

void free (void* ptr);

Освободете блок памет

Блок памет, предварително разпределен чрез извикване на malloc, calloc или realloc, се освобождава, което го прави отново достъпен за по-нататъшно разпределение.

person pzaenger    schedule 21.09.2013

използвате malloc, за да позволите на указателите да имат към какво да сочат.

указателят е точно като уличен адрес и сградата, която стои на адреса, е построена от malloc -- или поне размера, необходим за изграждане на сградата -- какво ще построите там зависи от вас.

във вашия пример всеки възел в дървото е брой байтове, разпределени с malloc, размерът на възела е броят байтове, необходими за задържане на цялото съдържание на възела.

двоичното дърво ще получи всеки от неговите възли, разпределен с malloc, където в паметта е без значение и това може би е нещото, което е малко трудно да се разбере с malloc и указатели. докато има указатели към тези места, всичко е наред.

person AndersK    schedule 21.09.2013

struct node* node = (struct node*)malloc(sizeof(struct node)); 

отделя достатъчно място за съхранение на node структура

person Farouq Jouti    schedule 21.09.2013