简体   繁体   中英

Valgrind - Memory Leaks When Duplicating/Freeing Arrays

I'm messing around with allocating and deallocating memory to get the hang of it. Below, I take in string arguments from the command line ( char **argv ) and try to duplicate them as uppercase strings in another malloc'd array:

static char **duplicateArgs(int argc, char **argv) {
    if (argc <= 1) {
        printf("%s\n", "No strings to duplicate.");
        exit(1);
    }
    char **duplicate = (char**)malloc((argc + 1) * sizeof(char**));

    int i = 1;
    int j = 0;
    int stringLength;

    while (argv[i]) {
        stringLength = strlen(argv[i]);
        duplicate[i] = (char*)malloc(stringLength * sizeof(char) + 1);
        for (j = 0; j < stringLength; j++) {
            duplicate[i][j] = toupper(argv[i][j]);
        }
        duplicate[i][j]= '\0';
        i++;
    }
    duplicate[i] = NULL;
    return duplicate;
}

I have a function that frees them as well:

static void freeDuplicateArgs(char **copy) {
    int i = 0;
    while (copy[i]) {
        if (copy[i] != NULL){
            free(copy[i]);
        }
        i++;
    }
    if (copy != NULL){
        free(copy);
        }
}

In the main function, I call these methods, print them out and then free them:

    char **copy = duplicateArgs(argc, argv);
    char **p = copy;

    argv++;
    p++;
    while(*argv) {
        printf("%s %s\n", *argv++, *p++);
    }

    freeDuplicateArgs(copy);
    return 0;

The output works as expected, printing out the string regularly and then printing it out in caps. However, when I check it against Valgrind, I get this mess:

==20867== Conditional jump or move depends on uninitialised value(s)
==20867==    at 0x100000D1A: freeDuplicateArgs (part2.c:32)
==20867==    by 0x100000E19: main (part2.c:57)
==20867== 
==20867== 
==20867== HEAP SUMMARY:
==20867==     in use at exit: 62,847 bytes in 368 blocks
==20867==   total heap usage: 520 allocs, 152 frees, 66,761 bytes allocated
==20867== 
==20867== LEAK SUMMARY:
==20867==    definitely lost: 8,639 bytes in 18 blocks
==20867==    indirectly lost: 1,168 bytes in 5 blocks
==20867==      possibly lost: 4,925 bytes in 68 blocks
==20867==    still reachable: 48,115 bytes in 277 blocks
==20867==         suppressed: 0 bytes in 0 blocks

Why am I getting such a memory mess if the compiler is raising no errors for my freeDuplicates method and my output is okay?

Edit: I Should also mention that I'm on OSX 10.8 -- apparently Valgrind has some issues with that...

Freeing code

Your freeing code is interesting — not precisely wrong, but not really right, either:

static void freeDuplicateArgs(char **copy) {
    int i = 0;
    while (copy[i]) {
        if (copy[i] != NULL){
            free(copy[i]);
        }
        i++;
    }
    if (copy != NULL){
        free(copy);
    }
}

You test for copy != NULL just before freeing it, but you've been using it all along, so the test is redundant there. Besides, you can do free(NULL) without running into problems.

