Unable to free memory from struct arrays, valgrind shows memory leak

时间:2015-08-07 02:11:57

标签: c memory memory-management struct valgrind

I have a nested struct to which I am dynamically assigning memory and creating arrays. So, its like a struct has rows and rows has columns both as structs.

First I assign memory for main struct, then based on the input rows I assign the memory for row struct*(number of rows) and then loop over rows and assign memory for number of columns.

The code for assigning:

    int parse_json_to_result(char *json, db_res_t** result) {
        printf("received json: %s\n", json);
        cJSON *root, *record;
        int recordCount = 0;
        int colCount = 0;
        int i = 0;
        int j = 0;
        int int_val = 0;
        char *str_val = '\0';

        root = cJSON_Parse(json);
        recordCount = cJSON_GetArraySize(root);
        *result = calloc(1,sizeof(db_res_t));
        (*result)->n = recordCount;

        // malloc for number of rows.
        (*result)->rows = calloc(recordCount,sizeof(db_row_t) );

        //this is done to get the count of columns only once.
        record = cJSON_GetArrayItem(root, i);
        colCount = cJSON_GetArraySize(record);

        for (i = 0; i < recordCount; i++) {
            j = 0;
            record = cJSON_GetArrayItem(root, i);
            (*result)->rows->n = colCount;
            // malloc for number of coulmns in each row.
            (*result)->rows[i].values = calloc(colCount, sizeof(db_val_t) );
            cJSON *subitem = record->child;
            while (subitem) {
                if (subitem->type == cJSON_Number) {
                int_val =
                        cJSON_GetObjectItem(record, subitem->string)->valueint;
                (*result)->rows[i].values[j].type = DB_INT;
                (*result)->rows[i].values[j].nul = 0;
                (*result)->rows[i].values[j++].val.int_val = int_val;
//              printf("%d\n", int_val);
            } else {
                str_val =
                        cJSON_GetObjectItem(record, subitem->string)->valuestring;
//              printf("%s\n", str_val);
                (*result)->rows[i].values[j].type = DB_STRING;
                if (strcmp(str_val, "") == 0) {
                    (*result)->rows[i].values[j].nul = 1;
                    (*result)->rows[i].values[j++].free = 0;
                } else {
                    (*result)->rows[i].values[j].nul = 0;
                    (*result)->rows[i].values[j].free = 1;
                    (*result)->rows[i].values[j++].val.string_val = strdup(str_val);
                }
            }
                subitem = subitem->next;
            }
        }
        cJSON_Delete(root);
        return 1;
    }

The Structs:

struct _str{
    char* s; /**< string as char array */
    int len; /**< string length, not including null-termination */
};

typedef struct _str str;
typedef str* db_key_t;

typedef enum {
    DB_INT,        /**< represents an 32 bit integer number      */
    DB_BIGINT,     /**< represents an 64 bit integer number      */
    DB_DOUBLE,     /**< represents a floating point number       */
    DB_STRING,     /**< represents a zero terminated const char* */
    DB_STR,        /**< represents a string of 'str' type        */
    DB_DATETIME,   /**< represents date and time                 */
    DB_BLOB,       /**< represents a large binary object         */
    DB_BITMAP      /**< an one-dimensional array of 32 flags     */
} db_type_t;


typedef struct {
    db_type_t type; /**< Type of the value                              */
    int nul;        /**< Means that the column in database has no value */
    int free;       /**< Means that the value should be freed */
    /** Column value structure that holds the actual data in a union.  */
    union {
        int           int_val;    /**< integer value              */
        long long     bigint_val; /**< big integer value          */
        double        double_val; /**< double value               */
        time_t        time_val;   /**< unix time_t value          */
        const char*   string_val; /**< zero terminated string     */
        str           str_val;    /**< str type string value      */
        str           blob_val;   /**< binary object data         */
        unsigned int  bitmap_val; /**< Bitmap data type           */
    } val;
} db_val_t;
typedef struct db_row {
    db_val_t* values;  /**< Columns in the row */
    int n;             /**< Number of columns in the row */
} db_row_t;
struct db_row;
typedef struct db_res {
    struct {
        db_key_t* names;   /**< Column names                    */
        db_type_t* types;  /**< Column types                    */
        int n;             /**< Number of columns               */
    } col;
    struct db_row* rows;   /**< Rows                            */
    int n;                 /**< Number of rows in current fetch */
    int res_rows;          /**< Number of total rows in query   */
    int last_row;          /**< Last row                        */
} db_res_t;

