未初始化的堆分配误解(代码有效——需要更正以消除 Valgrind 错误)

Unitialized Heap Allocation Misunderstanding (Code Works -- Need to Correct to Remove Valgrind Error)

我的代码工作正常,但我收到 valgrind 错误。我想知道如何更正我的代码以正确使用这些 malloc 和 free 语句与 char * * dest。请不要告诉我不要 malloc 和 free 除非我在不正确的位置这样做。拥有 answer03.c 中 strcat_ex 的一个或两个更正代码,或者解释我对 malloc、free 和 malloc 之后的初始化的误解,将不胜感激。对于冗长的post,我提前表示歉意,但我想提供所有必要的东西。

更多信息:我主要关注方法 strcat_ex(这与 strncat 不同——阅读函数描述以了解与 int *n 的区别)。出现问题的事实是我需要在 dest (char **) 中重新分配字符串 (char *) 的参数内存,如果它没有分配足够的 space 分配给它,并且在我 malloc 之后它是未初始化。这对我来说没有意义如何在 malloc 之后初始化 "heap" 内存。我不相信初始化必须在 malloc 之后发生。

注意:pa03.c 和 answer03.h 根本不应该改变。

这是相关的 valgrind 错误 (memcheck.log):

==28717== 1 errors in context 7 of 10:
==28717== Conditional jump or move depends on uninitialised value(s)
==28717==    at 0x402D09C: strcat (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==28717==    by 0x8048F98: strcat_ex (answer03.c:29)
==28717==    by 0x8048631: main (pa03.c:16)
==28717==  Uninitialised value was created by a heap allocation
==28717==    at 0x402A17C: malloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==28717==    by 0x8048F46: strcat_ex (answer03.c:21)
==28717==    by 0x8048631: main (pa03.c:16)
==28717== 
==28717== ERROR SUMMARY: 10 errors from 10 contexts (suppressed: 0 from 0)

引用的行数:

第 16 行(来自 pa03.c)不应更改。作为调用方法参数 defnsrcreturn 变量结果声明如下 pa03.c:

result=strcat_ex(&dest, &n, src);

第 21 行(来自 answer03.c):

char * buffer = malloc(1 + 2 * (sizeOfDest + strlen(src)));

第 29 行(来自 answer03.c):

buffer = strcat(buffer,src);

这里是相关的源代码。这是 valgrind 错误所在,需要 Whosebug 知识 (answer03.c):

编辑:已添加注释并注释掉行以删除我在代码中与我的问题直接无关的错误。对于这些令人发指的错误,我深表歉意,但保留了这些台词以帮助未来的读者理解。

#include "answer03.h"
#include <string.h>

char * strcat_ex(char * * dest, int * n, const char * src)
{
            //Edit: Removed Line Below - Irrelevant variable resplaced with *n
    //int sizeOfDest;

    if(*dest == NULL)
    {
        *n = 0;
    }
    else
    {
        //Edit: Removed Line Below - variable replaced with *n 
        //sizeOfDest = strlen(*dest);
    }
    //Edit: Removed Line Below
    //if(*dest != NULL && sizeOfDest >= 1 + sizeOfDest + strlen(src))
    //Edit: Corrected Line
    if(*dest !=NULL && *n >= 1 + strlen(*dest) + strlen(src)) 
    {
        strcat(*dest, src);
    }
    else
    {
        //Edit: *n replaced sizeOfDest and changes needed to be made to reflect this. Commented out lines were incorrect and irrelevant. Lines directly below them are the corrected versions, until you reach the next blank line

        //*n = 1 + 2 * (sizeOfDest + strlen(src));
        if(*dest != NULL)
           *n = 1 + 2 * (strlen(*dest) + strlen(src));
        else
           *n = 1 + 2 * strlen(src); 

        //char * buffer = malloc(1 + 2 * (sizeOfDest + strlen(src)));
        char * buffer = malloc(sizeof(char) * *n);

        if(*dest != NULL)
        {
            strcpy(buffer, *dest);
            free(*dest);
        }
        *dest = malloc(sizeof(buffer));
        buffer = strcat(buffer,src);
        *dest = buffer;
    }
    return *dest;
}




低于此点的所有内容都应保持不变并且已知是正确的:

我的编译语句(Makefile):

gcc -Wall -Wshadow -g pa03.c answer03.c -o pa03

我的 valgrind 语句(Makefile):

valgrind --tool=memcheck --leak-check=full --verbose --track-origins=yes --log-file=memcheck.log ./pa03

这里是 strcat_ex 的函数定义 (answer03.h):

#ifndef PA03_H
#define PA03_H 

#include <stdlib.h>

/**
 * Append the C-string 'src' to the end of the C-string '*dest'.
 *
 * strcat_ex(...) will append the C-string 'src' to the end of the string
 * at '*dest'. The parameter 'n' is the address of a int that specifies how
 * many characters can safely be stored in '*dest'. 
 *
 * If '*dest' is NULL, or if '*dest' is not large enough to contain the result
 * (that is, the sum of the lengths of *dest, src, and the null byte), then
 * strcat_ex will:
 * (1) malloc a new buffer of size 1 + 2 * (strlen(*dest) + strlen(src))
 * (2) set '*n' to the size of the new buffer
 * (3) copy '*dest' into the beginning of the new buffer
 * (4) free the memory '*dest', and then set '*dest' to point to the new buffer
 * (5) concatenate 'src' onto the end of '*dest'.
 *
 * Always returns *dest.
 *
 * Why do we need to pass dest as char * *, and n as int *? 
 * Please see the FAQ for an answer.
 *
 * Hint: These <string.h> functions will help: strcat, strcpy, strlen.
 * Hint: Leak no memory.
 */
char * strcat_ex(char * * dest, int * n, const char * src);
//...

下面是调用source的相关代码作为测试(pa03.c):

#include <stdio.h>
#include <string.h>
#include "answer03.h"

int main(int argc, char **argv)
{
  char * src;
  char * dest;
  char * result;
  int n;

  src="World!";
  dest=NULL;
  result=strcat_ex(&dest, &n, src);
  printf("src=\"World!\";\ndest=NULL;\nstrcat_ex(&dest, &n, src);\n --> gives %s with n=%d\n",result,n);
  result=strcat_ex(&dest, &n, "");
  printf("Then strcat_ex(&dest, &n, \"\") yields --> gives %s with n=%d\n",result,n);
  strcpy(dest,"abc");
  result=strcat_ex(&dest, &n, "def");
  printf("Then strcpy(dest,\"abc\"); strcat_ex(&dest, &n, \"def\") yields --> gives %s with n=%d\n",result,n);  
  free(dest);
  //...

这是相关的输出(打印来自 pa03.c 的语句): 请注意,这是正确的输出(我当前的代码能够生成)。

src="World!";
dest=NULL;
strcat_ex(&dest, &n, src);
 --> gives World! with n=13
Then strcat_ex(&dest, &n, "") yields --> gives World! with n=13
Then strcpy(dest,"abc"); strcat_ex(&dest, &n, "def") yields --> gives abcdef with n=13
//...

遗言:

我已附上编译此代码所需的文件以及 linux 使用 gcc 和 valgrind 的 valgrind 错误日志。 valgrind 中还有更多内容,但我 post 编辑了我认为最相关的内容。提前致谢。

Zip 包括所有文件:
http://www.filedropper.com/files_11

如果 *destNULL,当 buffer 刚被 malloc 时,您将在 buffer 上调用 strcat 并且它未初始化。将 buffer[0] 设置为 0,或使用 calloc.

此外,您分配了 *dest,然后在两行之后将 *dest 设置为 buffer,这会泄漏一些内存。不需要第一次赋值给*dest.

您当前的功能已完全损坏。它包含无法看到结果的逻辑,至少有一次内存泄漏,以及未经检查的连接到未初始化的目标缓冲区。其中错误的地方很多:

  • 假设 sizeofDest 不仅指示当前目标字符串的存储,而且指示任何串联操作的 容量 。这是完全错误的,这就是为该函数提供 n 的原因。
  • 彻底的内存泄漏:*dest = malloc(sizeof(buffer));不仅分配了完全错误的内存大小(指针的大小;而不是它指向的内容),而且仅在两行之后就泄漏了所述分配。
  • 死代码布尔逻辑:给定任何非负值 N,表达式 N >= N + 1 + M,其中 M 是一个非负值,不可能永远是真的。
  • 您永远不会使用与目标指针逐地址一起提供的关键信息:n 提供的当前目标缓冲区大小。该值对该算法来说是 critical,因为它决定了目标缓冲区的 real 大小,并结合当前字符串长度*dest*,最终将决定是否需要调整大小。

这是执行此功能的一种方法正确:

char *strcat_ex(char ** dest, int * n, const char * src)
{
    size_t dst_len = 0, src_len = strlen(src);

    // determine current string length held in *dest
    if (*dest && **dest)
        dst_len = strlen(*dest);

    size_t req_len = dst_len + src_len + 1;

    // is space already available for the concatination?
    if (*dest && *n >= req_len)
    {
        // we already know where the target address of the
        // concatination is, and we already know the length of
        // what is being copied. just copy chars.
        if (src_len)
            memcpy(*dest+dst_len, src, src_len+1);
    }
    else
    {
        // resize is required
        void *tmp = realloc(*dest, req_len);
        if (tmp != NULL)
        {
            // resize worked, original content of *dest retained, so
            // we can once again simply copy bytes.
            *dest = tmp;
            memcpy(*dest+dst_len, src, src_len+1);
            *n = (int)req_len;
        }
        else
        {
            perror("Failed to resize target buffer");
            exit(EXIT_FAILURE);
        }
    }

    return *dest;
}

我对这个设计的作者很不满意,因为他们选择 int 作为保存目标缓冲区大小的变量。这个大小显然永远不会是 负数 ,并且除了所有标准库大小操作所使用的相同类型 size_t 之外,使用任何其他类型都是没有意义的。我会引起设计者的注意。

简单测试

int main()
{
    char *dst = NULL;
    int n = 0;

    strcat_ex(&dst, &n, "some string");
    printf("%s : %d\n", dst, n);
    strcat_ex(&dst, &n, " more data");
    printf("%s : %d\n", dst, n);

    *dst = 0; // zero-term the string

    strcat_ex(&dst, &n, "after empty");
    printf("%s : %d\n", dst, n);
    strcat_ex(&dst, &n, " and more");
    printf("%s : %d\n", dst, n);
    strcat_ex(&dst, &n, " and still more");
    printf("%s : %d\n", dst, n);
}

输出

some string : 12
some string more data : 22
after empty : 22
after empty and more : 22
after empty and more and still more : 36

你的测试

运行 您的测试程序产生以下结果:

src="World!";
dest=NULL;
strcat_ex(&dest, &n, src);
 --> gives World! with n=7
Then strcat_ex(&dest, &n, "") yields --> gives World! with n=7
Then strcpy(dest,"abc"); strcat_ex(&dest, &n, "def") yields --> gives abcdef with n=7