【问题标题】:Double Free Detected in C When Deallocating a String Struct释放字符串结构时在 C 中检测到双释放
【发布时间】:2021-12-15 14:20:01
【问题描述】:

每当我为我的编程运行测试代码时,它都会以以下消息终止:

free(): 在 tcache 2 中检测到双重空闲

进入 gdb 并使用“回溯”命令让我知道问题出在我的 String_Dispose 函数上。

(#5 0x000000000040131c in String_Dispose (ppStr=0x7fffffffdee0) at String.c:225)

这是我的 String_Dipose 函数代码:

/**  Deallocates a String object and all its content.
 * 
 *   Pre:
 *     *ppStr is a pointer to a proper String object, so
 *     **ppStr is a proper String object
 *     **ppStr was allocated dynamically
 *   Post:
 *     (**ppStr).data has been deallocated
 *     **ppStr has been deallocated
 *     *ppStr == NULL
 */
void String_Dispose(String** ppStr) {

    ///  Implement this function!!  ///
    free((**ppStr).pData);
    free(*ppStr);
    
    *ppStr = NULL;  
}

这是函数调用在实践中的样子:

String *pStr = String_Create("And as we wind on down the road...", 34);
. . .
// Initialize the String and use it until we're done with it.
. . .
String_Dispose(&pStr);
// At this point, every trace of the String object is gone and pStr == NULL

这是上下文的其余代码,我不确定它是否正确(它也未完成)但这里的主要问题是 String_Dipose。

#include "String.h"
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>

/** The String is initialized to hold the values in *src.
 *
 *  Pre:
 *    *pSrc is C string with length up to slength (excludes null char)
 *  Post on success:
 *    A new, proper String object S is created such that:
 *       S.pData != pSrc->pData
 *       Up to slength characters in *pSrc are copied into dest->data
 *         (after dynamic allocation) and the new string is terminated
 *         with a '\0'
 *       S.length is set to the number of characters copied from *pSrc;
 *         this is no more than slength, but will be less if a '\0' is
 *         encountered in *pSrc before slength chars have occurred
 * Post on failure:
 *    NULL is returned
 * 
 * Returns:
 *    pointer to the new String object;
 *    NULL value if some error occurs
 */
String* String_Create(const char* const pSrc, uint32_t slength) {

    ///  Implement this function!!  ///
    String* str = (String*) malloc(sizeof(String));
    
    if (str == NULL) {
        printf("Failed to allocate memory.");
        return -1;
    }
    
    str->pData = pSrc;
    str->length = slength;
        
    return str;
}


/** Compares two Strings.
 * 
 *  Pre:
 *    *pLeft is a proper String object
 *    *pRight is is a proper String object
 *
 *  Returns:
 *    < 0 if *pLeft precedes *pRight, lexically
 *      0 if *pLeft equals *pRight
 *    > 0 if *pLeft follows *pRight, lexically
 */
int32_t String_Compare(const String* const pLeft, const String* const pRight) {

    ///  Implement this function!!  ///
    
    uint32_t left = 0, right = 0;
    uint32_t leftLength = pLeft->length, rightLength = pRight->length;
    
    while (left < leftLength && right < rightLength) {
        int compare = pLeft->pData[left++] - pRight->pData[right++];
        
        if (compare != 0) {
            return compare;
        }
    }
        
    return (pLeft->pData[left] - pRight->pData[right]);
}


/** Appends the String *pSrc to the String *pDest.
 * 
 *  Pre:
 *    *pDest is a proper String object
 *    *pSrc is is a proper String object
 *    pSrc != pDest (i.e., the source and destination are different String objects)
 *  Post on success:
 *    pSrc->pData is appended to the String pDest->pData
 *    *pDest is a proper String object  
 *  Post on failure:
 *    *pDest is unchanged
 * 
 *  Returns:
 *    the length of pDest->pData, if nothing goes wrong;
 *    a negative value, if some error occurs
 */
int32_t String_Cat(String* const pDest, const String* const pSrc) {

    ///  Implement this function!!  ///
    
    uint32_t srcLength = pSrc->length, destLength = pDest->length;
    
    char* strCmb = (char*) malloc(sizeof(char) * (srcLength + destLength + 1));
    
    if (strCmb == NULL) {
        printf("Failed to allocate memory.");
        return -1;
    }
    
    uint32_t i = 0, j = 0;
    
    while (j < destLength) {
        strCmb[i++] = pDest->pData[j++];
    }
    
    j = 0;
    
    while (j < srcLength) {
        strCmb[i++] = pDest->pData[j++];
    }
    
    strCmb[i] = '\0';
    
    pDest->pData = strCmb;
    pDest->length = i;
    
    return pDest->length;
}


/** Makes an exact, full copy of a substring.
 * 
 * Pre:
 *   *pSrc is a proper String object
 *   startIdx + length <= pSrc->length
 * Post:
 *    no memory leaks have occurred
 *    A new, proper string object S has been created such that S holds
 *      the specified substring of *pSrc
 *
 * Returns:
 *    pointer to a String object which holds a copy of specified substring;
 *    NULL if failure occurs
 */
String* String_subString(const String* const pSrc, uint32_t start, uint32_t length) {

    ///  Implement this function!!  ///
    
    String* subStr = (String*) malloc(sizeof(String));
    
    if (subStr == NULL) {
        printf("Failed to allocate memory.");
        return -1;
    }
    
    subStr->pData = NULL;
    subStr->length = length + 1;
    
    char* strData = (char*) malloc(sizeof(char) * (length + 1));
    
    int i = start, j = 0;
    
    while (i < length) {
        strData[j] = pSrc->pData[i];
        j++;
    }
    
    strData[j+1] = "\0";
    
    subStr->pData = strData;
        
    return subStr;
}


/** Erases a specified sequence of characters from a String.
 * 
 * Pre:
 *   *pSrc is a proper String object
 *   startIdx + length <= src->length
 * Post:
 *    no memory leaks have occurred
 *    the specified range of characters have been removed
 *    *pSrc is proper
 *
 * Returns:
 *    if successful, pSrc
 *    NULL if failure occurs
 */
String* String_Erase(String* const pSrc, uint32_t start, uint32_t length) {

    ///  Implement this function!!  ///
        
    return pSrc;
}


/**  Deallocates a String object and all its content.
 * 
 *   Pre:
 *     *ppStr is a pointer to a proper String object, so
 *     **ppStr is a proper String object
 *     **ppStr was allocated dynamically
 *   Post:
 *     (**ppStr).data has been deallocated
 *     **ppStr has been deallocated
 *     *ppStr == NULL
 */
void String_Dispose(String** ppStr) {

    ///  Implement this function!!  ///
    free((**ppStr).pData);
    free(*ppStr);
    
    *ppStr = NULL;  
}

不知道是什么问题,我也试过了:

String* ptr = *ppStr;

free (ptr->data); 
free (ptr);

*ppStr= NULL;

这最终会得到相同的结果,我有点迷茫,希望朝着正确的方向前进。

String.h 中的结构声明:

struct _String {

   char     *pData;    // dynamically-allocated array to hold the characters
   uint32_t  length;   // number of characters in the string
};
typedef struct _String String;

【问题讨论】:

  • 我忘了显示 String.h,但结构的声明非常简单,有两个字段:char *pData 和 uint32_t 长度。
  • 将声明添加到问题中。不要发表评论。
  • 为什么你的'String_Create()`函数没有复制字符串数据?你的类型没有管理它的所有资源;它无法控制其他代码的滥用。很难阅读所有 cmets 以了解您的代码在做什么。但是,您将一行显示为String *pStr = String_Create("And as we wind on down the road...", 34); — 该字符串不是由malloc() 分配的,因此无法安全地释放它。如果该类型复制了它传递的数据,那么正是可以避免的问题。
  • 不是malloc分配的吗?在 String_Create() 函数中,我调用 malloc 然后返回该字符串。我也不确定你在通过函数本身传递字符串数据时复制它是什么意思。
  • @Azure21 我相信 JonathanLeffler 是正确的。我自己的测试代码使用了您的 String_Create 并且没有错误地运行,但是如果我尝试使用 您的示例代码,那么我会得到您发布的错误。

标签: c pointers memory-management free


【解决方案1】:

这个电话对我来说似乎是正确的。有两个小不一致与您的问题无关:

 if (str == NULL) {
    printf("Failed to allocate memory.");
    return -1;
 }
 str->pData = pSrc;
 str->length = slength;        
 return str;

应该是(因为你想返回一个指针,而不是“-1”)

 if (str == NULL) {
    printf("Failed to allocate memory.");
 } else {
    str->pData = pSrc;
    str->length = slength;
 }
 return str;

在 String_Cat 中,pData 的先前值应被释放以避免泄漏,除非它为 NULL(但在我看来它永远不会);或者更好,使用 realloc() 而不是 strCmb 进行修改。

另一个奇怪的事情是,您似乎同时使用 Pascal 风格的字符串(长度存储在文本中)和 ASCIIZ 字符串(例如strCmb[i] = '\0';);这可能与分配冲突(例如,来自 strcat'ting He 和 llo 的字符串“Hello”的长度为 six,但您的测试字符串“And as we wind on down the road..." 是 34 个字符,长度为 34 而不是 35。当 Str_Compar'ing 时,这可能会在后面咬你。

上面代码的示例程序运行正确:

==1129378== Memcheck, a memory error detector
==1129378== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==1129378== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==1129378== Command: ./zot
==1129378==
Allocating dummy string
Freeing 0x4a51040
Freeing 0x1fff000900
==1129378==
==1129378== HEAP SUMMARY:
==1129378==     in use at exit: 0 bytes in 0 blocks
==1129378==   total heap usage: 3 allocs, 3 frees, 1,090,632 bytes allocated
==1129378==
==1129378== All heap blocks were freed -- no leaks are possible
==1129378==
==1129378== For lists of detected and suppressed errors, rerun with: -s
==1129378== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

因此,看起来错误可能在于任何代码调用String_Dispose()

例如,如果您按字面意思使用示例测试代码,

String *pStr = String_Create("And as we wind on down the road...", 34);

然后您传递给 String_Created 的字符串没有分配,所以 pStr 对象带有一个不适合 free() 的字符串,并且试图在 String_Dispos'ing 时释放它会调用未定义的行为。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-08
    • 2012-11-15
    • 2020-01-22
    • 2017-08-24
    • 1970-01-01
    • 2021-07-30
    • 2021-08-26
    相关资源
    最近更新 更多