Code to free the memory:

int free_result(db_res_t* _r)
{
    if (!_r)
    {
        return -1;
    }
    int i,row_count=0;
    int col_count=0;
    printf("freeing result set at %p\n", _r);
    row_count = _r->n;
    printf("RowCount %d .\n",row_count);
    for(i=0;i<row_count;i++)
    {
        printf("Freeing %d row.\n",i);
        col_count= _r->rows[i].n;
        printf("col_count %d .\n",col_count);
        int j=0;
        for(j=0;j<col_count;j++)
        {
            if(_r->rows[i].values[j].type == DB_STRING && _r->rows[i].values[j].nul==0)
            {
                printf("Freeing %d col.\n",j);
                free(_r->rows[i].values[j].val.string_val);
                _r->rows[i].values[j].val.string_val =NULL;
            }
            else if(_r->rows[i].values[j].type == DB_STR  && _r->rows[i].values[j].nul==0)
            {
                printf("Freeing %d col.",j);
                free(_r->rows[i].values[j].val.str_val.s);
                _r->rows[i].values[j].val.str_val.s =NULL;
            }
        }
        //free all value colums for each row.
        free(_r->rows[i].values);
        _r->rows[i].values = NULL;
    }
    //free all rows
    free(_r->rows);
    _r->rows =NULL;
    //free resultset
    free(_r);
    _r = NULL;
    //this will print nil.
    printf("freed result set a %p\n", _r);
    return 0;
}

My Sample input is 2 rows with 10 columns each out of which only few columns are char*. So in free I expect output somewhat like:

Freeing 0 row. col_count 10 . Freeing 1 col. Freeing 2 col. Freeing 3 col. Freeing 4 col. Freeing 1 row. col_count 10 . Freeing 1 col. Freeing 2 col. Freeing 3 col. Freeing 4 col. freed result set a (nil)

But what I actually get is:

freeing result set at 0x18e13e0 RowCount 3 . Freeing 0 row. col_count 10 . Freeing 1 col. Freeing 2 col. Freeing 3 col. Freeing 4 col. Freeing 1 row. col_count 0 . Freeing 2 row. col_count 0 . freed result set a (nil)

The loop does not go beyond 1st row. I think all the values are getting dereferenced in first pass itself. But how? I am unable to understand. I am able to access all the values of each row and columns. So, may be looping in free_result has some problem.

main

    int main(void) {
        db_res_t *result = NULL;
char* json = "[{\"id\":11,\"username\":\"microsip\",\"domain\":\"192.168.254.128\",\"event\":\"presence\",\"etag\":\"a.1437194656.2922.1.0\",\"expires\":1437200355,\"received_time\":-1,\"body\":\"\",\"extra_hdrs\":\"\",\"sender\":\"\"},{\"id\":12,\"username\":\"microsip\",\"domain\":\"92.168.254.128\",\"event\":\"presence\",\"etag\":\"a.1437194656.2922.1.0\",\"expires\":1437200355,\"received_time\":-1,\"body\":\"\",\"extra_hdrs\":\"\",\"sender\":\"\"}]";
parse_json_to_result(json,&result);
        free_result(result);
    }

The valgrind log:

HEAP SUMMARY:
     in use at exit: 119 bytes in 6 blocks
   total heap usage: 3,336 allocs, 3,330 frees, 195,040 bytes allocated

 55 bytes in 4 blocks are definitely lost in loss record 3 of 3
    at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
    by 0x542C839: strdup (strdup.c:42)
    by 0x4023D4: parse_json_to_result (Util.c:64)
    by 0x4035CE: getResource (RepositoryHandler.c:116)
    by 0x400FC5: test_util_get (Test.c:33)
    by 0x400F7A: main (Test.c:25)

 LEAK SUMMARY:
    definitely lost: 55 bytes in 4 blocks
    indirectly lost: 0 bytes in 0 blocks
      possibly lost: 0 bytes in 0 blocks
    still reachable: 64 bytes in 2 blocks
         suppressed: 0 bytes in 0 blocks
 Reachable blocks (those to which a pointer was found) are not shown.
 To see them, rerun with: --leak-check=full --show-leak-kinds=all

 For counts of detected and suppressed errors, rerun with: -v
 ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

Sorry for the long post but I have tried to provide all the information. I am stuck at this for very long and doesn't seem to find the problem.

EDIT

I am sorry guys for an external library but it's a single c file which has to be compiled along with -lm option. Link to ithttps://github.com/kbranigan/cJSON

gcc -o Test.c cJSON.c -lm

1 个答案:

答案 0 :(得分:2)

这个bug非常微妙。在分配代码中,您有:

(*result)->rows->n = colCount;

在发布代码中,您使用:

col_count= _r->rows[i].n;

您需要将分配代码更改为:

(*result)->rows[i].n = colCount;

然后正确释放所有内存。请注意,原件等同于:

(*result)->rows[0].n = colCount;

以便重复设置rows[0].n中的值,但rows[1].n中的值保留为零,由calloc()设置。

这是我最终得到的代码。它仍然有我用来帮助我缩小问题范围的调试打印语句。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "cJSON.h"

struct _str
{
    char *s; /**< string as char array */
    int len; /**< string length, not including null-termination */
};

typedef struct _str str;
typedef str *db_key_t;

typedef enum {
    DB_INT,        /**< represents an 32 bit integer number      */
    DB_BIGINT,     /**< represents an 64 bit integer number      */
    DB_DOUBLE,     /**< represents a floating point number       */
    DB_STRING,     /**< represents a zero terminated const char* */
    DB_STR,        /**< represents a string of 'str' type        */
    DB_DATETIME,   /**< represents date and time                 */
    DB_BLOB,       /**< represents a large binary object         */
    DB_BITMAP      /**< an one-dimensional array of 32 flags     */
} db_type_t;

typedef struct
{
    db_type_t type; /**< Type of the value                              */
    int nul;        /**< Means that the column in database has no value */
    int free;       /**< Means that the value should be freed */
    /** Column value structure that holds the actual data in a union.  */
    union
    {
        int int_val;              /**< integer value              */
        long long bigint_val;     /**< big integer value          */
        double double_val;        /**< double value               */
        time_t time_val;          /**< unix time_t value          */
        /*const*/ char *string_val;   /**< zero terminated string     */
        str str_val;              /**< str type string value      */
        str blob_val;             /**< binary object data         */
        unsigned int bitmap_val;  /**< Bitmap data type           */
    } val;
} db_val_t;
typedef struct db_row
{
    db_val_t *values;  /**< Columns in the row */
    int n;             /**< Number of columns in the row */
} db_row_t;

typedef struct db_res
{
    struct
    {
        db_key_t *names;   /**< Column names                    */
        db_type_t *types;  /**< Column types                    */
        int n;             /**< Number of columns               */
    } col;
    struct db_row *rows;   /**< Rows                            */
    int n;                 /**< Number of rows in current fetch */
    int res_rows;          /**< Number of total rows in query   */
    int last_row;          /**< Last row                        */
} db_res_t;

