正确使用strcpy()并避免valgrind中的读写大小错误

时间:2019-11-16 03:04:07

标签: c malloc valgrind strcpy

我有此代码:

static void foo(char *string1, char *string2)
{   
    char *string1_copy= malloc(strlen(string1));
    strcpy(string1_copy, haystack);

    char *string2_copy = malloc(strlen(string2));
    strcpy(string2_copy, needle);
}

我必须复制string1string2才能修改其副本并保留原件。这会执行应有的工作,并且可以编译而不会出现错误,但是在我运行时:

valgrind --leak-check=full -v ./myProgram

我明白了:

==20595== ERROR SUMMARY: 3 errors from 3 contexts (suppressed: 0 from 0)
==20595== 
==20595== 1 errors in context 1 of 3:
==20595== Invalid read of size 1
==20595==    at 0x4C376F4: strstr (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==20595==    by 0x108CED: grep (myProgram.c:87)
==20595==    by 0x109023: main (myProgram.c:214)
==20595==  Address 0x522e3b3 is 0 bytes after a block of size 3 alloc'd
==20595==    at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==20595==    by 0x108CA5: grep (myProgram.c:77)
==20595==    by 0x109023: main (myProgram.c:214)
==20595== 
==20595== 
==20595== 1 errors in context 2 of 3:
==20595== Invalid write of size 1
==20595==    at 0x4C32E0D: strcpy (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==20595==    by 0x108CBC: grep (myProgram.c:78)
==20595==    by 0x109023: main (myProgram.c:214)
==20595==  Address 0x522e3b3 is 0 bytes after a block of size 3 alloc'd
==20595==    at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==20595==    by 0x108CA5: grep (myProgram.c:77)
==20595==    by 0x109023: main (myProgram.c:214)
==20595== 
==20595== 
==20595== 1 errors in context 3 of 3:
==20595== Invalid write of size 1
==20595==    at 0x4C32E0D: strcpy (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==20595==    by 0x108C91: grep (myProgram.c:75)
==20595==    by 0x109023: main (myProgram.c:214)
==20595==  Address 0x522e362 is 0 bytes after a block of size 18 alloc'd
==20595==    at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==20595==    by 0x108C7A: grep (myProgram.c:74)
==20595==    by 0x109023: main (myProgram.c:214)
==20595== 
==20595== ERROR SUMMARY: 3 errors from 3 contexts (suppressed: 0 from 0)

这正是我使用strcpy()创建这两个副本以及使用strstr()阅读它们的地方。 有办法避免这种情况吗?还是我不应该在这里使用strcpy()?我传递的字符串strlen(string)的大小不正确吗?

1 个答案:

答案 0 :(得分:1)

您的分配过程中会出现经典的一次性发行问题。 C中的字符串始终以 nul-character 终止。这就是字符串字符数组的区别。要为源字符串src的副本正确分配存储空间,必须分配strlen(src) + 1个字节。

您的foo函数毫无意义。在foo中,您可以分配存储空间,例如char *string1_copy= malloc(strlen(string1));,但无法提供函数返回后程序可以利用分配的内存的方式。 void函数不返回任何值来消除确定副本成功或失败的任何方法,并且没有额外的指针到指针参数来提供任何内容在原始地址更新指针的方法。此外,在分配存储空间之后,该函数将返回,并且您丢失了保存每个分配的起始地址的指针,从而造成了内存泄漏

当查找两个字符串重复时,编写一个同时复制两个字符串的函数没有任何意义。该一次性功能几乎没有可重用性。相反,只需编写一个复制单个字符串的函数,提供一个有意义的回报,从而确定成功/失败,然后为您需要复制的每个字符串调用一次该函数。

对函数的重构不仅使您能够充分 验证每个分配 ,而且在您需要重复任何字符串时都可以重用。实际上,POSIX提供了一个strdup()函数,该函数可以执行此操作,但是您可以轻松编写自己的函数以确保严格遵守C标准。

此类功能的合理实现可以写为:

/* returns pointer to allocated copy of src, or NULL on failure */
char *dupstr (const char *src)
{
    size_t len = strlen (src);              /* get length of src */
    char *dest = malloc (len + 1);          /* allocate length + 1 bytes */

    if (!dest) {    /* validate EVERY allocation */
        perror ("dupstr() malloc-dest");
        return NULL;
    }

    return memcpy (dest, src, len + 1);     /* copy src to dest, return ptr */
}

注意:,您还可以在if (!src)上添加检查以确保传递的指针不是NULL -留给您)

这是一个简单的函数,它获取原始字符串的长度(作为const char*传递),然后分配len + 1个字节以提供足够的存储空间 验证 < / strong>分配,并在失败时提供错误并返回NULL。然后,该函数使用srcdest复制到目标字符串memcpy()并返回指向dest的指针

注意:,无需使用strcpy()。此时,您已经计算出src的长度,也无需扫描strcpy()>字符串结束。

复制并输出所有程序参数的简单实现可以是:

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

/* returns pointer to allocated copy of src, or NULL on failure */
char *dupstr (const char *src)
{
    size_t len = strlen (src);              /* get length of src */
    char *dest = malloc (len + 1);          /* allocate length + 1 bytes */

    if (!dest) {    /* validate EVERY allocation */
        perror ("dupstr() malloc-dest");
        return NULL;
    }

    return memcpy (dest, src, len + 1);     /* copy src to dest, return ptr */
}

int main (int argc, char **argv) {

    char *copies[argc];                     /* VLA of argc pointers to char */

    for (int i = 0; i < argc; i++) {            /* loop over each argument */
        if ((copies[i] = dupstr (argv[i]))) {   /* duplicate in copies[i] */
            puts (copies[i]);                   /* output copy */
            free (copies[i]);                   /* free copy */
        }
    }
}

使用/输出示例

$ ./bin/dupstr my dog has fleas and my cat has none - lucky cat
./bin/dupstr
my
dog
has
fleas
and
my
cat
has
none
-
lucky
cat

内存使用/错误检查

在您编写的任何动态分配内存的代码中,对于任何分配的内存块,您都有2个职责:(1)始终保留指向起始地址的指针因此,(2)当不再需要它时可以释放

当务之急是使用一个内存错误检查程序来确保您不尝试访问内存或不在分配的块的边界之外/之外写,尝试读取或基于未初始化的值进行条件跳转,最后,以确认您释放了已分配的所有内存。

对于Linux,valgrind是正常选择。每个平台都有类似的内存检查器。它们都很容易使用,只需通过它运行程序即可。

$ valgrind ./bin/dupstr my dog has fleas and my cat has none - lucky cat
==6014== Memcheck, a memory error detector
==6014== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==6014== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==6014== Command: ./bin/dupstr my dog has fleas and my cat has none - lucky cat
==6014==
./bin/dupstr
my
dog
has
fleas
and
my
cat
has
none
-
lucky
cat
==6014==
==6014== HEAP SUMMARY:
==6014==     in use at exit: 0 bytes in 0 blocks
==6014==   total heap usage: 14 allocs, 14 frees, 1,086 bytes allocated
==6014==
==6014== All heap blocks were freed -- no leaks are possible
==6014==
==6014== For counts of detected and suppressed errors, rerun with: -v
==6014== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

始终确认已释放已分配的所有内存,并且没有内存错误。

仔细检查一下,如果还有其他问题,请告诉我。