6

好的,首先,公平警告,这是一个班级项目。除了修复我的内存泄漏之外,我不寻求任何帮助。我想我在这个 C 代码的空间中遵循了一些可怕的编码实践。无论如何,当我运行 Valgrind 来搜索发生内存泄漏的位置时,我根本不清楚我错过了哪些内存泄漏。我知道至少有两个字符串我没有释放,但我 malloc() ed,纯粹基于 valgrind 输出的大小。因为我从项目中取出了一些无关的代码,valgrind 的行号很可能会被关闭,因此为了您的方便,我用注释标记了它们。

一点背景,我正在写一个shell的基本功能。它目前执行以下操作:

1.接受用户输入

2.将输入解析为cmdin结构

3.执行命令,前提是它没有管道。

4.从我创建的 cmdin 中释放空间,然后从步骤 1 重新开始。这就是问题发生的地方。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#define MAX_SIZE 1024
#define MAX_CLEN 2097152
#define READ_END 0
#define WRITE_END 1
#define CHILD_STATUS 0
struct cmdin
{
    char *cmd;
    char **args;
    int nargs;
    int pipeflag;
};

//check if the last argument of the command passed to it is a pipe.
//if so, return the position of the pipe, otherwise, return 0
//if it has already found a pipe, it will return the position of that pipe
int conpipe(struct cmdin * cmd)
{
    if(!cmd->pipeflag)
    {
        if(!strcmp(cmd->args[cmd->nargs], "|"))
        {
            return cmd->nargs;
        }
        else
        {
            return 0;
            /* PROBLEM LINE BELOW */
        }  // line 46, where valgrind claims one of the problems exists
    }
    else
    {
        //printf("pipeflag: %d\n", cmd->pipeflag);
        return (cmd->pipeflag);
    }
}

//free the command after each runthrough
int freeze(struct cmdin cmd)
{
    int i;
    for(i=0; i <= (cmd.nargs); i++)
    {
        //printf("cmd.args[%d]: %s\n",i, cmd.args[i]);
        /* PROBLEM LINE BELOW*/
        free(cmd.args[i]); //this is line 62, as noted by valgrind
    }
    free(cmd.args);
    free(cmd.cmd);
    return 0;
}

//parse input, and add a null to the end
struct cmdin * parse(char *cmd)
{
    //allocate space for the command 
    struct cmdin *ped = malloc(sizeof(struct cmdin)); 
    //declare pipeflag, and nargs as 0
    ped->pipeflag = 0;
    ped->nargs = 0;
    //allocate space for the array of strings, and for the string cmd.
    ped->args =  malloc(sizeof(char*) * MAX_SIZE);
    ped->cmd = malloc(sizeof(char) * MAX_SIZE);
    //scan the input, and put the first argument into the cmd string
    sscanf(cmd, "%s %[^\n]", ped->cmd, cmd);
    //allocate space for the next string, and then copy cmd to it.
    ped->args[ped->nargs] =  malloc(sizeof(char) * MAX_SIZE);
    strcpy(ped->args[ped->nargs], ped->cmd);
    ped->pipeflag = conpipe(ped);

    /* PROBLEM LINE BELOW*/
    ped->nargs++;     // line 86, where valgrind claims the second leak is called?
    ped->args[ped->nargs] =  malloc(sizeof(char) * MAX_SIZE);
    //loop that allocates space for a command, and then assigns
    //the next arg to it.
    while(sscanf(cmd, " %s %[^\n]", ped->args[ped->nargs], cmd) == 2)
    {
        ped->pipeflag = conpipe(ped);
        ped->nargs++;
        ped->args[ped->nargs] = malloc(sizeof(char) * MAX_SIZE);
    }

    strncpy(ped->args[ped->nargs], cmd, strlen(cmd)-1);
    ped->nargs++;
    //ped->args[ped->nargs] = malloc(sizeof(char) * MAX_SIZE);
    ped->args[ped->nargs] = NULL;

    return ped;

}

