C - 在函数中重新分配指针类型的结构成员变量。 Valgrind 报告 "Invalid read/write of size 1"

C - realloc a structs member variable of type pointer in a function. Valgrind reports "Invalid read/write of size 1"

我的问题与生成的可执行文件上的 运行 valgrind against the following unit test below. The unit test passes when running make clean all check, however when running valgrind 有关 我收到以下形式的错误:

Running suite(s): File System
==18845== Invalid read of size 1
==18845==    at 0x4C2B8C4: strcat (vg_replace_strmem.c:303)
==18845==    by 0x4E35E5F: filename_app (filename.c:196)
==18845==    by 0x401871: test_filename_app (check_file_system.c:157)
==18845==    by 0x503D78A: ??? (in /usr/lib64/libcheck.so.0.0.0)
==18845==    by 0x503DB7C: srunner_run (in /usr/lib64/libcheck.so.0.0.0)
==18845==    by 0x401ACE: main (check_file_system.c:196)
==18845==  Address 0x6154900 is 0 bytes inside a block of size 5 free'd
==18845==    at 0x4C2AC9B: realloc (vg_replace_malloc.c:785)
==18845==    by 0x4E35E0B: filename_app (filename.c:187)
==18845==    by 0x401871: test_filename_app (check_file_system.c:157)
==18845==    by 0x503D78A: ??? (in /usr/lib64/libcheck.so.0.0.0)
==18845==    by 0x503DB7C: srunner_run (in /usr/lib64/libcheck.so.0.0.0)
==18845==    by 0x401ACE: main (check_file_system.c:196)
==18845==  Block was alloc'd at
==18845==    at 0x4C28D06: malloc (vg_replace_malloc.c:299)
==18845==    by 0x52CC899: strdup (in /usr/lib64/libc-2.22.so)
==18845==    by 0x4E35A31: filename_create (filename.c:44)
==18845==    by 0x401815: test_filename_app (check_file_system.c:153)
==18845==    by 0x503D78A: ??? (in /usr/lib64/libcheck.so.0.0.0)
==18845==    by 0x503DB7C: srunner_run (in /usr/lib64/libcheck.so.0.0.0)
==18845==    by 0x401ACE: main (check_file_system.c:196)
...
=18845== Invalid write of size 1
==18845==    at 0x4C2B8FF: strcat (vg_replace_strmem.c:303)
==18845==    by 0x4E35E5F: filename_app (filename.c:196)
==18845==    by 0x401871: test_filename_app (check_file_system.c:157)
==18845==    by 0x503D78A: ??? (in /usr/lib64/libcheck.so.0.0.0)
==18845==    by 0x503DB7C: srunner_run (in /usr/lib64/libcheck.so.0.0.0)
==18845==    by 0x401ACE: main (check_file_system.c:196)
==18845==  Address 0x6154908 is 3 bytes after a block of size 5 free'd
==18845==    at 0x4C2AC9B: realloc (vg_replace_malloc.c:785)
==18845==    by 0x4E35E0B: filename_app (filename.c:187)
==18845==    by 0x401871: test_filename_app (check_file_system.c:157)
==18845==    by 0x503D78A: ??? (in /usr/lib64/libcheck.so.0.0.0)
==18845==    by 0x503DB7C: srunner_run (in /usr/lib64/libcheck.so.0.0.0)
==18845==    by 0x401ACE: main (check_file_system.c:196)
==18845==  Block was alloc'd at
==18845==    at 0x4C28D06: malloc (vg_replace_malloc.c:299)
==18845==    by 0x52CC899: strdup (in /usr/lib64/libc-2.22.so)
==18845==    by 0x4E35A31: filename_create (filename.c:44)
==18845==    by 0x401815: test_filename_app (check_file_system.c:153)
==18845==    by 0x503D78A: ??? (in /usr/lib64/libcheck.so.0.0.0)
==18845==    by 0x503DB7C: srunner_run (in /usr/lib64/libcheck.so.0.0.0)
==18845==    by 0x401ACE: main (check_file_system.c:196)
...
100%: Checks: 6, Failures: 0, Errors: 0
==18845== 
==18845== HEAP SUMMARY:
==18845==     in use at exit: 0 bytes in 0 blocks
==18845==   total heap usage: 222 allocs, 223 frees, 62,433 bytes allocated
==18845== 
==18845== All heap blocks were freed -- no leaks are possible
==18845== 
==18845== For counts of detected and suppressed errors, rerun with: -v
==18845== ERROR SUMMARY: 20 errors from 7 contexts (suppressed: 0 from 0)

我的单元测试采用以下形式:

START_TEST(test_filename_app)
{
  filename_t file;

  // Just to check for crashes. No actual unit test.
  filename_app(NULL, "frog");

  file = filename_create("frog");

  filename_app(&file, NULL);
  ck_assert_str_eq("frog", file.name);
  filename_app(&file, ".ext");
  ck_assert_str_eq("frog.ext", file.name);

  filename_free(&file);
}
END_TEST

如果我注释掉 filename_app(&file, ".ext"); 及其相关测试,valgrind 错误就会消失。被测试的函数,传入"all forms"的是:

typedef struct {
  char *name;
} filename_t;

void filename_app(filename_t *name, const char *app)
{
  void *tmp = NULL;

  if (name == NULL || name->name == NULL) {
    return;
  }

  if (app == NULL) {
    return;
  }

  size_t name_bytes_cnt = strlen(name->name);
  size_t app_bytes_cnt = strlen(app);
  size_t new_bytes_cnt = name_bytes_cnt + app_bytes_cnt + sizeof(char);

  errno = 0;
  tmp = realloc((void *)name->name, new_bytes_cnt);
  if (errno == ENOMEM || tmp == NULL) {
    free(tmp);
    return;
  }
  if (tmp != name->name) {
    free(tmp);
  }

  strcat(name->name, app);
}

我做错了什么得到一个额外的免费和无效 read/write?

假设您需要该结构,您进行的检查可能更直接:

typedef struct { char *name; } filename_t;

void filename_app(filename_t *name, const char *app)
{
  if (!app || !name || !name->name ) return;

  size_t name_bytes_cnt = strlen(name->name), 
          app_bytes_cnt = strlen(app),
          new_bytes_cnt = name_bytes_cnt + app_bytes_cnt + sizeof(char);

  void* tmp = realloc((void *)name->name, new_bytes_cnt);

这个:

if (tmp != name->name) {
    free(tmp);
  }

  strcat(name->name, app);
}

没有意义。 tmp 将等于 name->name 如果 realloc 在没有移动的情况下成功,但如果 移动成功,则 strcat name->name 未定义(name->name 将是 freed 内存(freed by realloc))。

你的问题是

tmp = realloc((void *)name->name, new_bytes_cnt);
if (errno == ENOMEM || tmp == NULL) {
    free(tmp);
    return;
 }
 if (tmp != name->name) {
    free(tmp);
 }

如果 realloc() 成功并且实际执行了调整大小,则 tmp != name->name 将为真。然后您的代码释放新分配的块,而不是调整大小之前的块。

如果realloc()失败,tmp将是NULL,所以没有必要传给free()

此外,如果 realloc() 失败,并非所有实现都将 errno 设置为 ENOMEM(这是 POSIX 的事情,而不是 C 标准的事情)。如果 realloc() 成功,那些并不总是改变 errno - 所以被测试的 errno 的值可能是其他一些(以前在你的程序中执行的)代码的结果,不相关到你的功能。

您也没有说明函数的调用者希望发生什么。如果 realloc() 失败,调用者是否希望保留旧值?