Realloc 给出错误:_CrtIsValidHeapPointer(block)

Realloc gives error: _CrtIsValidHeapPointer(block)

提问人:Windroz 提问时间:9/18/2023 最后编辑:Vlad from MoscowWindroz 更新时间:9/20/2023 访问量:65

问:

我有这个学校作业,我们得到了一些代码来修改。我遇到的部分是截断动态分配的字符串。我们正在编写一个函数,其中我们接受指向字符串的指针和字符串的新长度(作为 int)。

void dstring_truncate(DString* destination, unsigned int truncatedLength)
{

    assert(destination != NULL);
    assert(*destination != NULL);
    assert(truncatedLength > 0);

    DString temp = (DString)malloc(strlen(*destination) * sizeof(char));
    strcpy(temp, *destination);
    printf("\n%s test dstring truncate\n", temp);
    temp[truncatedLength] = '\0';
    temp = (DString)realloc(temp, truncatedLength + 1);

    *destination[truncatedLength] = '\0';
    *destination = (DString)realloc(*destination, truncatedLength + 1);
    printf("%s test dstring truncate after realloc\n", *destination);
    
}

对函数的调用看起来像是 DString 是我们正在使用的 ADTdstring_truncate(&str1, 10);typedef char* DString;

此外,程序中的第一个函数初始化字符串DString dstring_initialize(const char* str);

我假设我不理解指针,因为上面代码中的“Temp”按预期工作,但“*destination”没有。

我尝试使用“destination”、“*destination”、“**destination”,我已经重新观看了给出的材料和一些在线 youtube/文档,但我就是无法解决这个问题。

另外,这就是我编写初始化代码的方式,也许这里有什么问题?

DString dstring_initialize(const char* str)
{
    assert(str != NULL);
    DString arr = (DString)malloc((strlen(str) + 1) * sizeof(char));
    if (arr == NULL) {
        printf("Memory allocation Failed");
        return NULL;
    }
    strcpy(arr, str);
    if (strcmp(str, arr) == 0) 
    {
        return arr; // Ersatt denna rad. Den ar just nu med for att programmet ska kompilera
    }
}
C 按引用传递 dynamic-memory-allocation c-strings realloc

评论

1赞 Some programmer dude 9/18/2023
除此之外,为什么要先复制整个字符串,然后再重新分配?为什么不分配截断的长度(加一个用于空终止符),然后只复制截断的长度?
0赞 Some programmer dude 9/18/2023
其他重要注意事项:在 C 语言中,您不应该强制转换 mallocrealloc 或任何其他返回 .此外,不要重新分配回传递到的指针。如果失败,它将返回一个空指针,但保留旧内存。如果重新分配回同一指针,则会丢失原始内存。而且我没有看到您的代码进行任何错误检查或验证(如果它们相等,则无需执行任何操作)。void *reallocrealloctruncatedLength <= strlen(*destination)
0赞 Some programmer dude 9/18/2023
另外,请尽量避免指针的类型别名。这是一个坏习惯,往往会导致问题,并且代码更难阅读和维护。
1赞 Ian Abbott 9/18/2023
打电话的目的是什么?由于这紧接在使用相同参数调用 之后(尽管顺序相反),因此 将始终返回 0。此外,如果由于某种原因它没有返回 0,则没有语句来处理这种情况。strcmpdstring_initializestrcpystrcmpreturn
0赞 David C. Rankin 9/20/2023
您将需要查看:typedef 指针是个好主意吗?(答案:一般不定)

答:

0赞 Some programmer dude 9/18/2023 #1

当您超出了以前分配的内存的范围时,该错误是典型的。

就像你这样做的时候一样

strcpy(temp, *destination);

不为 null 终止符分配空间。

请记住,这计算 null 终止符。strlen

快速修复:

DString temp = malloc(strlen(*destination) + 1);

添加我的评论中提到的一些部分,我会推荐这样的功能:

DString dstring_truncate(DString* destination, size_t truncated_length)
{
    if (destination == NULL || *destination == NULL)
    {
        // No source/destination
        return NULL;
    }

    size_t original_length = strlen(*destination);

    if (truncated_length == original_length)
    {
        // No operation
    }
    else if (truncated_length > original_length)
    {
        // Try to make it longer? That's not truncation. No operation
    }
    else
    {
        // Truncate by reallocation
        // Note that this assumes the original buffer was allocated dynamically as well
        DString temp = realloc(*destination, truncated_length + 1);
        if (temp == NULL)
        {
            // Allocation failed, keep the old buffer but return as an error
            return NULL;
        }

        // Set the null-terminator
        temp[truncated_length] = '\0';

        // And finally reassign the destination
        *destination = temp;
    }

    // Return the (possibly new) string
    return *destination;
}
0赞 Ian Abbott 9/18/2023 #2

此行中存在运算符优先级错误:

    *destination[truncatedLength] = '\0';

它应该是:

    (*destination)[truncatedLength] = '\0';

解释:后缀运算符(如数组下标运算符)比一元运算符(如一元(间接)运算符)绑定得更紧密,所以实际上与 or 或相同,这不是您想要的。这将被视为指向数组的第一个元素,并将该数组的第 th 个元素设置为 。将被视为空指针常量,因此它将数组的第 th 个元素设置为空指针。不幸的是,它实际上指向的是单个,而不是数组的第一个元素,导致在 时出现未定义的行为。在两边加上括号意味着一元运算符应用于 而不是应用于 。 等价于 或 ,这是您想要的。(与 .)[]**destination[truncatedLength] = '\0';*(destination[truncatedLength]) = '\0';*(*(destination + truncatedLength)) = '\0';**(destination + truncatedLength) = '\0'destinationDStringtruncatedLength'\0''\0'truncatedLengthDStringdestinationDStringDStringtruncatedLength > 0*destination*destinationdestination[truncatedLength](*destination)[truncatedLength = '\0';*((*destination) + truncatedLength) = '\0';*(*destination + truncatedLength) = '\0';**(destination + truncatedLength)*(*destination + truncatedLength)

该函数假定 .如果要在调试期间断言条件为 true,可以添加调用:truncatedLength <= strlen(*destination)assert

    assert(truncatedLength <= strlen(*destination));

(请注意,如果定义了宏,则调用将不起作用。assertNDEBUG

或者,在以下情况下,可以将函数编写为不执行任何操作:truncatedLength >= strlen(*destination)

    if (truncatedLength >= strlen(*destination))
    {
        return;
    }
    (*destination)[truncatedLength] = '\0';

允许将字符串截断为零长度似乎是合理的,但现有的不允许这样做。assert(truncatedLength > 0);

调用 可能会返回 。在缩小分配块的大小时,这不太可能发生,但在技术上是可行的。在这种情况下,原始块仍然有效,并且由于它足够大,因此可以保留它。仅当调用成功时,以下代码才会替换:realloc()NULL*destinationrealloc()

    DString tempdest = realloc(*destination, truncatedLength + 1);
    if (tempdest != NULL)
    {
        *destination = tempdest;
    }

请注意,不需要强制转换 返回的值,或者因为 会自动将右侧的值转换为 左侧的指针对象的类型。void *malloc()calloc()realloc()=void *=

在使用 of 作为比例因子时存在一些不一致之处,因此上述代码可以更改为:sizeof(char)

    DString tempdest = realloc(*destination, (truncatedLength + 1) * sizeof(char));
    if (tempdest != NULL)
    {
        *destination = tempdest;
    }

请注意,根据定义,它是 1,因此可以删除所有这些因素。(对于适用于 的版本,需要保留这些因素。sizeof(char)* sizeof(char)wchar_t* sizeof(wchar_t)

变量的实验也存在问题。在这些行中:temp

    DString temp = (DString)malloc(strlen(*destination) * sizeof(char));
    strcpy(temp, *destination);

第一行没有为 null 终止符分配足够的空间,因此第二行写入了分配的内存的末尾。指向的内存永远不会被释放,所以这也是一个内存泄漏错误,但我理解这只是一个实验。temp

把所有这些放在一起并删除实验,我们最终可能会得到这样的东西:temp

void dstring_truncate(DString* destination, unsigned int truncatedLength)
{
    assert(destination != NULL);
    assert(*destination != NULL);

    if (truncatedLength >= strlen(*destination))
    {
         return;
    }
    DString tempdest = realloc(*destination, truncatedLength + 1);
    if (tempdest != NULL)
    {
        *destination = tempdest;
    }
    (*destination)[truncatedLength] = '\0';
}

在 中,并非所有通过函数的路径都返回一个值,但由于函数末尾的条件始终为 true,因此无论如何都不会到达该语句后面的代码。没有必要在最后语句中调用 。只需在通话后返回:dstring_initialize()ifififstrcmparrstrcpy()

DString dstring_initialize(const char* str)
{
    assert(str != NULL);
    DString arr = malloc(strlen(str) + 1);
    if (arr == NULL) {
        printf("Memory allocation Failed");
        return NULL;
    }
    strcpy(arr, str);
    return arr;
}
1赞 Vlad from Moscow 9/18/2023 #3

在函数中按原样使用 varable 是没有意义的,因为至少它会产生内存泄漏。我认为您包含带有变量 temporary 的代码仅用于测试目的。temptemp

然而,这部分代码在此语句中包含一个错误

 DString temp = (DString)malloc(strlen(*destination) * sizeof(char));

您没有为创建的字符串的终止零字符保留内存。因此,这句话'\0'

strcpy(temp, *destination);

覆盖超出已导致未定义行为的已分配动态内存范围的内存。

本声明

temp[truncatedLength] = '\0';

在逻辑上也是不正确的,因为您没有检查的值是否确实不大于源字符串的长度。在重新分配字符串之前,您需要检查 的值是否小于源字符串的 currect 长度。truncatedLengthtruncatedLength

顺便说一句,变量(函数参数)应该有 type 而不是 . 是函数的返回类型,也是在标准内存分配函数中指定大小的参数类型。truncatedLengthsize_tunsigned intsize_tstrlen

在此声明中

*destination[truncatedLength] = '\0';

使用无效的运算符顺序。相反,你需要写

( *destination )[truncatedLength] = '\0';

同样,您需要首先检查它是否不大于源字符串的长度。truncatedLength

该函数可以返回一个 null 指针。在这种情况下,您将发生内存泄漏,因为指针的值将在此语句中被覆盖realloc*destination

*destination = (DString)realloc(*destination, truncatedLength + 1);

您需要使用一个中间变量,函数的返回值将被分配给该中间变量。realloc

并且您应该向函数的调用者报告重新分配是否成功。因此,最好将函数的返回类型声明为 而不是 。intvoid

下面是一个演示程序,展示了如何更新函数

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

typedef char *DString;

DString dstring_initialize( const char *str )
{
    assert( str != NULL );

    DString arr = malloc( strlen( str ) + 1 );

    if ( arr == NULL)
    {
        puts( "Memory allocation Failed" );
    }
    else
    {
        strcpy( arr, str );
    }

    return arr;
}

int dstring_truncate( DString *destination, size_t truncatedLength )
{

    assert( destination != NULL );
    assert( *destination != NULL );
    assert( truncatedLength > 0 );

    size_t n = strlen( *destination );

    int success = truncatedLength < n;

    if ( success )
    {
        DString temp = realloc( *destination, truncatedLength + 1 );

        success = temp != NULL;

        if (success)
        {
            *destination = temp;
            ( *destination )[truncatedLength] = '\0';
        }
    }

    return success;
}

int main( void )
{
    const char *s = "Hello Wordl!";

    DString ds = dstring_initialize( s );

    if ( ds != NULL )
    {
        int success = dstring_truncate( &ds, strchr( ds, ' ' ) - ds );

        if (success)
        {
            printf( "Now after truncation the string is \"%s\"\n", ds );
        }

        free( ds );
    }
}

程序输出为

Now after truncation the string is "Hello"

实际上,该函数不应发出任何消息。它是函数的调用者根据被调用函数的返回值来决定是否发出消息。因此,该函数应如下所示dstring_initialize

DString dstring_initialize( const char *str )
{
    assert( str != NULL );

    DString arr = malloc( strlen( str ) + 1 );

    if ( arr != NULL)
    {
        strcpy( arr, str );
    }

    return arr;
}

评论

0赞 Windroz 9/20/2023
谢谢你,这完美地解决了它,谢谢你解释所有步骤,以便我能够理解。正如您提到的,很多代码(例如您提到的临时代码)都用于测试目的,因此这是一团糟:P
0赞 Vlad from Moscow 9/20/2023
@Windroz 完全没有。不客气:)