【问题标题】:Memory leak in a join string function连接字符串函数中的内存泄漏
【发布时间】:2021-01-07 20:18:19
【问题描述】:

我正在尝试创建一个函数,将 2 个字符串(str1 和 str2)连接成一个由 char 分隔符分隔的新字符串(str3)。不幸的是,我的这个函数发生了内存泄漏,我真的不知道为什么,因为我最后释放了 str3。

示例:str_join_string("ABC","DEF",'|') ---> "ABC|DEF"

代码如下:

char *str_join_string(const char *str1, const char *str2, char separator) {
  char *str3;
  size_t len = str_length(str1)+ str_length(str2)+1;
  size_t i = 0;
  size_t j = 0;

  str3 = (char * )calloc(len, sizeof(char));
  if(str3 == NULL){
    printf("Impossible d'allouer la mémoire");
    return NULL;
  }
  

  while(str1[i] != '\0' && str1 != NULL){
    str3[i] = str1[i];
    i++;
  }
  str3[i] = separator;
  i+=1;

  while(str2[j] != '\0' && str2 != NULL){
    str3[i+j] = str2[j];
    j++;
  }
  str3[len] = '\0';
  
  return str3;
} 

我要补充一点,我不能使用 strcat() 之类的任何函数或来自 string.h 的任何函数。

Valgrind 展示的内容:

==4300== Searching for pointers to 3 not-freed blocks
==4300== Checked 131,560 bytes
==4300== 
==4300== 4 bytes in 1 blocks are definitely lost in loss record 1 of 3
==4300==    at 0x4C31B25: calloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==4300==    by 0x13E3B2: str_join_string (stringslib.c:238)
==4300==    by 0x13E545: str_join_array (stringslib.c:283)
==4300==    by 0x137065: JoinArrayTest_OneEmpty_Test::TestBody() (stringslib_test.cc:779)
==4300==    by 0x1652A9: HandleSehExceptionsInMethodIfSupported<testing::Test, void> (gtest.cc:2611)
==4300==    by 0x1652A9: void testing::internal::HandleExceptionsInMethodIfSupported<testing::Test, void>(testing::Test*, void (testing::Test::*)(), char const*) (gtest.cc:2647)
==4300==    by 0x15A9DE: testing::Test::Run() [clone .part.658] (gtest.cc:2686)
==4300==    by 0x15AC61: Run (gtest.cc:2677)
==4300==    by 0x15AC61: testing::TestInfo::Run() [clone .part.659] (gtest.cc:2863)
==4300==    by 0x15B350: Run (gtest.cc:2837)
==4300==    by 0x15B350: testing::TestSuite::Run() [clone .part.660] (gtest.cc:3017)
==4300==    by 0x15BAF4: Run (gtest.cc:2997)
==4300==    by 0x15BAF4: testing::internal::UnitTestImpl::RunAllTests() (gtest.cc:5709)
==4300==    by 0x165769: HandleSehExceptionsInMethodIfSupported<testing::internal::UnitTestImpl, bool> (gtest.cc:2611)
==4300==    by 0x165769: bool testing::internal::HandleExceptionsInMethodIfSupported<testing::internal::UnitTestImpl, bool>(testing::internal::UnitTestImpl*, bool (testing::internal::UnitTestImpl::*)(), char const*) (gtest.cc:2647)
==4300==    by 0x15AD82: testing::UnitTest::Run() (gtest.cc:5292)
==4300==    by 0x11C08E: RUN_ALL_TESTS (gtest.h:2485)
==4300==    by 0x11C08E: main (stringslib_test.cc:799)
==4300== 

我希望你能帮助我,因为我现在真的很迷茫。

--------编辑------ 是的,我完全忘了添加调用者,这是我释放内存的地方:

TEST(JoinStringTest, Simple) {
  char *buf = str_join_string("ABC", "XYZ", '|');
  ASSERT_TRUE(buf != NULL);
  EXPECT_EQ(buf[0], 'A');
  EXPECT_EQ(buf[1], 'B');
  EXPECT_EQ(buf[2], 'C');
  EXPECT_EQ(buf[3], '|');
  EXPECT_EQ(buf[4], 'X');
  EXPECT_EQ(buf[5], 'Y');
  EXPECT_EQ(buf[6], 'Z');
  EXPECT_EQ(buf[7], '\0');
  free(buf);
}

