无法从结构数组中释放内存,valgrind 显示内存泄漏

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

我有一个嵌套结构,我正在为其动态分配内存并创建数组。所以,它就像一个结构有行,行有列,两者都是结构。

首先我为主结构分配内存,然后根据输入行为行结构*(行数)分配内存,然后遍历行并为列数分配内存。

赋值代码:

    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 = '[=10=]';

        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;
    }

结构体:

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;

释放内存的代码:

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;
}

我的示例输入是 2 行,每行 10 列,其中只有几列是 char*。所以在免费的情况下,我希望输出有点像:

正在释放 0 行。 col_count 10。 释放 1 列。 释放 2 列。 释放 3 列。 释放 4 列。 释放 1 行。 col_count 10。 释放 1 列。 释放 2 列。 释放 3 列。 释放 4 列。 释放结果集 a (nil)

但我实际得到的是:

释放结果集在 0x18e13e0 行数 3。 释放 0 行。 col_count 10。 释放 1 列。 释放 2 列。 释放 3 列。 释放 4 列。 释放 1 行。 col_count 0 。 正在释放 2 行。 col_count 0 . 释放结果集 a (nil)

循环没有超出第一行。我认为所有的值都在第一次通过时被取消引用。但是怎么办?我无法理解。我能够访问每一行和每一列的所有值。所以,可能循环 free_result 有一些问题。

主要

    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);
    }

valgrind 日志:

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)

抱歉拖了这么久 post 但我已尽力提供所有信息。我在这个问题上停留了很长时间,似乎没有找到问题所在。

编辑

我很抱歉外部库,但它是一个单独的 c 文件,必须与 -lm 选项一起编译。 Link到它https://github.com/kbranigan/cJSON

gcc -o Test.c cJSON.c -lm

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

(*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 = '[=14=]';

    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 保留标识符:

中定义
  • All identifiers that begin with an underscore and either an uppercase letter or another underscore are always reserved for any use.
  • All identifiers that begin with an underscore are always reserved for use as identifiers with file scope in both the ordinary and tag name spaces.