c - 即使没有内存泄漏,Valgrind 也会出现无效读取错误

标签 c memory memory-leaks valgrind

所以我在“main.c”中有类似的东西

#include "foo.h"
#include <stdio.h>
#include <stdlib.h>

typedef struct myStruct
{
    int dummyMember1;
    float dummyMember2;
} myStruct_t;

int main()
{
    myStruct_t* a = malloc(sizeof(myStruct_t));
    a->dummyMember1 = 10;
    a->dummyMember2 = 5.5;
    push(a);

    myStruct_t* b = pop();
    if(b != NULL)
    {
        b->dummyMember1;
        b->dummyMember2;
    }

    free(b);
    return 0;
}

然后我在某个文件“foo.h”中有类似的东西

void push(void* item);
void* pop();

在“foo.c”中我有这样的东西

#include "foo.h"
#include <stdlib.h>
#include <string.h>

#define ARR_SIZE 50

void* arr[ARR_SIZE];

int free_space = ARR_SIZE;

void push(void* item)
{
    if(free_space > 0){
        arr[ARR_SIZE - free_space] = item;
        free_space--;
    }
    return;
}

void* pop()
{
    void* p_ret = NULL;
    if(free_space < ARR_SIZE)
    {
        void* p = arr[ARR_SIZE-(free_space+1)];
        p_ret = malloc(sizeof(*p));
        memcpy(p_ret, p, sizeof(*p));
        free(p);
        free_space++;
    }
    return p_ret;
}

此代码编译并运行时没有段错误,但问题是 valgrind 报告无效读取错误(即使它显示“所有堆 block 已释放 - 不可能发生泄漏”)。

我的问题是,是什么导致 valgrind 报告无效读取错误(全部引用 pop() 函数)?

最佳答案

您在 push()pop() 中的代码有点复杂。获取您的代码并进行最少的必要更改以在我首选的编译器选项下进行编译,然后在 valgrind 下运行它,我收到警告:

==59504== Invalid read of size 4
==59504==    at 0x100000EBF: main (main.c:21)
==59504==  Address 0x100aa74e4 is 3 bytes after a block of size 1 alloc'd
==59504==    at 0x100007E81: malloc (vg_replace_malloc.c:302)
==59504==    by 0x100000E50: pop (foo.c:26)
==59504==    by 0x100000EA4: main (main.c:18)

问题出在你的 pop() 函数上;您正在尝试分配 sizeof(*p) 字节,其中 pvoid *,因此 *p 是a void,没有大小。 GCC(在我看来毫无帮助)将其视为对 1 字节的请求,而不是给出应该给出的错误。您必须请求 -pedantic 警告才能获取消息:

$ gcc -O3 -g -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes \
>     -Wold-style-definition -Werror -pedantic -c foo.c
foo.c: In function ‘pop’:
foo.c:26:30: error: invalid application of ‘sizeof’ to a void type [-Werror=pointer-arith]
         p_ret = malloc(sizeof(*p));
                              ^
In file included from /usr/include/string.h:186:0,
                 from foo.c:3:
foo.c:27:32: error: invalid application of ‘sizeof’ to a void type [-Werror=pointer-arith]
         memcpy(p_ret, p, sizeof(*p));
cc1: all warnings being treated as errors
$

由于您将 void * 值存储在固定数组中,因此不需要在堆栈中进行任何动态内存分配。您可以使用:

#include "foo.h"

enum { ARR_SIZE = 50 };

static void *arr[ARR_SIZE];
static int   free_space = ARR_SIZE;

void push(void *item)
{
    if (free_space > 0)
    {
        arr[ARR_SIZE - free_space] = item;
        free_space--;
    }
}

void *pop(void)
{
    void *p_ret = 0;
    if (free_space < ARR_SIZE)
    {
        p_ret = arr[ARR_SIZE - (free_space + 1)];
        free_space++;
    }
    return p_ret;
}

修改后的测试程序:

#include "foo.h"
#include <stdio.h>
#include <stdlib.h>

typedef struct myStruct
{
    int dummyMember1;
    float dummyMember2;
} myStruct_t;