int main()
{
    char cwd[MAX_CLEN];
    getcwd(cwd, sizeof(cwd));
    char input[MAX_CLEN]; 
    char *exit  = "exit\n";
    char *cd = "cd";

    //Command to take input
    printf("tosh$ ");
    fgets(input, sizeof input, stdin);    

    while(strcmp(input, exit))
    {
       //Command to parse input
       struct cmdin *cmd;
       cmd = parse(input);
       //if there is not a pipeflag
       if(!cmd->pipeflag)
       {
            //Change directories
            if(!strcmp(cd, cmd->args[0]))
            {
                chdir(cmd->args[1]); 
                getcwd(cwd, sizeof(cwd));
            }
            else if(strcmp(input, exit))
            {
                //command to run input
                int child_pid;
                child_pid = fork();
                if(child_pid == 0)
                {
                    if(strcmp(cmd->args[1],cmd->args[0]))
                    {
                        execvp(cmd->cmd, cmd->args);
                    }
                    else
                    {
                        free(cmd->args[1]);
                        cmd->args[1] = NULL;
                        cmd->nargs--;
                        execvp(cmd->cmd, cmd->args);

                    }
                }
                else
                {
                    wait(&child_pid);
                }
            }
            freeze(*cmd);
            free(cmd);
        }       
        //Command to take input
        printf("tosh$ ");
        fgets(input, sizeof input, stdin);
    }
    return 0;
}

注意:这个 valgrind 的输入如下所示:

tosh$ ls -al
tosh$ exit

valgrind 堆和泄漏摘要如下:

HEAP SUMMARY:
==4901==     in use at exit: 4,096 bytes in 2 blocks
==4901==   total heap usage: 6 allocs, 4 frees, 24,600 bytes allocated
==4901== 
==4901== 2,048 bytes in 1 blocks are definitely lost in loss record 1 of 2 
==4901==    at 0x4C2B3F8: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==4901==    by 0x400A48: parse (tosh.c:46)
==4901==    by 0x400C97: main (tosh.c:86)
==4901== 
==4901== 2,048 bytes in 1 blocks are definitely lost in loss record 2 of 2
==4901==    at 0x4C2B3F8: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==4901==    by 0x400BDE: parse (tosh.c:62)
==4901==    by 0x400C97: main (tosh.c:86)
==4901== 
==4901== LEAK SUMMARY:
==4901==    definitely lost: 4,096 bytes in 2 blocks
==4901==    indirectly lost: 0 bytes in 0 blocks
==4901==      possibly lost: 0 bytes in 0 blocks
==4901==    still reachable: 0 bytes in 0 blocks
==4901==         suppressed: 0 bytes in 0 blocks

更新:根据要求。我的制作文件:

CC=gcc
CFLAGS=-g -Wall
TARGET=tosh

$(TARGET): $(TARGET).c
    $(CC) $(CFLAGS) -o $(TARGET) $(TARGET).c

Valgrind 版本:valgrind-3.7.0

gcc 版本:gcc (Ubuntu/Linaro 4.7.2-2ubuntu1) 4.7.2

回声 $LD_PRELOAD:

(这什么也没打印)

4

2 回答 2

4

没有设法重现。但是有几点注意事项。

  1. 使用可用的错误和警告。

    gcc -Wall -Wextra -pedantic …
    
  2. 检查返回值和操作是否成功。这对于减少错误至关重要,尤其是那些只有一英里外的用户案例才能揭示的错误。


if (!(foo = malloc(size))) {
    perror("mem");
}

...


/* This line is no good: */
sscanf(cmd, "%s %[^\n]", ped->cmd, cmd);
/*      |                           |
 *      +----------+----------------+
 *                 |
 *                 +--- Not good. UB. (Input / Output same.)
 */

可以添加一个例程来获取计数和大小...

i = sscanf(cmd, "%s%n %[^\n]%n", ped->cmd, &n1, cmd, &n2);

if (i < 2) {
    /* Debug print. */
    fprintf(stderr, 
            "%s:%-3d; "
            "sscanf => %d items and %d bytes str[%d]<%s>\n", 
            __FILE__, __LINENO_,
            i, n2, n1, ped->cmd
    );
}

等等

于 2013-10-31T07:29:43.803 回答
3

好的,我刚刚弄清楚问题出在哪里。大多数评论者是对的。那是我在不同的源代码上运行 valgrind。我一直在我的 ~/usr/bin (在我的 $PATH 中)中保留一份 tosh 的副本。我忘记用我最近的更改(包括修复我的内存泄漏问题的那些)来更新那段代码。

当我运行 valgrind 时,我从包含 tosh.c tosh 的目录中使用了以下命令:

$ valgrind tosh

我应该运行的是:

$ valgrind ./tosh

当我对命令进行更新时,代码运行良好,没有内存泄漏。感谢您的所有帮助!抱歉,我花了这么长时间才弄清楚这一点。

于 2013-10-31T06:56:05.077 回答