尽管尚未到达字符串末尾,strtok返回NULL

时间:2019-08-16 13:06:06

标签: c

我正在编写一个程序,该程序解析来自stdin的输入并根据输入调用函数。 我的程序应该处理的输入如下:

end //stops the program
report //prints a specific output
addent "ent_id"
delent "ent_id"
addrel "ent_id1" "ent_id2" "rel_id"
delrel "ent_id1" "ent_id2" "rel_id"

输入调用的函数与我的问题无关,但请注意传递给函数的所有参数都在引号之间。

这是代码

int main() {
    const char Comando[6][7] = { "addrel", "addent", "delrel", "delent", "report", "end" };
    const char spazio[2] = " ";
    const char newline[3] = "\n";
    const char quote[2] = "\"";
    char sample[100];
    char *temp;
    char *comandoIN;
    char *argomento1;
    char *dest;
    char *rel;

    RelHead = NULL;
    init_array();

    char *str = fgets(sample, 100, stdin);

    for (;;) {
        if (strncmp(sample, Comando[5], 3) == 0) {
            return 0;
        } else if (strncmp(sample, Comando[4], 6) == 0) {
            report();
        } else {
            temp = strtok(sample, newline);
            comandoIN = strtok(temp, spazio);
            argomento1 = strtok(NULL, quote);

            if (strncmp(Comando[1], comandoIN, 7) == 0) {
                addent(argomento1);
            } else if (strncmp(Comando[3], comandoIN, 7) == 0) {
                delent(argomento1);
            } else {
                temp = strtok(NULL, quote);
                dest = strtok(NULL, quote);
                temp = strtok(NULL, quote);
                rel = strtok(NULL, quote);

                if (strncmp(Comando[0], comandoIN, 7) == 0) {
                    addrel(argomento1, dest, rel);
                } else if (strncmp(Comando[2], comandoIN, 7) == 0) {
                    delrel(argomento1, dest, rel);
                }
            }
        }

        char *str = fgets(sample, 69, stdin);
    }
    return 0;
}

不正确的行为是由以下输入引起的:

addrel "The_Ruler_of_the_Universe" "The_Lajestic_Vantrashell_of_Lob" "knows"

,这导致strtok的最后两个调用分别返回NULL(而不是" "(空格)和"knows"(不带引号)。 此外,如果这是给程序的第一个输入,则它的行为正确,如果是最后一个,则下一个循环会将"knows"放入"comandoIN"变量中。这是我到目前为止发现的唯一导致此问题的输入,我认为这与第一次调用strtok删除换行符有关。

这是uni的一项工作,因此我们有几个输入来测试程序,我的程序通过了其中的前4个(每个测试大约有200个输入),所以我不太了解是什么导致了该错误。有什么想法吗?

2 个答案:

答案 0 :(得分:3)

这里的问题是输入:

addrel "The_Ruler_of_the_Universe" "The_Lajestic_Vantrashell_of_Lob" "knows"    

长度为77个字节(76个字符加上终止NULL)。

在循环结束时,您有:

char *str = fgets(sample, 69, stdin);

您声明缓冲区的长度为69。

如果它是第一个输入,为什么它的行为正确?

在进行for循环之前:

char *str = fgets(sample, 100, stdin);
for(;;)
...

这里使用的大小为100,因此如果您在启动程序后直接使用上面的输入,则可以使用它。

答案 1 :(得分:1)

使用strtok来使用不同的分隔符集来解析命令行会造成混乱并且容易出错。用一个简单的循环解析命令行并显式处理空格和引号,然后分派第一个单词会更简单。

这是一种较为系统的方法:

#include <stdio.h>

char *getarg(char **pp) {
    char *p = *pp;
    char *arg = NULL;
    while (*p == ' ')
         p++;
    if (*p == '\0' || *p == '\n')
        return arg;
    if (*p == '"') {
        arg = ++p;
        while (*p != '\0' && *p != '"')
            p++;
        if (*p == '"')
            *p++ = '\0';
    } else {
        arg = p++;
        while (*p != '\0' && *p != ' ' && *p != '\n')
            p++;
        if (*p != '\0')
            *p++ = '\0';
    }
    *pp = p;
    return arg;
}

int main() {
    char sample[100];
    char *cmd, *arg1, *arg2, *arg3;

    RelHead = NULL;
    init_array();

    while (fgets(sample, sizeof sample, stdin)) {
        char *p = sample;
        cmd = getarg(&p);
        arg1 = getarg(&p);
        arg2 = getarg(&p);
        arg3 = getarg(&p);

        if (cmd == NULL) {  // empty line
            continue;
        } else
        if (!strcmp(cmd, "end")) {
            break;
        } else
        if (!strcmp(cmd, "report")) {
            report();
        } else
        if (!strcmp(cmd, "addent")) {
            addent(arg1);
        } else
        if (!strcmp(cmd, "delent")) {
            delent(arg1);
        } else
        if (!strcmp(cmd, "addrel")) {
            addrel(arg1, arg2, arg3);
        } else
        if (!strcmp(cmd, "delrel")) {
            delrel(arg1, arg2, arg3);
        } else {
            printf("invalid command\n");
        }
    }
    return 0;
}