解析器在C中生成段错误

时间:2015-10-22 15:23:05

标签: c shell parsing

过去两周我一直试图让这个工作无效。 我有一个项目来创建一个实现解析和内置命令的shell。我遇到的问题是当我将char *传递给我的解析函数并返回时,当我尝试访问它的任何部分时,我得到了一个段错误。我已经尝试了不同的方法,包括一个持有char **的结构都有相同的问题,所以我猜测它是我的解析器的问题。我将不胜感激任何帮助。 parser.c的代码:

#define BUFSIZE 1024
#define TOK_BUFSIZE 64
#define TOK_DELIM " \t\r\n\a"

char*** Parse(char *line0){
char* null_ptr = 0;
char*** cmd = malloc(MAX_SIZE * sizeof(char**));
/*
char arg[] = argument
char* argv[] = argument array
char** cmd[] = array of argument arrays
*/
int bufsize = MAX_SIZE, cmdp = 0, argp = 0, com = FALSE, redir = FALSE;
char *token;
char* line = malloc(100*sizeof(char));
strcpy(line,line0);

token = strtok(line, TOK_DELIM);
while (token){
    if (*token == ';'){ // new command string
        char* tmp1 = malloc(BUFSIZE * sizeof(char));
        char** tmpa = malloc(BUFSIZE * sizeof(char*));
        strcpy(tmp1, token);
        tmp1[sizeof(token)] = null_ptr;
        tmpa[0]=tmp1;
        cmd[cmdp] = tmpa;
        argp = 0;
        cmdp++;
        com = FALSE;
        redir = FALSE;
    }
    else if (*token == '>' || *token == '<' || token == ">>"){  // redirects
        argp = 0;
        char* tmp1 = malloc(BUFSIZE * sizeof(char));
        char** tmpa = malloc(BUFSIZE * sizeof(char*));
        strcpy(tmp1, token);
        tmp1[sizeof(token)] = null_ptr;
        tmpa[argp]=tmp1;
        argp++;
        printf("Redirect: %s\n",tmp1);
        com = FALSE;
        redir = TRUE;
    }
    else if (*token == '|'){        // pipe
        printf("PIPE\n");
        cmdp++;
        argp = 0;
        com = FALSE;
    }
    else if (redir){        // redirect file name
        // redirect token stored in arg[]
        char* tmp1 = malloc(BUFSIZE * sizeof(char));
        char** tmpa = malloc(BUFSIZE * sizeof(char*));
        strcpy(tmp1, token);
        tmp1[sizeof(token)] = null_ptr;
        tmpa[argp]=tmp1;
        cmd[cmdp]=tmpa;
        argp = 0;
        cmdp++;
        redir = FALSE;
        com = FALSE;
        printf("File: %s\n", token);
    }
    else if (token == "&")      // background
    {
        cmdp++;
        argp = 0;
        char* tmp1 = malloc(BUFSIZE * sizeof(char));
        char** tmpa = malloc(BUFSIZE * sizeof(char*));
        strcpy(tmp1, token);
        tmp1[sizeof(token)] = null_ptr;
        tmpa[0]=tmp1;
        cmd[cmdp]=tmpa;

        printf("Background");
    }
    else if (!com && !redir){   // command entered
        argp = 0;
        char* tmp1 = malloc(BUFSIZE * sizeof(char));
        char** tmpa = malloc(BUFSIZE * sizeof(char*));
        strcpy(tmp1, token);
        tmp1[sizeof(token)] = null_ptr;
        tmpa[argp] = tmp1;

        argp++;
        printf("Command %s\n", token);
        com = TRUE;
    }
    else if (com){      // argument to command, all other redirects and pipes taken care of
        char* tmp1 = malloc(BUFSIZE * sizeof(char));
        char** tmpa = malloc(BUFSIZE * sizeof(char*));
        strcpy(tmp1, token);
        tmp1[sizeof(token)] = null_ptr;
        tmpa[argp] = tmp1;
        argp++;
            printf("Argument: %s\n", token);
            //cmd[cmdp] = argv;     // save current working argument array
            //cmdp++;
        }
        // end of if else statements

        token = strtok(NULL, TOK_DELIM);



    }   // end of while
    cmdp++;
    cmd[cmdp] = NULL;

return &cmd;
}

2 个答案:

答案 0 :(得分:0)

当我在命令行上编译代码时输入:

gcc /path/to/yourcodefilename.c -Wall -Wextra

但是将/path/to/yourcodefilename.c替换为包含最终调用函数的main函数的代码的实际文件名(我的文件是test2.c),我收到了警告。第一个是:

./test2.c:21: error: 'aaa' undeclared (first use in this function)
./test2.c:21: error: (Each undeclared identifier is reported only once
./test2.c:21: error: for each function it appears in.)

我收到了其中一些。 &#34; AAA&#34;被您在函数中使用的以前未定义的内容命名。这包括单词TRUE和FALSE。要更正此问题,您可以在程序的顶部使用:

#define FALSE n
#define TRUE y