Then we look at the loop:

 while (copy[i] != NULL)
 {
     if (copy[i] != NULL)

The loop condition and the if condition are identical — and again, you can free(NULL) .

Your while loop has the structure of a for loop written out. You'd do better with:

static void freeDuplicateArgs(char **copy)
{
    if (copy != NULL)
    {
        for (int i = 0; copy[i] != NULL; i++)
            free(copy[i]);
        free(copy);
    }
}

The test for copy != NULL prevents crashes, and only freeing it when it is known to be non-null is a (very, very) minor optimization.

I doubt that this is directly the cause of the problems (I'll look a bit harder at the rest of the code in a moment), but it is something to consider.

Allocating code

You have:

static char **duplicateArgs(int argc, char **argv) {
    if (argc <= 1) {
        printf("%s\n", "No strings to duplicate.");
        exit(1);
    }

First of all, a program can be invoked with no user arguments but it still has argv[0] , the program name and a NULL pointer. Your code should probably simply handle the negative values by returning (char **)NULL . For 0 arguments, you return a pointer to a null pointer. And for one or more arguments, there is work to do.

Consider the line:

duplicate[i] = (char*)malloc(stringLength * sizeof(char) + 1);

There are two cases that you might consider: sizeof(char) == 1 and sizeof(char) > 1 (yes, hold on — wait!). If sizeof(char) == 1 , then you don't need to multiply by it. If sizeof(char) > 1 , you aren't allocating enough space; the expression should be (stringLength + 1) * sizeof(char) . So, write one of these two lines:

duplicate[i] = (char*)malloc((stringLength + 1) * sizeof(char));
duplicate[i] = (char*)malloc(stringLength + 1);

I note that the C standard says that sizeof(char) == 1 by definition, so the actual result is the same, but if you were dealing with arrays of int or something else, getting the multiplication by sizeof(type) in the right place could be crucial.

ISO/IEC 9899:2011 §6.5.3.4 The sizeof and _Alignof operators

¶4 When sizeof is applied to an operand that has type char , unsigned char , or signed char , (or a qualified version thereof) the result is 1.

Valgrind on Mac OS X 10.7.5

Taking your code almost as written in the question:

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

static char **duplicateArgs(int argc, char **argv)
{
    if (argc <= 1) {
        printf("%s\n", "No strings to duplicate.");
        exit(1);
    }
    char **duplicate = (char**)malloc((argc + 1) * sizeof(char**));

    int i = 0;
    int j = 0;
    int stringLength;

    while (argv[i]) {
        stringLength = strlen(argv[i]);
        duplicate[i] = (char*)malloc(stringLength * sizeof(char) + 1);
        for (j = 0; j < stringLength; j++) {
            duplicate[i][j] = toupper(argv[i][j]);
        }
        duplicate[i][j]= '\0';
        i++;
    }
    duplicate[i] = NULL;
    return duplicate;
}

static void freeDuplicateArgs(char **copy)
{
    int i = 0;
    while (copy[i]) {
        if (copy[i] != NULL){
            free(copy[i]);
        }
        i++;
    }
    if (copy != NULL){
        free(copy);
        }
}

int main(int argc, char **argv)
{
    char **copy = duplicateArgs(argc, argv);
    char **p = copy;

    argv++;
    p++;
    while(*argv) {
        printf("%s %s\n", *argv++, *p++);
    }

    freeDuplicateArgs(copy);
    return 0;
}

(The difference is the int i = 0; in this working code instead of int i = 1; in the question code.)

When run under valgrind , this gets a clean bill of health:

$ gcc -O3 -g -std=c99 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition mlk.c -o mlk
$ valgrind ./mlk nuts oh hazelnuts
==582== Memcheck, a memory error detector
==582== Copyright (C) 2002-2011, and GNU GPL'd, by Julian Seward et al.
==582== Using Valgrind-3.7.0 and LibVEX; rerun with -h for copyright info
==582== Command: ./mlk nuts oh hazelnuts
==582== 
nuts NUTS
oh OH
hazelnuts HAZELNUTS
==582== 
==582== HEAP SUMMARY:
==582==     in use at exit: 18,500 bytes in 33 blocks
==582==   total heap usage: 38 allocs, 5 frees, 18,564 bytes allocated
==582== 
==582== LEAK SUMMARY:
==582==    definitely lost: 0 bytes in 0 blocks
==582==    indirectly lost: 0 bytes in 0 blocks
==582==      possibly lost: 0 bytes in 0 blocks
==582==    still reachable: 18,500 bytes in 33 blocks
==582==         suppressed: 0 bytes in 0 blocks
==582== Rerun with --leak-check=full to see details of leaked memory
==582== 
==582== For counts of detected and suppressed errors, rerun with: -v
==582== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 1 from 1)
$

As you can see, there are no memory errors reported. The 33 blocks still in use is 'normal' for this machine; they're allocated by the Mac OS XC runtime library. I haven't got valgrind on a Mac OS X 10.8 system to compare with. I recommend you try running a very simple program under valgrind and see what it tells you:

int main(void) { return 0; }

That will be the baseline for your system. If there are leaks, they are not caused by your code, and you'll need to learn how to record suppressions so that your programs are not penalized for the transgressions of the O/S.

If you run the simple program above and find that it doesn't produce leaks — or the leaks are radically simpler and smaller — then try some slightly more complex programs:

#include <stdlib.h>
int main(void)
{
    void *vp = malloc(32);
    free(vp);
    return(0);
}

This ensures that malloc() is dragged in and used, but clearly doesn't leak (and it handles malloc() failure correctly too!).

valgrind support for Mac OS X 10.8

The Valgrind web site says:

18 September 2012: valgrind-3.8.1, for X86/Linux, AMD64/Linux, ARM/Linux, PPC32/Linux, PPC64/Linux, S390X/Linux, MIPS/Linux, ARM/Android (2.3.x and later), X86/Android (4.0 and later), X86/Darwin and AMD64/Darwin (Mac OS X 10.6 and 10.7, with limited support for 10.8) is available.

What you're seeing may be an aspect of the limited support for Mac OS X 10.8.

I see I need to rebuild valgrind for myself (3.7.0 is not current).

我认为您永远不会将freeDuplicateArgs duplicate[0]设置为任何值,所以当您在freeDuplicateArgs使用copy[i]时(从i = 0开始),您将不知道会发生什么。

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM