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

标签 c memory-management malloc valgrind char-pointer

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

更多信息:我主要关注方法 strcat_ex(这与 strncat 不同——阅读函数的描述以了解与 int *n 的区别)。问题出现的事实是我需要在 dest (char **) 中重新分配字符串 (char *) 的参数内存,如果它没有分配足够的空间,并且在我 malloc 之后它没有被初始化。这对我来说没有意义如何在 malloc 之后初始化“堆”内存。我不相信初始化必须在 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 错误所在,需要 stackoverflow 知识 (answer03.c):

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

#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 中还有更多内容,但我发布了我认为最相关的内容。提前致谢。

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

最佳答案

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

  • 假设 sizeofDest 不仅指示当前目标字符串的存储,而且指示任何串联操作的容量。这是完全错误的,这就是为该函数提供 n 的原因。
  • 彻底的内存泄漏:*dest = malloc(sizeof(buffer)); 不仅分配了完全不正确的内存大小(指针的大小;而不是它指向的内容),而且简而言之两行后泄漏说分配。
  • 死代码 bool 逻辑:给定任何非负值 N,表达式 N >= N + 1 + M,其中 M 是一个非负值,不可能永远是真的。
  • 您永远不会使用与目标指针地址一起提供的关键信息:n 提供的当前目标缓冲区大小。该值对该算法至关重要,因为它决定了目标缓冲区的真实大小,并与 *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

关于c - 未初始化的堆分配误解(代码有效——需要更正以消除 Valgrind 错误),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28549515/

相关文章:

c - 在 Bison 出错后释放留在堆栈上的指针

使用运行 ubuntu 的 VM Player 在 Windows 上为 Linux 交叉编译 C 源代码

c - gcc scanf 警告认为 float 是 double

c - 释放内存后我们如何访问指针?

c - 为什么在执行 memcpy 时检测到堆栈粉碎?

C# 字典和高效内存使用

macos - calloc 或 malloc 可以用于在 OSX 中仅分配物理内存吗?

c - 使用C获取文件名列表并将它们存储在Linux上的数组中

c - 八进制转十进制的c程序

c - 用户进程是否可以告诉操作系统将 mmap 完成的映射重新定位到其他 NUMA 节点?