正确使用 strcpy() 并避免在 valgrind 中出现读写大小错误
Correct use of strcpy() and avoid read and write size errors in valgrind
我有这个代码:
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);
}
我必须复制string1
和string2
来修改他们的副本并维护原件。这做了它应该做的并且编译没有错误,但是当我 运行:
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()
创建这 2 个副本的地方以及我用 strstr()
阅读它们的地方。
有没有办法避免这种情况,或者我不应该在这里使用 strcpy()
?我传递的字符串的 strlen(string)
大小不正确吗?
您的分配出现了经典的 Off-By-One 问题。 C 中的字符串总是以 空字符 结束。这就是 字符串 与 字符数组 的区别。要为源字符串 src
的副本正确分配存储空间,您必须分配 strlen(src) + 1
字节。
您的 foo
功能毫无意义。在 foo
内,您分配存储空间,例如char *string1_copy= malloc(strlen(string1));
但无法让您的程序在函数 return 之后使用分配的内存。一个void
函数returns没有任何值消除任何确定success/failure的副本,并且没有额外的指针-to-pointer 参数提供任何方式来更新原始地址的指针。此外,在分配存储后,函数 returns 和您丢失了保存每个分配起始地址的指针,从而造成 内存泄漏 .
当想要复制两个字符串时,编写一个复制两个字符串的函数是没有意义的。该一次性功能几乎没有可重用性。相反,只需编写一个复制单个字符串的函数,提供一个 有意义的 return 以允许确定 success/failure 和然后为每个需要复制的字符串调用该函数一次。
重构您的函数不仅可以让您充分验证每个分配,而且可以随时重用你需要复制一个字符串。事实上,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
字节以提供足够的存储空间 验证 分配并在失败时提供错误并returning NULL
。然后该函数使用 memcpy()
return 指向 dest
的指针将 src
复制到目标字符串 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 */
}
}
}
例子Use/Output
$ ./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
内存Use/Error检查
在您编写的任何动态分配内存的代码中,您对分配的任何内存块负有 2 责任:(1) 始终保留指向内存块的起始地址 因此,(2) 当不再需要它时可以释放。
您必须使用内存错误检查程序来确保您不会尝试访问内存或写入 beyond/outside 您分配的块的边界,尝试读取或基于未初始化的条件跳转值,最后,确认您释放了所有已分配的内存。
对于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)
始终确认您已释放所有分配的内存并且没有内存错误。
检查一下,如果您还有其他问题,请告诉我。
我有这个代码:
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);
}
我必须复制string1
和string2
来修改他们的副本并维护原件。这做了它应该做的并且编译没有错误,但是当我 运行:
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()
创建这 2 个副本的地方以及我用 strstr()
阅读它们的地方。
有没有办法避免这种情况,或者我不应该在这里使用 strcpy()
?我传递的字符串的 strlen(string)
大小不正确吗?
您的分配出现了经典的 Off-By-One 问题。 C 中的字符串总是以 空字符 结束。这就是 字符串 与 字符数组 的区别。要为源字符串 src
的副本正确分配存储空间,您必须分配 strlen(src) + 1
字节。
您的 foo
功能毫无意义。在 foo
内,您分配存储空间,例如char *string1_copy= malloc(strlen(string1));
但无法让您的程序在函数 return 之后使用分配的内存。一个void
函数returns没有任何值消除任何确定success/failure的副本,并且没有额外的指针-to-pointer 参数提供任何方式来更新原始地址的指针。此外,在分配存储后,函数 returns 和您丢失了保存每个分配起始地址的指针,从而造成 内存泄漏 .
当想要复制两个字符串时,编写一个复制两个字符串的函数是没有意义的。该一次性功能几乎没有可重用性。相反,只需编写一个复制单个字符串的函数,提供一个 有意义的 return 以允许确定 success/failure 和然后为每个需要复制的字符串调用该函数一次。
重构您的函数不仅可以让您充分验证每个分配,而且可以随时重用你需要复制一个字符串。事实上,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
字节以提供足够的存储空间 验证 分配并在失败时提供错误并returning NULL
。然后该函数使用 memcpy()
return 指向 dest
src
复制到目标字符串 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 */
}
}
}
例子Use/Output
$ ./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
内存Use/Error检查
在您编写的任何动态分配内存的代码中,您对分配的任何内存块负有 2 责任:(1) 始终保留指向内存块的起始地址 因此,(2) 当不再需要它时可以释放。
您必须使用内存错误检查程序来确保您不会尝试访问内存或写入 beyond/outside 您分配的块的边界,尝试读取或基于未初始化的条件跳转值,最后,确认您释放了所有已分配的内存。
对于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)
始终确认您已释放所有分配的内存并且没有内存错误。
检查一下,如果您还有其他问题,请告诉我。