C 编程 - 释放一个用单引号填充的 char ** 和用双引号填充的区别 - valgrind

Posted

技术标签:

【中文标题】C 编程 - 释放一个用单引号填充的 char ** 和用双引号填充的区别 - valgrind【英文标题】:C programming - difference between freeing a char ** filled with simple quotes and filled with double quotes - valgrind 【发布时间】:2017-11-21 17:09:52 【问题描述】:

我正在尝试用这个程序测试免费功能

  char **tab;

  // allocation
  tab = malloc(5 * sizeof(char*));
  for(int j=0 ; j<5 ; j++)
    
      tab[j] = malloc(4 * sizeof(char));
    

  // FILL TAB FIRST WAY
  /*
  for(int j=0 ; j<5 ; j++)
    
      for(int i=0 ; i<4 ; i++)
        
          tab[j][i] = '@';
        
    
  */

  // FILL TAB SECOND WAY
  for(int j=0 ; j<5 ; j++)
    
      tab[j] = "@@@@";
    

  //free
  for(int j=0 ; j<5 ; j++)
    
      free(tab[j]);
    

  free(tab);

使用 valgrind 填充制表符的第一种方式(每个字符单独)不会返回内存错误,而第二种方式(逐行填充制表符)会返回一些内存错误。

HEAP SUMMARY:
==447==     in use at exit: 20 bytes in 5 blocks
==447==   total heap usage: 6 allocs, 6 frees, 60 bytes allocated
==447==
==447== Searching for pointers to 5 not-freed blocks
==447== Checked 64,648 bytes
==447==
==447== LEAK SUMMARY:
==447==    definitely lost: 20 bytes in 5 blocks
==447==    indirectly lost: 0 bytes in 0 blocks
==447==      possibly lost: 0 bytes in 0 blocks
==447==    still reachable: 0 bytes in 0 blocks
==447==         suppressed: 0 bytes in 0 blocks
==447== Rerun with --leak-check=full to see details of leaked memory
==447==
==447== ERROR SUMMARY: 5 errors from 1 contexts (suppressed: 0 from 0)
==447==
==447== 5 errors in context 1 of 1:
==447== Invalid free() / delete / delete[] / realloc()
==447==    at 0x4C2EDEB: free (in /usr/lib/valgrind/vgpreload_memcheck-
amd64-linux.so)
==447==    by 0x400607: main (test_malloc.c:35)

对此有何解释?

【问题讨论】:

tab[j] = "@@@@";你知道你在这里做什么吗? 另外,您正在尝试将 5 个字符(“@@@@”和一个 NULL 终止符)放入 4 个字符的内存块中。 你知道你在这里做什么吗?事实上并非如此,我对代码背后发生的事情缺乏了解 【参考方案1】:

对于第二种情况,您已使用以下方式分配内存:

tab[j] = malloc(4 * sizeof(char));

然后用"@@@@"(字符串文字)的地址覆盖指针:

tab[j] = "@@@@";

所以你刚刚:

    丢失了malloced 内存(您的LEAK SUMMARY 状态)。 尝试free() 字符串文字(您的ERROR SUMMARY 状态)。

如果你想正确地将"@@@@"复制到内存中,你可以使用strncpy()

strncpy(tab[j], "@@@@", 4);

【讨论】:

【参考方案2】:

你不能做tab[j] = "@@@@"。它根本无效。或者至少,它并没有按照你的想法去做。

正确的方法可能是strncpy(tab[j], "@@@@", 4)

【讨论】:

【参考方案3】:

这是为 tab[j] 分配 4 个字节的内存,足够容纳 3 个字符加上 NUL 终止符

tab[j] = malloc(4 * sizeof(char));

这一行将tab[j] 的值替换为尚未分配给malloc 的字符串。您的 free 几行之后不会释放您分配的内存,并且会知道什么,因为它正在传递一些未分配的东西。

tab[j] = "@@@@";

第二行应该使用strcpy,除了答案顶部提到的,你只为3个字符分配空间,在这里,你试图存储4个。

【讨论】:

【参考方案4】:

当你这样做时

tab[j] = "@@@@";

你覆盖了你之前所做的分配

tab[j] = malloc(4 * sizeof(char));

字符串"@@@@" 不是由您的代码分配的,也不是由malloc() 分配的,这是一个静态区域中的地址,不能用free() 释放。

释放这样的地址是未定义的行为。

这与您的代码无关(因为分配的地址被覆盖),但 malloc() 分配应该是 5 个字节,以应付 "@@@@" 的最终 '\0'

【讨论】:

【参考方案5】:

以下声明有区别

char s[4] = "@@@@";

and(也可以使用赋值表达式代替声明)

char *s = "@@@@";

在第一种情况下,字符串文字"@@@@" 的元素用于初始化数组的元素。所以第一个声明等价于

char s[4] =  '@', '@', '@', '@' ;

在第二种情况下,创建了一个具有静态存储持续时间的字符串文字,其类型为char[5](由于附加的终止零),并将字符串文字的第一个字符的地址分配给指针@987654326 @。

你可以想象这个声明(或assignmnet表达式) 如下方式

static char unnamed_string_literal[5] =  '@', '@', '@', '@', '\0' ;
char *s = &unnamed_string_literal[0];

char *s;
static char unnamed_string_literal[5] =  '@', '@', '@', '@', '\0' ;
s = &unnamed_string_literal[0];

来自 C 标准(6.4.5 字符串文字)

6 在翻译阶段 7,一个字节或值为零的代码被附加到每个由一个或多个字符串文字产生的多字节字符序列。78) 然后使用多字节字符序列来初始化 一个静态数组存储持续时间 和长度刚好足以包含序列。对于字符串字面量,数组元素的类型为 char,并使用多字节字符序列的各个字节进行初始化......

所以在这段代码中sn-p

  for(int i=0 ; i<4 ; i++)
    
      tab[j][i] = '@';`
    

你用字符'@'“手动”填充了分配的内存。

但是在这段代码中,sn-p 作为表达式tab[j] 的类型为char *

for(int j=0 ; j<5 ; j++)

  tab[j] = "@@@@";

然后你用字符串字面量或字面量"@@@@" 的地址覆盖了作为分配内存地址的指针的先前值。

这会导致内存泄漏,因为已分配内存的地址丢失,并且您可能无法将函数 free 应用于现在指向内存的指针,其中静态存储持续时间由字符串文字或文字占用(取决于关于编译器如何存储彼此相等的字符串文字)。

【讨论】:

谢谢您的解释。我更了解代码背后发生了什么。

以上是关于C 编程 - 释放一个用单引号填充的 char ** 和用双引号填充的区别 - valgrind的主要内容,如果未能解决你的问题,请参考以下文章

C语言基础学习基本数据类型-Char类型

C#里 用字符串怎么输出双引号“

PHP编程效率的20个要点

PHP编程效率的20个要点

当我用单引号将字符串括起来时,C++ std::cout 打印奇怪的字符[重复]

c语言中单引号,双引号,ASCⅡ码的具体用法