1

I have problems with memory deallocation in C. Without the division of functions everything is OK, but unfortunately it does not work on the same functions. Here is the code:

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

typedef struct {
    char *name;
    enum {
        summer,
        winter
    } index;
} student;

bool init(student *s) {
    printf("The next stage in the allocation of memory\n");
    s = (student*)malloc(sizeof(*s));
    if (&s == NULL) {
        printf("Allocation Failed\n");
        return 0;
    } else {
        printf("Allocation completed successfully\n");
    }
}   

void delete(student *s) {
    if (&s != NULL) {
        printf("begin removal\n");
        free(s);
        printf("Released memory");
    }
}

int main() {
    student *s;
    init(s);
    delete(s);

    return 0;
}

I do not know what I'm doing wrong. Please help.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • 4
    What do you mean by, "does not work"? What behavior are you seeing, and what do you expect to happen? – Fred Larson Dec 14 '16 at 20:17
  • 6
    `if (&s!=NULL){` Why do you care about *the address* of `s`? – EOF Dec 14 '16 at 20:17
  • 1
    Probably not a good idea to call a function `delete` - as it is a keyword in C++ and may lead to confusion – Ed Heal Dec 14 '16 at 20:18
  • `student *s; init(s);` uses the value of an uninitialized variable (`s`), which has undefined behavior. – melpomene Dec 14 '16 at 20:19
  • [please read this about casting malloc](http://stackoverflow.com/questions/605845/do-i-cast-the-result-of-malloc) – Ed Heal Dec 14 '16 at 20:19
  • 1
    C passes arguments by value. `f(x)` can't change `x`. – melpomene Dec 14 '16 at 20:19
  • I'm starting to just play with the programming. Still I do not know the function – user7298655 Dec 14 '16 at 20:20
  • 2
    `free()` is happy to take a null pointer and does nothing. The address of `s` will never be a null pointer; the `if` line is useless (and the compiler I use, GCC, would warn about it because of the compilation options I use — if yours doesn't, you aren't getting full value from your compiler, or you need a better compiler). – Jonathan Leffler Dec 14 '16 at 20:21
  • 4
    Your problem can be reduced to: `void foo(int s) { s = 42; } int main(void) { int s = 0; foo(s); printf("%d\n", s); return 0; }`. – melpomene Dec 14 '16 at 20:21
  • @melpomene: I think you put your finger on it. `s` in `main()` is never changed. – Fred Larson Dec 14 '16 at 20:21
  • @user7298655 - Please start reading the manual pages. – Ed Heal Dec 14 '16 at 20:22
  • 1
    @melpomene: I'm pretty sure it's not undefined behavior to pass an uninitialized variable by value into a function. It's not like `init` does anything with the value passed in anyways. –  Dec 14 '16 at 20:22
  • @Hurkyl "*The behavior is undefined in the following circumstances: [...] - The value of an object with automatic storage duration is used while it is indeterminate (6.2.4, 6.7.8, 6.8).*" – melpomene Dec 14 '16 at 20:25
  • 1
    There are various errors in your code which result from basic misscinception. Please (re-)read the section about pointers in your favourite C book. Learning C by trial&error, questionalbel online tutorials (including youtube videos) is a really bad idea. – too honest for this site Dec 14 '16 at 20:36

3 Answers3

1

First of all the function init has undefined bbehaviour because it returns nothing in the case of successful memory allocation.

You can check whether the memory was allocated or not by returning pointer to the allocated memory or NULL.

Also this statement

if(&s==NULL){

is wrong. The condition will always yield false because the address of the local variable s is not equal to NULL.

So the function can be rewritten either the following way

student * init()
{
    printf("The next stage in the allocation of memory\n");

    student *s = ( student* )malloc( sizeof( *s ) );

    if ( s == NULL )
    {
        printf("Allocation Failed\n");
    } 
    else 
    {
        printf("Allocation completed successfully\n");
    }

    return s;
} 

And called like

int main( void )
          ^^^^^
{
    student *s = init();
    //...

Or it can be defined the following way

int init( student **s )
{
    printf("The next stage in the allocation of memory\n");

    *s = ( student* )malloc( sizeof( **s ) );

    int success = *s != NULL;

    if ( !success )
    {
        printf("Allocation Failed\n");
    } 
    else 
    {
        printf("Allocation completed successfully\n");
    }

    return success;
} 

and called like

int main( void )
          ^^^^^
{
    student *s;
    init( &s );
    //...

The function delete should be defined at least like

void delete(student *s) {
    if (s != NULL) {
       ^^^
        printf("begin removal\n");
        free(s);
        printf("Released memory");
    }
}
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
0

Firstly, free is NULL safe. If variable is NULL already, basically nothing happens. You do not have to check if it is NULL. (you can check page 313 ISO-IEC 9899 )

Also, when you initialize student->name and allocate, there will be memory leak. You have to free that too.

So, your delete function could be like this ;

  void delete(student *s) {
    printf("begin removal\n");
    free(s->name);
    free(s);
    printf("Released memory");
}

And if (&s == NULL) is wrong. They must be changed with if (s == NULL).

Your allocation may cause really big troubles in the big codes. If you allocate s = (student*)malloc(sizeof(*s)); it means that "allocate s with size of *s". But pointer size is fixed memory block (mostly 8 bytes). It means that you blocks certain size of memory. If you have bigger struct than that, this kind of allocation will corrupt the memory and your executable will be killed by the OS(you can try add some more variables to your struct and initialize them). In small structs and very short runtimes, mostly this allocation works too. But i guarantee that this is not safe for run-time. And it will not give any warnings or errors in compile-time. True way is s = malloc(sizeof(student)). With this way, you exactly allocates all the memory blocks. And your memory stay safe in run-time.

Lastly, your init function should return the initialized variable. And your init function could be like this ;

#define NAME_LENGHT 128

...

student * init(student *s) {
   printf("The next stage in the allocation of memory\n");
   s = malloc(sizeof(student));
   if (s == NULL) {
       printf("Allocation Failed\n");
       return NULL;
   }

   s->name = malloc(NAME_LENGHT); 
   if (s->name == NULL) {
       printf("Allocation Failed\n");
       return NULL;
   } else {
       printf("Allocation completed successfully\n");
   }
   //alternatively you can strdup directly without any allocation
   // s->name = strdup("some name");
   return s; 
}   
Hakkı I.
  • 11
  • 5
-3

You never change the s in main.

Solution 1: Pass a pointer to the variable in main for init to populate.

void init_student(student** s_ptr) {
    *s_ptr = (student*)malloc(sizeof(student));
    if (*s_ptr == NULL) {
        fprintf(stderr "panic: Allocation Failed\n");
        exit(1);
    }

    (*s_ptr)->name = malloc(MAX_NAME_SIZE + 1);
    if ((*s_ptr)->name == NULL) {
        fprintf(stderr "panic: Allocation Failed\n");
        exit(1);
    }

    (*s_ptr)->name[0] = 0;

    (*s_ptr)->gpa = 0;
}   

void delete_student(student* s) {
    free(s->name);
    free(s);
}

int main() {
    student* s;
    init_student(&s);
    delete_student(s);
    return 0;
}

Solution 1b: Same, but a cleaner implementation.

void init_student(student** s_ptr) {
    student* s = (student*)malloc(sizeof(student));
    if (*s_ptr == NULL) {
        fprintf(stderr "panic: Allocation Failed\n");
        exit(1);
    }

    s->name = malloc(MAX_NAME_SIZE + 1);
    if (s->name == NULL) {
        fprintf(stderr "panic: Allocation Failed\n");
        exit(1);
    }

    s->name[0] = 0;

    s->gpa = 0;

    *s_ptr = s;
}   

void delete_student(student* s) {
    free(s->name);
    free(s);
}

int main() {
    student* s;
    init_student(&s);
    delete_student(s);
    return 0;
}

Solution 2: Return the allocated point to main.

student* init_student() {
    student* s = (student*)malloc(sizeof(student));
    if (s == NULL) {
        fprintf(stderr "panic: Allocation Failed\n");
        exit(1);
    }

    s->name = malloc(MAX_NAME_SIZE + 1);
    if (s->name == NULL) {
        fprintf(stderr "panic: Allocation Failed\n");
        exit(1);
    }

    s->name[0] = 0;

    s->gpa = 0;

    return s;
}   

void delete_student(student* s) {
    free(s->name);
    free(s);
}

int main() {
    student* s = init_student();
    delete_student(s);
    return 0;
}

Note that &s == NULL will never be true because &s is the address of the variable itself. You want just s == NULL to check the value of the pointer in s.

ikegami
  • 367,544
  • 15
  • 269
  • 518
  • 4
    `malloc(sizeof *s)` was fine, and is better style than repeating the type. – Quentin Dec 14 '16 at 20:25
  • @Quentin, Wasn't sure if you could do that in the same statement in which `s` was declared. And there's no repeating of the type since the cast isn't needed. – ikegami Dec 14 '16 at 20:27
  • 3
    @ikegami `student *s = ... sizeof (student)` contains "student" twice. – melpomene Dec 14 '16 at 20:27
  • @melpomene, if you change it to `*s`, it would contain `*s` twice, so no better. That particular code duplication is unavoidable. – ikegami Dec 14 '16 at 20:28
  • 2
    @ikegami Eh? The point is not to avoid all repetition; the point is to avoid hard-to-diagnose bugs if you ever change the type of the pointer but forget to update the `sizeof` part. `sizeof *s` is always correct, no matter how `s` is declared. – melpomene Dec 14 '16 at 20:30
  • 1
    Forgetting to change the variable name in one place just gives you a hard error ("undeclared identifier" or something), not a silently wrong allocation. – melpomene Dec 14 '16 at 20:32
  • Same with changing the type name! Are you saying you have a problem's with C++'s `new Student` and Perl's `Student->new`. Nonsense! – ikegami Dec 14 '16 at 20:33
  • I was talking about changing the type of `s`, not renaming the struct. – melpomene Dec 14 '16 at 20:33
  • What do you mean, change the type of `s`? You're allocating a student. If you change the type of `s`, that would be an error! – ikegami Dec 14 '16 at 20:35
  • 1
    I don't understand. Consider: `typedef struct { ... } teacher; ... teacher *s; ... s = malloc(sizeof(student));`. Now you've allocated a block of size A but you're using it as if it were size B. To track this down, you have to check every call to `malloc` and make sure that `sizeof(TYPE)` matches the declaration of the pointer you're assigning to. On the other hand, `s = malloc(sizeof *s);` is correct and you don't have to look at any other lines to very this. – melpomene Dec 14 '16 at 20:40
  • `s = malloc(sizeof *s);` is NOT correct. It would not allocate the right amount. It would allocate `sizeof(teacher)` instead of `sizeof(student)` as desired. I know it's buggy, but the bug is that `teacher *s` should be `student *s`. – ikegami Dec 14 '16 at 20:41
  • Not necessarily. Maybe the code was refactored, or maybe it was copy/pasted because the code for teachers started out very similar to students. `teacher` may well be the right type in this particular case. Or think of numeric code: Maybe initially it was `float *v; ... v = malloc(n * sizeof (float));` but then it was changed to `double *v;` and nobody noticed the `sizeof (float)` hidden in there. Bam, a memory corruption bug. `n * sizeof *v` is both more robust and easier to verify correct. – melpomene Dec 14 '16 at 20:46
  • I'm not talking about some other function. I'm talking about this function whose sole purpose is to initialize a student. If you create a function to initialize a teacher then you'll need to write code to allocate a teacher. // I'm also not talking about primitive types. This function deals specifically with a semantic type. I would indeed use `n * sizeof *v` in that case because it wouldn't be in a function to initialize a float; it would be in a function that initializes something that happens to be float. – ikegami Dec 14 '16 at 20:48
  • 1
    @ikegami Seriously? You don't understand why `malloc(sizeof thing)` is more error resistant and therefore better practice than `malloc(sizeof type-of-thing)`? – Carey Gregory Dec 14 '16 at 20:56
  • More error prone, as it might not allocate a student. Why would you risk having it allocate something else?! I don't know about you, but I use variables needed by the code; I don't declare arbitrary variables first then hope their the right ones for my code. That sounds like a very brain dead way of doing things to me. Again, the point of this function is to allocate and initialize a student. The next line will be something like `s->gpa = 0;`. You can't just change the type of `s`! – ikegami Dec 14 '16 at 21:02
  • Furthermore, if you want your code to also compile as C++, then you'd need `(student*)malloc(sizeof(student))`, which is way safer than `(student*)malloc(sizeof(*s))` because the former would look obviously wrong if there was a type mismatch. So I'll meet you guys in the middle an introduce a compile-time type check by using a cast. – ikegami Dec 14 '16 at 21:15