【问题讨论】:

  • 当一个函数返回时,它已经执行完毕。您的 free(str3) 行将永远不会被调用,并且是“死代码”。 str_join_string cannot free str3 不管怎样,因为它会将它返回给调用者; 调用者必须负责在不再需要返回值时释放它。
  • 确实,我只是忘记将调用者添加到我使用 free() 的帖子中,我将删除这些代码行。对不起。
  • 什么是str_length()?它与标准库的strlen() 有何不同?
  • @JohnBollinger str_length() 返回与 strlen() 相同的结果,但是对于这个任务,禁止使用 strlen() 我必须自己制作一个。
  • 你确定你已经展示了 Valgrind 将泄漏归因于的测试吗? Valgrind 的堆栈跟踪中的JoinArrayTest_OneEmpty_Test::TestBody() 似乎与TEST(JoinStringTest, Simple) 不匹配。

标签: c memory-leaks string-concatenation c-strings function-definition


【解决方案1】:
return str3;
free(str3);

看看这个 sn-p,你认为free() 会被调用吗?

【讨论】:

  • 但这很好,因为如果这样做,返回的指针会悬空。
  • 你说得对,我只是忘了删除这段代码,我添加了调用者,这是我 free() 的地方。
【解决方案2】:

对于初学者,该函数调用未定义的行为,因为没有为结果字符串分配足够的内存。

代替

size_t len = str_length(str1)+ str_length(str2)+1;

你必须写

size_t len = str_length(str1)+ str_length(str2)+2;

还有这个说法

str3[len] = '\0';

还尝试写入分配数组之外的内存。

看来你的意思

str3[i + j] = '\0';

尽管您可以删除此语句,因为您使用的函数calloc 将分配的内存设置为零。另一方面,在函数上下文中使用calloc 是低效的。

返回语句后的and语句

//...
return str3;
free(str3);
str3 = NULL;

永远不会被执行。

注意这个 for 循环中的条件

while(str1[i] != '\0' && str1 != NULL){

没有意义。至少逻辑与运算符的操作数应该像这样交换

while( str1 != NULL && str1[i] != '\0' ){

尽管在任何情况下条件 str1 != NULL 都是多余的,或者您可以在 if 语句中检查循环之前的条件。

这是一个演示程序,展示了如何定义和调用函数(不使用标准字符串函数)。

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

size_t str_length( const char *s )
{
    size_t n = 0;
    
    while ( *s++ ) ++n;
    
    return n;
}

char * str_join_string( const char *s1, const char *s2, char separator ) 
{
    size_t n = str_length( s1 ) + str_length( s2 ) + sizeof( separator ) + 1;
    
    char *s3 = malloc( n );
    
    if ( s3 )
    {
        char *p = s3;
        
        for ( ; *s1; ++s1 ) *p++ = *s1;
        
        *p++ = separator;
        
        for ( ; *s2; ++s2 ) *p++ = *s2;
        
        *p = '\0';
    }
    
    
    return s3;
}

int main(void) 
{
    char *s = str_join_string( "ABC", "DEF", '|' );
    
    if ( s ) puts( s );
    
    free( s );
    
    return 0;
}

程序输出是

ABC|DEF

函数的用户应提供不等于NULL的参数。

【讨论】:

  • 您似乎知道非标准str_length() 函数的作用。还是您假设 OP 拼错了strlen
  • @JohnBollinger 我想他需要在不使用标准字符串函数的情况下编写函数。否则,他使用标准字符串函数代替循环。
  • 很可能是这样,但如果是这样,你怎么能确定它没有在其返回值中计算字符串终止符,无论是错误的还是设计的,从而得出分配内存不足的结论?
  • @JohnBollinger 因为否则他分配的内存比需要的多。
  • 这将是一个不同的问题,并且没有错误。
【解决方案3】:

也许是因为你先返回你的函数然后释放你的缓冲区!?

https://docs.microsoft.com/en-us/cpp/c-language/return-statement-c?view=vs-2019

return 语句结束函数的执行,并返回 控制调用函数

【讨论】:

  • 如果他按其他顺序做会更糟,因为他会返回一个无效的指针。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-11-09
  • 2014-04-30
  • 1970-01-01
  • 2011-08-29
  • 2013-04-03
  • 1970-01-01
  • 2017-06-19
相关资源
最近更新 更多