int main(void)
{
    for (int i = 0; i < 3; i++)
    {
        myStruct_t *a = malloc(sizeof(myStruct_t));
        a->dummyMember1 = 10 + i;
        a->dummyMember2 = 5.5 * i;
        push(a);

        myStruct_t *b = pop();
        if (b != NULL)
        {
            printf("Popped: %d and %f\n", b->dummyMember1, b->dummyMember2);
        }

        free(a);
    }
    return 0;
}

我得到 valgrind 输出,例如:

==59611== Memcheck, a memory error detector
==59611== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==59611== Using Valgrind-3.12.0.SVN and LibVEX; rerun with -h for copyright info
==59611== Command: foo
==59611== 
--59611-- run: /usr/bin/dsymutil "./foo"
Popped: 10 and 0.000000
Popped: 11 and 5.500000
Popped: 12 and 11.000000
==59611== 
==59611== HEAP SUMMARY:
==59611==     in use at exit: 26,437 bytes in 189 blocks
==59611==   total heap usage: 274 allocs, 85 frees, 32,661 bytes allocated
==59611== 
==59611== LEAK SUMMARY:
==59611==    definitely lost: 80 bytes in 1 blocks
==59611==    indirectly lost: 68 bytes in 2 blocks
==59611==      possibly lost: 0 bytes in 0 blocks
==59611==    still reachable: 0 bytes in 0 blocks
==59611==         suppressed: 26,289 bytes in 186 blocks
==59611== Rerun with --leak-check=full to see details of leaked memory
==59611== 
==59611== For counts of detected and suppressed errors, rerun with: -v
==59611== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

看起来很糟糕——失去了内存。但使用 --leak-check=full 运行会产生:

==59641== 148 (80 direct, 68 indirect) bytes in 1 blocks are definitely lost in loss record 43 of 66
==59641==    at 0x100007E81: malloc (vg_replace_malloc.c:302)
==59641==    by 0x1001E58D6: __Balloc_D2A (in /usr/lib/system/libsystem_c.dylib)
==59641==    by 0x1001E2553: __rv_alloc_D2A (in /usr/lib/system/libsystem_c.dylib)
==59641==    by 0x1001E2574: __nrv_alloc_D2A (in /usr/lib/system/libsystem_c.dylib)
==59641==    by 0x10020B3E6: __vfprintf (in /usr/lib/system/libsystem_c.dylib)
==59641==    by 0x1002346C8: __v2printf (in /usr/lib/system/libsystem_c.dylib)
==59641==    by 0x10020A389: vfprintf_l (in /usr/lib/system/libsystem_c.dylib)
==59641==    by 0x100208223: printf (in /usr/lib/system/libsystem_c.dylib)
==59641==    by 0x100000EA4: main (main.c:23)

我尝试添加fclose(stdout),但这并没有解决问题。这个问题深埋在本地 printf() 函数内部,完全超出了你我的控制范围。我需要添加一个抑制。 Mac OS X 往往有相当多的此类泄漏 — 见证已经就地的抑制(180 多个分配中几乎 27 KiB)。

<小时/>

如果您确实必须在堆栈中使用内存分配,则需要相当仔细地考虑将哪些内容压入堆栈以及从堆栈中弹出哪些内容。您可能需要知道要存储的对象的大小。然后,您将证明堆栈不会因为修改压入堆栈的值而受到影响,因为堆栈复制了该值。虽然您只是堆叠指针,但只有当当前数组太小时堆栈增长时才需要动态内存分配。

关于c - 即使没有内存泄漏,Valgrind 也会出现无效读取错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35979835/

相关文章:

c - Firebase 使用哪种加密协议(protocol)?

python - Django 上的多个站点

ios - 内存泄漏: AVplayerViewController

php - 在 PHP 中使用静态方法和属性会占用更少的内存吗?

调用(): Do the individual values matter for performance?

c - 合并排序实现中的段错误

java - 使用什么代替 postDelayed 来节省内存

java - C# 相当于 Java 内存映射方法

javascript - 该模式如何获得循环引用

c - 如何检查array1中的所有元素是否都在array2中?