static
int parse_json_to_result(char *json, db_res_t **result)
{
    printf("received json: %s\n", json);
    cJSON *root, *record;
    int recordCount = 0;
    int colCount = 0;
    int i = 0;
    int j = 0;
    int int_val = 0;
    char *str_val = '\0';

    root = cJSON_Parse(json);
    recordCount = cJSON_GetArraySize(root);
    *result = calloc(1, sizeof(db_res_t));
    (*result)->n = recordCount;

    // malloc for number of rows.
    (*result)->rows = calloc(recordCount, sizeof(db_row_t) );

    // this is done to get the count of columns only once.
    record = cJSON_GetArrayItem(root, i);
    colCount = cJSON_GetArraySize(record);
    printf("Record count = %d\n", recordCount);
    printf("colCount-1 = %d\n", colCount);

    for (i = 0; i < recordCount; i++)
    {
        printf("Allocating record %d\n", i);
        j = 0;
        record = cJSON_GetArrayItem(root, i);
        (*result)->rows[i].n = colCount;
        printf("colCount-2 = %d\n", colCount);
        // malloc for number of columns in each row.
        (*result)->rows[i].values = calloc(colCount, sizeof(db_val_t) );
        cJSON *subitem = record->child;
        while (subitem)
        {
            if (subitem->type == cJSON_Number)
            {
                int_val =
                    cJSON_GetObjectItem(record, subitem->string)->valueint;
                (*result)->rows[i].values[j].type = DB_INT;
                (*result)->rows[i].values[j].nul = 0;
                (*result)->rows[i].values[j++].val.int_val = int_val;
//              printf("%d\n", int_val);
            }
            else
            {
                str_val =
                    cJSON_GetObjectItem(record, subitem->string)->valuestring;
//              printf("%s\n", str_val);
                (*result)->rows[i].values[j].type = DB_STRING;
                if (strcmp(str_val, "") == 0)
                {
                    (*result)->rows[i].values[j].nul = 1;
                    (*result)->rows[i].values[j].free = 0;
                    (*result)->rows[i].values[j++].val.string_val = NULL;
                }
                else
                {
                    static int count = 0;
                    printf("Allocate %d: %s\n", ++count, str_val);
                    (*result)->rows[i].values[j].nul = 0;
                    (*result)->rows[i].values[j].free = 1;
                    (*result)->rows[i].values[j++].val.string_val = strdup(str_val);
                }
            }
            subitem = subitem->next;
        }
    }
    cJSON_Delete(root);
    return 1;
}

static
int free_result(db_res_t *_r)
{
    if (!_r)
    {
        return -1;
    }
    int i, row_count = 0;
    int col_count = 0;
    printf("freeing result set at %p\n", _r);
    row_count = _r->n;
    printf("RowCount %d .\n", row_count);
    for (i = 0; i < row_count; i++)
    {
        printf("Freeing %d row.\n", i);
        col_count = _r->rows[i].n;
        printf("col_count %d.\n", col_count);
        int j = 0;
        for (j = 0; j < col_count; j++)
        {
            if (_r->rows[i].values[j].type == DB_STRING && _r->rows[i].values[j].nul == 0)
            {
                printf("Freeing-1 %d col [%s]\n", j, _r->rows[i].values[j].val.string_val);
                free(_r->rows[i].values[j].val.string_val);
                _r->rows[i].values[j].val.string_val = NULL;
            }
            else if (_r->rows[i].values[j].type == DB_STR  && _r->rows[i].values[j].nul == 0)
            {
                printf("Freeing-2 %d col [%s]\n", j, _r->rows[i].values[j].val.string_val);
                free(_r->rows[i].values[j].val.str_val.s);
                _r->rows[i].values[j].val.str_val.s = NULL;
            }
        }
        // free all value colums for each row.
        free(_r->rows[i].values);
        _r->rows[i].values = NULL;
    }
    // free all rows
    free(_r->rows);
    _r->rows = NULL;
    // free resultset
    free(_r);
    _r = NULL;
    // this will print nil.
    printf("freed result set a %p\n", _r);
    return 0;
}