其中n和y分别代表false和true。另一种纠正方法是包含包含&#34; TRUE&#34;的定义的头文件。和&#34;错误&#34;。

我在几行中注意到的第二件事是:

warning: assignment makes integer from pointer without a cast

确保您不会将数据从一种类型转换为另一种类型。例如,不要将字符变量设置为指针值。

例如,更改:

  tmp1[sizeof(token)] = null_ptr;

为:

  tmp1[sizeof(token)] = '\0';

因为指定char*的索引意味着指定char,而null_ptr的类型为char*char*char不相同。我所做的是分配了一个char的空值。

我希望这有助于您进行一些故障排除

答案 1 :(得分:0)

这里有几个问题:

  • 您分配cmd及其子阵列。您在函数末尾返回该数组的地址。地址的类型为char ****,这不是正确的返回类型。更糟糕的是:该地址是局部变量的地址,在返回后立即超出范围。返回从malloc获得的句柄:

    char ***Parse(char *line0)
    {
        char ***cmd = malloc(MAX_SIZE * sizeof(*cmd));
    
        // fill cmd
    
        return cmd;
    }
    
  • 您的代码不必要很长,主要是因为您编写了分配内存,复制字符串并显式空终止它的步骤。 (其他人已经指出你没有正确地进行空终止。你也分配了一个固定大小的1024字节的实际字符串长度,这是非常浪费的。)你可以写一个函数来复制字符串或使用非标准但广泛可用的strdup;这将使您的代码更易于阅读。

  • 所有临时分配都难以遵循。例如,在分支if (!com && !redir)中,您分配给tmpa,但您从未将该值存储在cmd中。重定向分支也是如此。

  • 启动新命令时也不清楚。在解析第一个令牌之前,遇到管道之后或遇到分号之后应该有一个新命令。您还可以启动重定向和背景&符号的新命令。

  • 比较token == ">>"始终为false:tokenline中的地址,">>"是存储在静态内存中的字符串文字。您应该使用strcmp来比较两个字符串。

通常,您希望在cmdp增加时分配新列表。在这种情况下,argp将重置为零。否则,您只需附加到当前命令。

我认为通过将一切都视为特殊而使事情复杂化。我建议简化代码并暂时保留重定向和背景。调用命令时可以轻松解析它们。 (您的代码使用redircom设置状态,但它在重定向后从不强制执行文件名。例如,当所有令牌都到位时,您可以轻松地执行此操作。)

下面的代码只将管道和分号视为命令分隔符。当命令是管道时,管道令牌将添加到以下命令之前:

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

#define MAX_SIZE 32
#define TOK_DELIM " \t\r\n\a"

char *sdup(const char *str)
{
    size_t len = strlen(str);
    char *dup = malloc(len + 1);

    if (dup) {
        memcpy(dup, str, len);
        dup[len] = '\0';
    }

    return dup;
}

char ***parse(char *line0)
{
    char *token;
    char *line = sdup(line0);

    token = strtok(line, TOK_DELIM);
    if (token == NULL) return NULL;

    char ***cmd = malloc(MAX_SIZE * sizeof(char **));

    int cmdp = 0;
    int argp = 0;

    cmd[0] = malloc(MAX_SIZE * sizeof(*cmd[0]));

    while (token) {
        if (strcmp(token, ";") == 0 || strcmp(token, "|") == 0) {
            // begin new command
            cmd[cmdp][argp++] = NULL;

            cmdp++;
            if (cmdp + 1 == MAX_SIZE) break;

            argp = 0;
            cmd[cmdp] = malloc(MAX_SIZE * sizeof(*cmd[0]));

            // prepend pipe token
            if (*token == '|') {
                cmd[cmdp][argp++] = sdup(token);
            }
        } else {
            // append to current command
            if (argp + 1 < MAX_SIZE) {
                cmd[cmdp][argp++] = sdup(token);
            }
        }

        token = strtok(NULL, TOK_DELIM);
    }

    // null-terminate arg and cmd lists
    cmd[cmdp][argp] = NULL;
    cmdp++;
    cmd[cmdp] = NULL;

    return cmd;
}

int main()
{
    char ***cmd = parse("echo start ; ls -l | wc > output ; echo stop");
    char ***p = cmd;

    while (*p) {
        char **q = *p;

        while (*q) {
            printf("'%s' ", *q);
            free(*q);
            q++;
        }
        puts("");
        free(*p);
        p++;
    }    

    free(cmd);

    return 0;
}

进一步评论:

  • 我不确定当前格式是否适合该任务。最好有一个树结构来处理管道,分号以及&&||,然后让叶节点包含参数链接列表的命令。

  • 使用strtok进行标记需要在所有标记之间使用空格,但标点符号通常可以在没有显式空格的情况下编写,例如:"./a.out>kk&"。所以你需要一种更好的解析方法。

  • 目前,您为每个字符串分配空间,以后必须释放。如果您创建一个令牌结构,将令牌描述为原始字符串的只读视图,则可以不进行分配。但是,视图不是以空值终止的,因此您需要比较一下这对于启动指针和长度的工作,例如。