2

This is my code:

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

void getinfo(unsigned int a, unsigned int b, char **pStr);

int main(){
    unsigned int len_max = 8;
    unsigned int current_size = 0;
    current_size = len_max;
    char *host, *user;
    char *pStr = malloc(len_max);
    if(pStr == NULL){
        perror("\nMemory allocation\n");
        return EXIT_FAILURE;
    }
    printf("Inserisci hostname: ");
    getinfo(len_max, current_size, &pStr);
    if((host=malloc(strlen(pStr)+1 * sizeof(char))) == NULL) abort();
    strncpy(host, pStr, strlen(pStr)+1);
    printf("Inserisci username: ");
    getinfo(len_max, current_size, &pStr);
    if((user=malloc(strlen(pStr)+1 * sizeof(char))) == NULL) abort();
    strncpy(user, pStr, strlen(pStr)+1);
    printf("\nHostname: %s\nUsername: %s\n", host, user);
    free(pStr);
    free(host);
    free(user);
    return EXIT_SUCCESS;
}

void getinfo(unsigned int a, unsigned int b, char **pStr){
    unsigned int i = 0;
    int c = EOF;
    while((c = getchar()) != '\n'){
        (*pStr)[i++] = (char)c;
        if(i == b){
            b = i+a;
            if((*pStr = realloc(*pStr, b)) == NULL){
                perror("\nMemory allocation error\n");
                exit(EXIT_FAILURE);
            }
        }
    }
    (*pStr)[i]='\0';
}

The problem is that if realloc fails i have to exit (because i cannot allocate memory). But before exit there's to free all the used pointers.
The problem is that if the function fails the first time there's only 1 pointer that have to be freed (pStr).
But if it fails the second time there's 2 pointers that have to be freed (pstr & user).
How can i fix it?

