【问题标题】:Parser generating segfaults in C解析器在 C 中生成段错误
【发布时间】:2016-01-21 22:45:27
【问题描述】:

在过去的 2 周里,我一直试图让它工作,但无济于事。 我有一个项目来创建一个实现解析和内置命令的 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 周我一直试图让它工作但无济于事' - 你是在调试器下运行它吗?如果不是,为什么不呢?如果有,你发现了什么?
  • @Martin James 调试器说段错误在我的执行函数中,我尝试访问数组中的参数,当我尝试访问 cmd[n][n] 时它会出现段错误,这应该是字符*
  • tmp1[sizeof(token)] = null_ptr;: token 是一个指针,所以它的计算结果总是相同的(即 64 位上的 8 和 32 位上的 4)。
  • 当我尝试访问应该是char *cmd[n][n] 时出现段错误您在显示的代码中尝试在哪里访问cmd[n][n]?跨度>
  • tmp1[sizeof(token)] = null_ptr; 为空,在第 9 个索引处终止。 (sizeof token = 8 在 x86_64 上,4 在 x86 上)。

标签: c shell parsing


【解决方案1】:

当我在命令行上输入以下代码编译您的代码时:

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

但是将/path/to/yourcodefilename.c 替换为包含最终调用您的函数的主函数的代码的实际文件名(我的文件是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.)

我收到了其中一些。 “aaa” 被您在函数中使用的、之前未定义的名称替换。这包括单词 TRUE 和 FALSE。要更正此问题,您可以在程序顶部使用:

#define FALSE n
#define TRUE y

其中 n 和 y 是分别代表 false 和 true 的数字。另一种更正它的方法是包含包含“TRUE”和“FALSE”定义的头文件。

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

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

我希望这可以帮助您解决一些问题

【讨论】:

  • tmp1[sizeof(token)]反正肯定是个错误,指针的大小与字符串的长度无关
【解决方案2】:

这里有几个问题:

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

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

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

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

  • 比较token == "&gt;&gt;" 将始终为假:tokenline 中的地址,"&gt;&gt;" 是存储在静态内存中的字符串文字。您应该使用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;
}

补充说明:

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

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

  • 目前,您为每个字符串分配空间,以后必须释放这些空间。如果您创建一个将令牌描述为原始字符串的只读视图的令牌结构,则无需分配即可。但是,视图不是以空值结尾的,因此您需要比较方法,例如,对起始指针加长度起作用。

【讨论】:

    猜你喜欢
    • 2023-03-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多