int main(void)
{
    db_res_t *result = NULL;
    char json[] =
            "[{\"id\":11,\"username\":\"microsip\",\"domain\":\"192.168.254.128\","
            "\"event\":\"presence\",\"etag\":\"a.1437194656.2922.1.0\",\"expires\":1437200355,"
            "\"received_time\":-1,\"body\":\"\",\"extra_hdrs\":\"\",\"sender\":\"\"},"
            "{\"id\":12,\"username\":\"microsip\",\"domain\":\"92.168.254.128\",\"event\":\"presence\","
            "\"etag\":\"a.1437194656.2922.1.0\",\"expires\":1437200355,\"received_time\":-1,"
            "\"body\":\"\",\"extra_hdrs\":\"\",\"sender\":\"\"}]";

    parse_json_to_result(json, &result);
    free_result(result);
    return 0;
}

代码的泄漏版本产生了如下输出:

received json: [{"id":11,"username":"microsip","domain":"192.168.254.128","event":"presence","etag":"a.1437194656.2922.1.0","expires":1437200355,"received_time":-1,"body":"","extra_hdrs":"","sender":""},{"id":12,"username":"microsip","domain":"92.168.254.128","event":"presence","etag":"a.1437194656.2922.1.0","expires":1437200355,"received_time":-1,"body":"","extra_hdrs":"","sender":""}]
Record count = 2
colCount-1 = 10
Allocating record 0
colCount-2 = 10
Allocate 1: microsip
Allocate 2: 192.168.254.128
Allocate 3: presence
Allocate 4: a.1437194656.2922.1.0
Allocating record 1
colCount-2 = 10
Allocate 5: microsip
Allocate 6: 92.168.254.128
Allocate 7: presence
Allocate 8: a.1437194656.2922.1.0
freeing result set at 0x10082b0a0
RowCount 2 .
Freeing 0 row.
col_count 10.
Freeing-1 1 col [microsip]
Freeing-1 2 col [192.168.254.128]
Freeing-1 3 col [presence]
Freeing-1 4 col [a.1437194656.2922.1.0]
Freeing 1 row.
col_count 0.
freed result set a 0x0

固定版本产生的输出如下:

received json: [{"id":11,"username":"microsip","domain":"192.168.254.128","event":"presence","etag":"a.1437194656.2922.1.0","expires":1437200355,"received_time":-1,"body":"","extra_hdrs":"","sender":""},{"id":12,"username":"microsip","domain":"92.168.254.128","event":"presence","etag":"a.1437194656.2922.1.0","expires":1437200355,"received_time":-1,"body":"","extra_hdrs":"","sender":""}]
Record count = 2
colCount-1 = 10
Allocating record 0
colCount-2 = 10
Allocate 1: microsip
Allocate 2: 192.168.254.128
Allocate 3: presence
Allocate 4: a.1437194656.2922.1.0
Allocating record 1
colCount-2 = 10
Allocate 5: microsip
Allocate 6: 92.168.254.128
Allocate 7: presence
Allocate 8: a.1437194656.2922.1.0
freeing result set at 0x10082b0a0
RowCount 2 .
Freeing 0 row.
col_count 10.
Freeing-1 1 col [microsip]
Freeing-1 2 col [192.168.254.128]
Freeing-1 3 col [presence]
Freeing-1 4 col [a.1437194656.2922.1.0]
Freeing 1 row.
col_count 10.
Freeing-1 1 col [microsip]
Freeing-1 2 col [92.168.254.128]
Freeing-1 3 col [presence]
Freeing-1 4 col [a.1437194656.2922.1.0]
freed result set a 0x0

如您所见,第2行的colCount在错误输出中是错误的(0而不是10)。诀窍是从那里向后工作,找出为什么价值被破坏,或者没有被设置,因为它结果。

顺便说一句,您应该警惕使用以下划线开头的_r之类的名称。它们基本上保留用于实现。 C的规则在§7.1.3保留标识符

中定义
  
      
  • 所有以下划线开头且以大写字母或其他下划线开头的标识符始终保留供任何使用。
  •   
  • 所有以下划线开头的标识符始终保留用作普通和标记名称空间中具有文件范围的标识符。
  •