polslinux
  • 1,739
  • 9
  • 34
  • 73
  • 2
    You dont have to free memory before exit, the operating system will do it for you – thumbmunkeys May 05 '12 at 17:17
  • I'd already thought about this in your previous question. Your code is just fine in this regard since the OS will simply reclaim the memory. – David Heffernan May 05 '12 at 17:21
  • 1
    It is considered a good practice to `free` before exiting, although most the new OSes will do it for your. There is [this](http://stackoverflow.com/questions/2213627/when-you-exit-a-c-application-is-the-malloc-ed-memory-automatically-freed) related question – another.anon.coward May 05 '12 at 17:25
  • Why not change the function to return in value to indicate if there are any failures in the function? Change the return type to say `int` & return 0 on successful completion of the function else return say 1. Instead of exiting from the function return success/failure. Now in the code, check the return value of the function, if it is not 0, then free the resources appropriatelty – another.anon.coward May 05 '12 at 17:30

5 Answers5

3

As already noted, if you are going to exit, then all practical modern O/S will release the allocated memory before exit. It was not always thus; early versions of AmigaDOS, IIRC, did not reclaim allocated memory automatically until reboot.

This is a simple case. There are more complex cases, such as you are parsing a file into memory and the 579th memory allocation fails, and you'd like to release the previous 578 memory allocations so that the user can try again.

In such cases, you have to keep a record of each memory allocation that is relevant (which may itself require some memory allocation — though if you're parsing a file, you probably have a master structure which contains the complete description) and then release all the allocated data.

In your example, if this was not a main() function and if you did not abort on memory allocation error, then you would need to ensure that the three allocated pointers are released on exit from the function. The standard tricks for that include:

  1. Initialize the pointers to 0 so they can be freed reliably:

    char *host = 0;
    char *user = 0;
    
  2. When using realloc(), do not assign the result to the expression passed as the first parameter:

    Do NOT do:

    ptr = realloc(ptr, newsize);
    

    If (when) ptr is the only reference to the allocated memory and the reallocation fails, you've just leaked memory; there is no way to release the memory that is still allocated to you.

    Use:

    void *newptr = realloc(oldptr, newsize);
    if (newptr == 0)
        ...recover from memory allocation failure
    oldptr = newptr;
    

    The trouble with the simpler version is that you've just thrown away the only reference to the allocated memory. (Note that your code falls into the dangerous/leaking pattern).

  3. Note that pretty much every function that acquires resources must either release the acquired resource before returning, or make the resource available to some other part of the program so that the other part can release the resource when it is done with it.

    The 'make available' operation might be returning the acquired resource (think of it as memory, but it could be a file descriptor, directory stream, or any of a large number of other allocated resources) to the calling function, or storing it in a structure that was passed to the current function, or copying it to a global or (file) static variable, or even stashing it in a (function) static variable so if the function is called again, it has some resource available on entry.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • Wow! :O thanks a lot for your answer! So i've to change my code because at this point it this in "dangerous state" right? – polslinux May 05 '12 at 21:40
  • Given that your code is currently exiting immediately on the `realloc()` failure, it is not crucial that you change to avoid the `ptr = realloc(ptr, newsize);` anti-pattern. However, if you were going to try and recover from the error, then it would be crucial to avoid the anti-pattern. You need to be aware of the issue. On the whole, making the change is for the better. But if you continue to exit after failure, it is not crucial. – Jonathan Leffler May 05 '12 at 21:44
  • Mmm ok i have undestand this part but...what i have to do to "recover from memory allocation failure"?? What do you mean? – polslinux May 05 '12 at 21:49
  • In this simple program, there isn't much you can do if you get a memory allocation failure; exit on error is a perfectly reasonable strategy. However, I mentioned in the answer an example where you might well need a better strategy (the 'reading a file and the 579th allocation fails' part). For example, your program may have been invoked to convert 20 files, and the third file happens to be too big to convert; should you abandon its conversion with an error and avoid converting any of the others, or should you release the memory used for the third file and go on to process the rest? (Cont'd) – Jonathan Leffler May 05 '12 at 21:59
  • (Cont'n) Both answers are possible. On the whole, releasing the memory and continuing with the next file is better, but it is not a clear-cut decision. Many of my programs use the 'abandon ship' strategy (via a set of routines `emalloc()`, `erealloc()`, `ecalloc()` and `efree()` which never return a null pointer); they avoid doing recovery on allocation failure. But they don't leak like a sieve, either; at the end of processing each file, the memory allocated for processing that file is all freed and available for processing the next file too. It is a judgement call, and not an easy one. – Jonathan Leffler May 05 '12 at 22:03
3

As a few people have pointed out, modern OS's reclaim memory on exit. However, it is considered a best practice to free your resources anyway, as this makes debugging easier. For example, if you are trying to find a leak and you use a tool like valgrind, all the memory you don't properly free (even if by the program logic, this doesn't matter) will appear as leaks. There are some large API's around that notoriously don't do this, and they make tracking leaks in applications which use them a nightmare.

Also, in some specialized environments it might be important to clean up after yourself. Therefore, it's a good habit to get into now.

A clean-up technique you'll see occasionally (eg, in the linux kernel) is something I think of as the "bail and release" pattern. It's one of the few (perhaps: only) contexts where goto is still considered acceptable. It depends upon you being able to free your resources in the opposite order you allocated them. Usually this is in the context of a single function, in this case main():

#include <stdlib.h>

int main(void) {
    int exit_status = EXIT_FAILURE;

    char *s1, *s2, *s3;

    s1 = malloc(100);
    if (!s1) return EXIT_FAILURE;

    s2 = malloc(100);
    if (!s2) goto endB;

    s3 = malloc(100);
    if (!s3) goto endA;

    exit_status = EXIT_SUCCESS;

    /* do whatever */

    free(s3);
endA:
    free(s2);
endB:
    free(s1);
    return exit_status;
}            

To explain: if allocating s1 fails, we just return -- there is nothing to clean-up. But if allocating s2 fails, we goto endB, freeing s1. If allocating s3 fails, we goto endA, which will free s2 and s1. Finally, if everything succeeded, we do whatever, and afterward, all three pointers will be freed. If this were a normal function, we might be returning a pointer, so there would be a separate return for that before the "end" bits, which would complete with "return null" instead.

Nb: please don't take this as a licence to make free-wheeling use of goto!

CodeClown42
  • 11,194
  • 1
  • 32
  • 67
  • 2
    Nothing wrong with `goto` -- you could argue that it's *necessary* to write correct C in general! :-) In some way, C++ is just C with automagic `goto`s, called "destructor". – Kerrek SB May 05 '12 at 17:43
3

This is more of a general C language advice than a specific answer, but it's too long to go in comment.

The usual way to write C in the presence of dynamic resource management is to goto suitable labels which are followed by the relevant deallocation calls:

int f(int n)
{
    void * p1, * p2, * p3;
    int result = -1;

    p1 = malloc(n);

    if (!p1) goto END0;
    if (n % 2) goto END1;

    p2 = malloc(3 * n);

    if (!p2) goto END1;
    if (n % 7 == 0) goto END2;

    p3 = malloc(7 * n + 8);

    if (!p3) goto END2;

    if (do_useful_stuff(p1, p2, p3) == -1) goto END3;
    result = compute_more_stuff(p1, p2, p3);

END3:
    free(p3);
END2:
    free(p2);
END1:
    free(p1);
END0:
    return result;
}

The alternative is to split up your code into very small functions that do only one thing at a time and handle resource allocation in an ad-hoc fashion:

int small_function(void ** p)
{
    void * q = malloc(13);

    if (some_init(&q) == -1)
    {
        free(q);    // ad-hoc exit management
        return -1;
    }

    int n = complex_computation(q);
    free(q);
    return n;
}
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • Added a minor typo correction. Is there is a reason for `n % 2` etc or is it just a sample? – another.anon.coward May 05 '12 at 17:42
  • @another.anon.coward: Just a silly example of some contrived control flow! The point is that the function may exit in multiple ways depending on runtime data. (I had an error in the gotos, now fixed.) – Kerrek SB May 05 '12 at 17:44
3

Yeah, you don't have to free anything if this is the whole program.

The whole reason to free memory is so it can be reused somewhere later in the program. Even if your program went on from this point, you've only allocated a small number of bytes. It's OK to allocate them and keep them forever.

In fact, you don't even have to do the arithmetic you're doing there to carve out exact-sized mallocs, you could say Oh, usernames and hostnames are never more than like 30 chars, just to be sure I'll allocate 256 char blocks. Oh wait your max is 8 chars, whatever. Or even just make a global buffer 256 chars or 8 chars long. Then make sure your strncpy()s never go past len_max or else you're risking a buffer overflow hack.

meanwhile that getinfo() looks painful. Try something like fgets(mybuffer, len_max, stdin).

Last I checked, the executable doesn't even bother to 'free' all unfreed blocks at the end, it just walks away. The VM system returns all the used memory (including the stack and program code) to the OS, and the process vaporizes and it's over. The malloc()ed blocks are just a pattern of bytes on that memory, and it's all forgotten.

OsamaBinLogin
  • 609
  • 6
  • 10
  • Yes, you're right. In fact I had initially thought that this was not necessary but I wanted to learn to use realloc so I preferred to do so :) – polslinux May 05 '12 at 21:42
2

You don't have to free dynamically allocated memory before exiting. The OS will make all that memory available to the next process.

Jens
  • 69,818
  • 15
  • 125
  • 179