Lab 10




As with previous labs, set up an appropriate directory. Get feedback on your work from the TA (only) during the lab. There is no submission required with this week's lab

In this lab you will start investigating how to use valgrind.

From the valgrind website:
Valgrind is an instrumentation framework for building dynamic analysis tools. There are Valgrind tools that can automatically detect many memory management and threading bugs, and profile your programs in detail. You can also use Valgrind to build new tools.

We shall be concerned only with the basics of memory management.

For information on valgrind, try

Exercises

    Running Valgrind

  1. For a first look at valgrind, just write and run the helloWorld program. When you compile it to an executable called hello and then run it, here's what you get:
    $
    $ hello
    Hello World!
    $
    
    When you run this using valgrind, you give exactly the same command - with the same commandline arguments, if any - but preceded by "valgrind" - and any arguments that it takes.

    That will give you something like this:
    $
    $ valgrind hello
    ==14222== Memcheck, a memory error detector
    ==14222== Copyright (C) 2002-2010, and GNU GPL'd, by Julian Seward et al.
    ==14222== Using Valgrind-3.6.1 and LibVEX; rerun with -h for copyright info
    ==14222== Command: hello
    ==14222==
    Hello World!
    ==14222==
    ==14222== HEAP SUMMARY:
    ==14222==     in use at exit: 0 bytes in 0 blocks
    ==14222==   total heap usage: 0 allocs, 0 frees, 0 bytes allocated
    ==14222==
    ==14222== All heap blocks were freed -- no leaks are possible
    ==14222==
    ==14222== For counts of detected and suppressed errors, rerun with: -v
    ==14222== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 6 from 6)
    $
    
    And now the obvious: don't just read this stuff - write it and run it!

    We want to use valgrind for detection of memory allocation problems, so we need to make it shut up in normal situations. Just run it in quiet mode, so it won't give us any unwanted information:
    $
    $ valgrind -q hello
    Hello World!
    $
    
    Quickly try
    $ man valgrind
    
    to see some of the other flags/options besides - q.

  2. Valgrind and Uninitialized Values

  3. Now write a program called prog1a.c that will malloc space for 5 ints and then print out the 4th one.
    (DO NOT just copy and paste this - or any of the programs here!)
    #include <stdlib.h>
    #include <stdio.h>
    
    int main(void)
    {
        int *q = (int *)malloc(5 * sizeof(int));
        if (q == NULL)
        {
            perror("malloc(5 * sizeof(int))");
            exit(1);
        }
        int n = q[3];
        printf("q[3] = %d\n", n);
    
        return 0;
    }
    
    Compile and run this and you might get the following:
    $
    $ gcc prog1a.c
    $ a.out
    q[3] = 0
    $
    
    This is all very straightforward, except that it is deceiving and contains an error. Space allocated by malloc is not initialized, not even to 0 (zero), even if running the above code would lead you to believe otherwise.

    Will valgrind report use of an uninitialized variable? Yes and no.
    Try the "no" case first. In prog1a.c, keep the assignment statement "int n = ...", but omit the printf() statement. Run this with valgrind:
    $
    $ gcc prog1a.c
    $ valgrind -q a.out
    $
    
    So - there are no complaints.
    Now try the "yes" case - put the printf() statement back in and try again:
    $
    $ gcc prog1.c
    $ valgrind -q a.out
    ==30494== Use of uninitialised value of size 8
    ==30494==    at 0x301BA437BB: _itoa_word (in /lib64/libc-2.12.so)
    ==30494==    by 0x301BA46367: vfprintf (in /lib64/libc-2.12.so)
    ==30494==    by 0x301BA4EA29: printf (in /lib64/libc-2.12.so)
    ==30494==    by 0x4005E8: main (in /cse/dept/www/course_archive/2011-12/S/2031/Labs/10/a.out)
    ==30494==
    ==30494== Conditional jump or move depends on uninitialised value(s)
    ==30494==    at 0x301BA437C5: _itoa_word (in /lib64/libc-2.12.so)
    ==30494==    by 0x301BA46367: vfprintf (in /lib64/libc-2.12.so)
    ==30494==    by 0x301BA4EA29: printf (in /lib64/libc-2.12.so)
    ==30494==    by 0x4005E8: main (in /cse/dept/www/course_archive/2011-12/S/2031/Labs/10/a.out)
    ==30494==
    ==30494== Conditional jump or move depends on uninitialised value(s)
    ==30494==    at 0x301BA45979: vfprintf (in /lib64/libc-2.12.so)
    ==30494==    by 0x301BA4EA29: printf (in /lib64/libc-2.12.so)
    ==30494==    by 0x4005E8: main (in /cse/dept/www/course_archive/2011-12/S/2031/Labs/10/a.out)
    ==30494==
    ==30494== Conditional jump or move depends on uninitialised value(s)
    ==30494==    at 0x301BA45997: vfprintf (in /lib64/libc-2.12.so)
    ==30494==    by 0x301BA4EA29: printf (in /lib64/libc-2.12.so)
    ==30494==    by 0x4005E8: main (in /cse/dept/www/course_archive/2011-12/S/2031/Labs/10/a.out)
    ==30494==
    q[3] = 0  <--- here's the program output
    $
    
    The "q[3] = 0" was written to standard output; all the valgrind messages were written to standard error. Check this by redirecting standard error:
    $
    $ valgrind -q a.out 2>/dev/null
    q[3] = 0
    $
    
    If you need to separate the error messages from your program from valgrind output, you can specify a file where valgrind output goes. Like this:
    $
    $ valgrind -q --log-file=temp a.out
    q[3] = 0
    $
    $ more temp
    ==31666== Use of uninitialised value of size 8
    ==31666==    at 0x301BA437BB: _itoa_word (in /lib64/libc-2.12.so)
    ==31666==    by 0x301BA46367: vfprintf (in /lib64/libc-2.12.so)
    ==31666==    by 0x301BA4EA29: printf (in /lib64/libc-2.12.so)
    ==31666==    by 0x4005E8: main (in /cse/dept/www/course_archive/2011-12/S/2031/Labs/10/a.out)
    ==31666==
    ==31666== Conditional jump or move depends on uninitialised value(s)
    ==31666==    at 0x301BA437C5: _itoa_word (in /lib64/libc-2.12.so)
    ==31666==    by 0x301BA46367: vfprintf (in /lib64/libc-2.12.so)
    ==31666==    by 0x301BA4EA29: printf (in /lib64/libc-2.12.so)
    ==31666==    by 0x4005E8: main (in /cse/dept/www/course_archive/2011-12/S/2031/Labs/10/a.out)
    ==31666==
    ==31666== Conditional jump or move depends on uninitialised value(s)
    ==31666==    at 0x301BA45979: vfprintf (in /lib64/libc-2.12.so)
    ==31666==    by 0x301BA4EA29: printf (in /lib64/libc-2.12.so)
    ==31666==    by 0x4005E8: main (in /cse/dept/www/course_archive/2011-12/S/2031/Labs/10/a.out)
    ==31666==
    ==31666== Conditional jump or move depends on uninitialised value(s)
    ==31666==    at 0x301BA45997: vfprintf (in /lib64/libc-2.12.so)
    ==31666==    by 0x301BA4EA29: printf (in /lib64/libc-2.12.so)
    ==31666==    by 0x4005E8: main (in /cse/dept/www/course_archive/2011-12/S/2031/Labs/10/a.out)
    ==31666==
    $
    
    These messages would be better if they included line numbers. When line numbers are needed here or for other debugging purposes, compile using the - g flag and optimization level 0. Try this
    $
    $ gcc -Wall -g -O0 prog1a.c
    $ valgrind -q a.out
    ==31847== Use of uninitialised value of size 8
    ==31847==    at 0x301BA437BB: _itoa_word (in /lib64/libc-2.12.so)
    ==31847==    by 0x301BA46367: vfprintf (in /lib64/libc-2.12.so)
    ==31847==    by 0x301BA4EA29: printf (in /lib64/libc-2.12.so)
    ==31847==    by 0x4005E8: main (prog1.c:28)
    ==31847==
    ==31847== Conditional jump or move depends on uninitialised value(s)
    ==31847==    at 0x301BA437C5: _itoa_word (in /lib64/libc-2.12.so)
    ==31847==    by 0x301BA46367: vfprintf (in /lib64/libc-2.12.so)
    ==31847==    by 0x301BA4EA29: printf (in /lib64/libc-2.12.so)
    ==31847==    by 0x4005E8: main (prog1.c:28)
    ==31847==
    ==31847== Conditional jump or move depends on uninitialised value(s)
    ==31847==    at 0x301BA45979: vfprintf (in /lib64/libc-2.12.so)
    ==31847==    by 0x301BA4EA29: printf (in /lib64/libc-2.12.so)
    ==31847==    by 0x4005E8: main (prog1.c:28)
    ==31847==
    ==31847== Conditional jump or move depends on uninitialised value(s)
    ==31847==    at 0x301BA45997: vfprintf (in /lib64/libc-2.12.so)
    ==31847==    by 0x301BA4EA29: printf (in /lib64/libc-2.12.so)
    ==31847==    by 0x4005E8: main (prog1.c:28)
    ==31847==
    q[3] = 0
    $
    
    Note that you can now see at which line number the problem occurred - namely at the printf() statement.

    In "- O0", the first O is capital letter 'O' for "Optimization level", and the second '0' is a 0 (zero). Asking for optimization level 0, tells the compiler skip optimization, so it won't mess up the line numbering.

  4. When q[3] = 0 is printed, it seems like the space was automatically initialized to hold zero's, but that is not the case. If q[3] is not specifically assigned, then it has whatever happens to be at its location. To see this, get prog1b.c by adding the code in blue to prog1a.c
    #include <stdlib.h>
    #include <stdio.h>
    
    int main(void)
    {
    
        int *p = (int *)malloc(5 * sizeof(int));
        if (p == NULL)
        {
            perror("malloc(5 * sizeof(int))");
            exit(1);
        }
    
        int i;
        for (i = 0; i < 5; i++)
            p[i] = 100 * i;
    
        free(p);
    
    
        int *q = (int *)malloc(5 * sizeof(int));
        if (q == NULL)
        {
            perror("malloc(5 * sizeof(int))");
            exit(1);
        }
        int n = q[3];
        printf("q[3] = %d\n", n);
    
        return 0;
    }
    
    Compile and run this and you get
    $
    $ gcc -Wall prog1b.c
    $ a.out
    q[3] = 300
    $
    
    q[3] was never assigned a value, yet held the value 300.

    But we need to be careful here: as part of its setup, valgrind zeros out memory, so if we run the above using valgrind we get this:
    $
    $ gcc -Wall -g -O0 prog1b.c
    $ valgrind -q a.out
    ==399== Use of uninitialised value of size 8
    ==399==    at 0x301BA437BB: _itoa_word (in /lib64/libc-2.12.so)
    ==399==    by 0x301BA46367: vfprintf (in /lib64/libc-2.12.so)
    ==399==    by 0x301BA4EA29: printf (in /lib64/libc-2.12.so)
    ==399==    by 0x400695: main (prog1.c:28)
    ==399==
    ==399== Conditional jump or move depends on uninitialised value(s)
    ==399==    at 0x301BA437C5: _itoa_word (in /lib64/libc-2.12.so)
    ==399==    by 0x301BA46367: vfprintf (in /lib64/libc-2.12.so)
    ==399==    by 0x301BA4EA29: printf (in /lib64/libc-2.12.so)
    ==399==    by 0x400695: main (prog1.c:28)
    ==399==
    ==399== Conditional jump or move depends on uninitialised value(s)
    ==399==    at 0x301BA45979: vfprintf (in /lib64/libc-2.12.so)
    ==399==    by 0x301BA4EA29: printf (in /lib64/libc-2.12.so)
    ==399==    by 0x400695: main (prog1.c:28)
    ==399==
    ==399== Conditional jump or move depends on uninitialised value(s)
    ==399==    at 0x301BA45997: vfprintf (in /lib64/libc-2.12.so)
    ==399==    by 0x301BA4EA29: printf (in /lib64/libc-2.12.so)
    ==399==    by 0x400695: main (prog1.c:28)
    ==399==
    q[3] = 0 <--- this (incorrectly) gave us 300 before!
    $
    

  5. On the other hand, calloc() does give us space that is all initialized to zeros. Get prog1c.c from prog1b.c by replacing the int *q = (int *)malloc ... line with
    int *q = (int *)calloc(5, sizeof(int));
    
    Now compile and run under valgrind as before:
    $
    $ gcc -Wall -g -O0 prog1c.c
    $ valgrind -q a.out
    q[3] = 0
    $
    
    ... no valgrind complaints about uninitialized values.

  6. Valgrind and Array Overflow/Underflow

  7. C does not detect when you go beyond the end of an array. Valgrind can be helpful here. Start with prog2a.c:
    #include 
    #include 
    
    int main(void)
    {
        // malloc an array
        int *q = (int *)malloc(5 * sizeof(int));
        if (q == NULL)
        {
            perror("malloc(5 * sizeof(int))");
            exit(1);
        }
    
        // initialize the malloc'd array
        int i;
        for (i = 0; i < 5; i++)
            q[i] = 100 * i + 1;
    
        // print out array values
        for (i = 0; i < 5; i++)
            printf("q[%d] = %d\n", i, q[i]);
    
        return 0;
    }
    
    Compile and run this under valgrind:
    $
    $ gcc -Wall -g -O0 prog2a.c
    $ valgrind -q a.out
    q[0] = 1
    q[1] = 101
    q[2] = 201
    q[3] = 301
    q[4] = 401
    $
    ... valgrind is happy.
    
    Now get prog2b.c from prog2a.c by adding lines that access outside the malloc'd array, namely at indices -1, -2, ... ,-5 and 5, 6, ... ,9
        ...
        ...
        // use indices that are out of bounds
        printf("q[%d] = %d\n", -1, q[-1]);  // line 24
        printf("q[%d] = %d\n", -2, q[-2]);
        printf("q[%d] = %d\n", -3, q[-3]);
        printf("q[%d] = %d\n", -4, q[-4]);
        printf("q[%d] = %d\n", -5, q[-5]);
        printf("q[%d] = %d\n", 5, q[5]);
        printf("q[%d] = %d\n", 6, q[6]);
        printf("q[%d] = %d\n", 7, q[7]);
        printf("q[%d] = %d\n", 8, q[8]);
        printf("q[%d] = %d\n", 9, q[9]);
    
        return 0;
    
    Compile and run this using valgrind. There's quite a bit of output, but it's instructive.
    $
    $ gcc -Wall -g -O0 prog2b.c
    $ valgrind -q a.out
    q[0] = 1
    q[1] = 101
    q[2] = 201
    q[3] = 301
    q[4] = 401
    ==2660== Invalid read of size 4
    ==2660==    at 0x400631: main (prog2b.c:24)
    ==2660==  Address 0x4c3403c is 4 bytes before a block of size 20 alloc'd
    ==2660==    at 0x4A054C2: malloc (vg_replace_malloc.c:236)
    ==2660==    by 0x4005A5: main (prog2b.c:7)
    ==2660==
    q[-1] = 0
    ==2660== Invalid read of size 4
    ==2660==    at 0x400652: main (prog2b.c:25)
    ==2660==  Address 0x4c34038 is 8 bytes before a block of size 20 alloc'd
    ==2660==    at 0x4A054C2: malloc (vg_replace_malloc.c:236)
    ==2660==    by 0x4005A5: main (prog2b.c:7)
    ==2660==
    q[-2] = 0
    ==2660== Invalid read of size 4
    ==2660==    at 0x400673: main (prog2b.c:26)
    ==2660==  Address 0x4c34034 is 12 bytes before a block of size 20 alloc'd
    ==2660==    at 0x4A054C2: malloc (vg_replace_malloc.c:236)
    ==2660==    by 0x4005A5: main (prog2b.c:7)
    ==2660==
    q[-3] = 0
    ==2660== Invalid read of size 4
    ==2660==    at 0x400694: main (prog2b.c:27)
    ==2660==  Address 0x4c34030 is 16 bytes before a block of size 20 alloc'd
    ==2660==    at 0x4A054C2: malloc (vg_replace_malloc.c:236)
    ==2660==    by 0x4005A5: main (prog2b.c:7)
    ==2660==
    q[-4] = 0
    ==2990== Invalid read of size 4
    ==2990==    at 0x4006B5: main (prog2b.c:28)
    ==2990==  Address 0x4c3402c is not stack'd, malloc'd or (recently) free'd
    ==2990==
    q[-5] = 0
    ==2990== Invalid read of size 4
    ==2990==    at 0x4006D6: main (prog2b.c:29)
    ==2990==  Address 0x4c34054 is 0 bytes after a block of size 20 alloc'd
    ==2990==    at 0x4A054C2: malloc (vg_replace_malloc.c:236)
    ==2990==    by 0x4005A5: main (prog2b.c:7)
    ==2990==
    q[5] = 0
    ==2990== Invalid read of size 4
    ==2990==    at 0x4006F7: main (prog2b.c:30)
    ==2990==  Address 0x4c34058 is 4 bytes after a block of size 20 alloc'd
    ==2990==    at 0x4A054C2: malloc (vg_replace_malloc.c:236)
    ==2990==    by 0x4005A5: main (prog2b.c:7)
    ==2990==
    q[6] = 0
    ==2990== Invalid read of size 4
    ==2990==    at 0x400718: main (prog2b.c:31)
    ==2990==  Address 0x4c3405c is 8 bytes after a block of size 20 alloc'd
    ==2990==    at 0x4A054C2: malloc (vg_replace_malloc.c:236)
    ==2990==    by 0x4005A5: main (prog2b.c:7)
    ==2990==
    q[7] = 0
    ==2990== Invalid read of size 4
    ==2990==    at 0x400739: main (prog2b.c:32)
    ==2990==  Address 0x4c34060 is 12 bytes after a block of size 20 alloc'd
    ==2990==    at 0x4A054C2: malloc (vg_replace_malloc.c:236)
    ==2990==    by 0x4005A5: main (prog2b.c:7)
    ==2990==
    q[8] = 0
    ==2990== Invalid read of size 4
    ==2990==    at 0x40075A: main (prog2b.c:33)
    ==2990==  Address 0x4c34064 is not stack'd, malloc'd or (recently) free'd
    ==2990==
    q[9] = 0
    $
    
    Note how valgrind tells you where the inappropriate read was:
        ...
        Address 0x4c3403c is 4 bytes before a block of size 20 alloc'd
        ...
        Address 0x4c34038 is 8 bytes before a block of size 20 alloc'd
        ...
        but when it is far enough away, there is no reference to the malloc'd space
        ...
        Address 0x4c3402c is not stack'd, malloc'd or (recently) free'd
    
    Similarily at the other end.

  8. prog2b.c used invalid reads. Would invalid writes be the same? Get prog2c.c from prog2b.c by replacing the reads with writes at the same addresses:
        ...
        ...
        // use indices that are out of bounds
        q[-1] = 99; // line 24
        q[-2] = 99;
        q[-3] = 99;
        q[-4] = 99;
        q[-5] = 99;
        q[5] = 99;
        q[6] = 99;
        q[7] = 99;
        q[8] = 99;
        q[9] = 99;
    
    
        return 0;
    }
    
    Compile and run. As before, some messages give location in terms of before/after malloc'd space, but not for writes that are quite far away from the malloc'd space.
    $
    $ gcc -Wall -g -O0 prog2c.c
    $ valgrind -q a.out
    q[0] = 1
    q[1] = 101
    q[2] = 201
    q[3] = 301
    q[4] = 401
    ==4009== Invalid write of size 4
    ==4009==    at 0x400631: main (prog2c.c:24)
    ==4009==  Address 0x4c3403c is 4 bytes before a block of size 20 alloc'd
    ==4009==    at 0x4A054C2: malloc (vg_replace_malloc.c:236)
    ==4009==    by 0x4005A5: main (prog2c.c:7)
    ==4009==
    ==4009== Invalid write of size 4
    ==4009==    at 0x40063F: main (prog2c.c:25)
    ==4009==  Address 0x4c34038 is 8 bytes before a block of size 20 alloc'd
    ==4009==    at 0x4A054C2: malloc (vg_replace_malloc.c:236)
    ==4009==    by 0x4005A5: main (prog2c.c:7)
    ==4009==
    ==4009== Invalid write of size 4
    ==4009==    at 0x40064D: main (prog2c.c:26)
    ==4009==  Address 0x4c34034 is 12 bytes before a block of size 20 alloc'd
    ==4009==    at 0x4A054C2: malloc (vg_replace_malloc.c:236)
    ==4009==    by 0x4005A5: main (prog2c.c:7)
    ==4009==
    ==4009== Invalid write of size 4
    ==4009==    at 0x40065B: main (prog2c.c:27)
    ==4009==  Address 0x4c34030 is 16 bytes before a block of size 20 alloc'd
    ==4009==    at 0x4A054C2: malloc (vg_replace_malloc.c:236)
    ==4009==    by 0x4005A5: main (prog2c.c:7)
    ==4009==
    ==4009== Invalid write of size 4
    ==4009==    at 0x400669: main (prog2c.c:28)
    ==4009==  Address 0x4c3402c is not stack'd, malloc'd or (recently) free'd
    ==4009==
    ==4009== Invalid write of size 4
    ==4009==    at 0x400677: main (prog2c.c:29)
    ==4009==  Address 0x4c34054 is 0 bytes after a block of size 20 alloc'd
    ==4009==    at 0x4A054C2: malloc (vg_replace_malloc.c:236)
    ==4009==    by 0x4005A5: main (prog2c.c:7)
    ==4009==
    ==4009== Invalid write of size 4
    ==4009==    at 0x400685: main (prog2c.c:30)
    ==4009==  Address 0x4c34058 is 4 bytes after a block of size 20 alloc'd
    ==4009==    at 0x4A054C2: malloc (vg_replace_malloc.c:236)
    ==4009==    by 0x4005A5: main (prog2c.c:7)
    ==4009==
    ==4009== Invalid write of size 4
    ==4009==    at 0x400693: main (prog2c.c:31)
    ==4009==  Address 0x4c3405c is 8 bytes after a block of size 20 alloc'd
    ==4009==    at 0x4A054C2: malloc (vg_replace_malloc.c:236)
    ==4009==    by 0x4005A5: main (prog2c.c:7)
    ==4009==
    ==4009== Invalid write of size 4
    ==4009==    at 0x4006A1: main (prog2c.c:32)
    ==4009==  Address 0x4c34060 is 12 bytes after a block of size 20 alloc'd
    ==4009==    at 0x4A054C2: malloc (vg_replace_malloc.c:236)
    ==4009==    by 0x4005A5: main (prog2c.c:7)
    ==4009==
    ==4009== Invalid write of size 4
    ==4009==    at 0x4006AF: main (prog2c.c:33)
    ==4009==  Address 0x4c34064 is not stack'd, malloc'd or (recently) free'd
    ==4009==
    $
    

  9. Valgrind and Memory Leaks (and a double free)

  10. Access to the malloc'd space is through the pointer returned by malloc. If that pointer is reset to point somewhere else, the malloc'd space is lost/no longer reachable.

    The following prog3a.c calls malloc twice.
    #include <stdlib.h>
    #include <stdio.h>
    
    int main(void)
    {
    
        // allocate 55 bytes
        printf("Allocating 55 bytes\n");
        char *q = (char *)malloc(55);
        if (q == NULL)
        {
            perror("malloc(55)");
            exit(1);
        }
    
        // allocate a different 50 bytes
        printf("Allocating a different 50 bytes\n");
        char *p = (char *)malloc(50);
        if (p == NULL)
        {
            perror("malloc(50)");
            exit(1);
        }
    
        free(p);
        free(q);
    
        return 0;
    }
    
    Compile and run this under valgrind. There should be no output from the program or from valgrind.
    $
    $ gcc -Wall -g -O0 prog3a.c
    $ valgrind -q a.out
    Allocating 55 bytes
    Allocating a different 50 bytes
    $
    
    Now get prog3b.c from prog3a.c by reassigning q to create a memory leak. We just make q point to the same place as p and remove the "free(q)" statement:
    #include <stdlib.h>
    #include <stdio.h>
    
    int main(void)
    {
    
        // allocate 55 bytes
        printf("Allocating 55 bytes\n");
        char *q = (char *)malloc(55); // line 9
        if (q == NULL)
        {
            perror("malloc(55)");
            exit(1);
        }
    
        // allocate a different 50 bytes
        printf("Allocating a different 50 bytes\n");
        char *p = (char *)malloc(50); // line 18
        if (p == NULL)
        {
            perror("malloc(50)");
            exit(1);
        }
    
        // should cause a memory leak - nothing is pointing to the 55 bytes
        q = p;  // line 26
    
        free(p);
    
        return 0;
    }
    
    Compile and run.
    $
    $ gcc -Wall -g -O0 prog3b.c
    $ valgrind -q a.out
    Allocating 55 bytes
    Allocating a different 50 bytes
    $
    
    Valgrind doesn't seem to find a problem here. We need to add an option for leak checking:
    $
    $ gcc -Wall -g -O0 prog3b.c
    $ valgrind -q --leak-check=full a.out
    Allocating 55 bytes
    Allocating a different 50 bytes
    ==6632== 55 bytes in 1 blocks are definitely lost in loss record 1 of 1
    ==6632==    at 0x4A054C2: malloc (vg_replace_malloc.c:236)
    ==6632==    by 0x4005FF: main (prog3b.c:9)
    ==6632==
    $
    
    Try adding "free(q)" right after the "free(p)" statement and see what you get:
        ...
        // should cause a memory leak - nothing pointing to the 55 bytes
        q = p;  // line 26
    
        free(p);
        free(q);
    
        return 0;
    
    Compile and run. p and q point to the same space. Once that space has been free'd using p, it cannot be free'd again.
    $
    $ gcc -Wall -g -O0 prog3b.c
    $ valgrind -q --leak-check=full a.out
    Allocating 55 bytes
    Allocating a different 50 bytes
    ==5614== Invalid free() / delete / delete[]
    ==5614==    at 0x4A050D7: free (vg_replace_malloc.c:366)
    ==5614==    by 0x400671: main (prog3b.c:29)
    ==5614==  Address 0x4c340c0 is 0 bytes inside a block of size 50 free'd
    ==5614==    at 0x4A050D7: free (vg_replace_malloc.c:366)
    ==5614==    by 0x400665: main (prog3b.c:28)
    ==5614==
    ==5614== 55 bytes in 1 blocks are definitely lost in loss record 1 of 1
    ==5614==    at 0x4A054C2: malloc (vg_replace_malloc.c:236)
    ==5614==    by 0x4005FF: main (prog3b.c:9)
    ==5614==
    $
    

  11. Now get prog3c.c from prog3b.c by removing the lines
        // should cause a memory leak - nothing pointing to the 55 bytes
        q = p;  // line 26
    
        free(p);
        free(q);
    
    So we have a main() with q pointing to 55 bytes and p pointing to 50 bytes when main ends. This will also give notice of memory leaks:
    $
    $ gcc -Wall -g -O0 prog3c.c
    $ valgrind -q --leak-check=full a.out
    Allocating 55 bytes
    Allocating a different 50 bytes
    ==5935== 50 bytes in 1 blocks are definitely lost in loss record 1 of 2
    ==5935==    at 0x4A054C2: malloc (vg_replace_malloc.c:236)
    ==5935==    by 0x4005E2: main (prog3c.c:18)
    ==5935==
    ==5935== 55 bytes in 1 blocks are definitely lost in loss record 2 of 2
    ==5935==    at 0x4A054C2: malloc (vg_replace_malloc.c:236)
    ==5935==    by 0x4005AF: main (prog3c.c:9)
    ==5935==
    $
    
    What's going on here? Remember that main() is a function. Local variables in a function are allocated on the stack and they disappear when the function returns. So variables p and q are gone when the function returns. But the malloc'd space is still there - and there is now nothing pointing to it, so we have memory leaks.

    Of course, this doesn't matter in the main() function, as all memory will be released when the program finishes, but, in general, it does matter for space malloc'd inside a function body. It will depend on the situation. Sometimes you want the function to allocate some space and return a pointer to it (for example, to create a linked list node and return a pointer to it). In that case, you would certainly not free the allocated memory before the function returns. On the other hand, sometimes the function will allocate space, use the space and then not need it when it returns. In that case, the memory should be free'd before the function returns - especially if the function may be called inside a loop.

End of Lab 10