【问题标题】:Program strangely returning to the top of the loop程序奇怪地返回到循环的顶部
【发布时间】:2023-03-31 20:04:01
【问题描述】:

我想在这个问题的开头说我是 C 的新手,因此对它很糟糕,所以我提前为任何明显的错误或糟糕的风格道歉。另外,在向您展示我的代码之前,我不确定如何介绍问题,所以这里是:

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


int main()
{

    int MAX_INPUT_SIZE = 200;
    volatile int running = 1;
    while (running)
    {

        char input[MAX_INPUT_SIZE];
        char *tokens[100];
        const char *cmds[] = { "wait", "pwd", "cd", "exit" };
        char *cmdargs[100];

        printf("shell> ");
        fgets(input, MAX_INPUT_SIZE, stdin);

        // remove newline character at end
        int nl = strlen(input) - 1;
        if (input[nl] == '\n')
        {
            input[nl] = '\0';
        }

        // tokenize input string, put each token into an array
        char *space;
        space = strtok(input, " ");
        tokens[0] = space;

        int i = 1;
        while (space != NULL)
        {
            space = strtok(NULL, " ");
            tokens[i] = space;
            ++i;
        }

        // copy tokens after first one into string
        int noargscheck;
        if (tokens[1] != NULL)
        {
            noargscheck = 0;
            strcpy((char *)cmdargs, tokens[1]);
            for (i = 2; tokens[i] != NULL; i++)
            {
                strcat((char *)cmdargs, " ");
                strcat((char *)cmdargs, tokens[i]);
            }
        }
        else
        {
            noargscheck = 1;
        }

        // compare tokens[0] to list of internal commands
        int isInternal = -1;
        for (i = 0; i < 4; i++)
        {
            if (strcmp(tokens[0], cmds[i]) == 0)
            {
                isInternal = i;
            }
        }


        // internal commands
        char wd[200];
        if (isInternal != -1)
        {
            switch (isInternal)
            {
            case 0:
                // wait
                break;
            case 1:
                // pwd
                if (getcwd(wd, sizeof(wd)) == NULL)
                {
                    perror("getcwd() error!");
                }
                else
                {
                    printf("%s\n", wd);
                }
                break;
            case 2:
                // cd
                if (noargscheck)
                {
                    chdir("/home");
                }
                else if (chdir((const char *)cmdargs) != 0)
                {
                    perror("cd failed");
                }
                break;
            case 3:
                // exit
                exit(1);
                break;
            }
        }

        else
        {
            // external commands

            pid_t child_pid;
            switch (child_pid = fork())
            {
            case -1:
                perror("Fork failed");
                return 1;
            case 0:
                // child
                printf("\nHERE\n"); // for debugging
                execvp(tokens[0], cmdargs);
                break;
            }
        }
    }
}

当我使用输入echo hello world 运行此代码时,程序在以switch (child_pid=fork()) 开头的第二个switch 语句中成功进入case 0 情况,但输出结果出乎意料,如下所示:

输出:(包括在提示符下显示我输入的一行)

shell&gt; echo hello world(我的输入)

shell&gt;(这是我看不懂的部分)

HERE

shell&gt;(程序现在在提示符处等待下一个用户输入)

我无法弄清楚为什么要打印额外的 shell&gt; 提示。有人能看出问题吗?

编辑:修复了 execvp 的第一个参数。从"echo"(因为我很傻所以在那里)更改为tokens[0]

【问题讨论】:

  • 您能解释一下为什么您希望看到它吗?换句话说,描述你认为你的代码不应该导致这种行为的方式。
  • 我不想打印额外的shell&gt; ,即介于第一个shell&gt; HERE 之间的那个。 Dan 在下面指出,问题在于我的子进程从 execvp 报告错误,我没有检查,然后返回循环顶部。

标签: c loops switch-statement


【解决方案1】:

当你 fork 时,你现在有两个进程。您的孩子将打印HERE 消息,然后致电execvp。您没有检查 execvp 的返回值,因此它可能会返回错误。 cmdargs 应该是一个向量 - 即一个以空指针结尾的字符串指针数组。相反,您传递的是 execvp 一个字符串。换句话说,它需要一个char* [],这就是cmdargs,但是您之前错误地对待了cmdargs

例如,您说strcpy((char*)cmdargs, tokens[1]);。这会在*cmdargs 处放置一个字符串。字符串是由零个或多个非零字符组成的数组,后跟一个 8 位宽的 ascii NUL

char* cmdargs[] is a double pointer
you treat cmdargs as a single pointer and feed it to strcpy
cmdargs points to: 'H' 'e' 'l' 'l' 'o' '\0'

但是,这不是 execvp 想要的。 Execvp 想要一个向量,看起来更像这样:

char* cmdargs[] is a double pointer
cmdargs[0] is a single pointer
cmdargs[0] points to: 'H' 'e' 'l' 'l' 'o' '\0'
cmdargs[1] points to: 'w' 'o' 'r' 'l' 'd' '\0'
cmdargs[2] is a null pointer, indicating that it is the end of the vector

因此,execvp 无法找到向量的结尾而失败,返回-1。您无需对此进行测试,因此子进程与父进程一样继续回到循环的顶部,并且两个进程都打印shell&gt;

编辑:顺便说一句,argv 向量中的第一个字符串应该是正在执行的文件的名称 - 在这种情况下,echo,然后第二个和第三个字符串应该是第一个和第二个'参数' - 这里是 helloworld。这是提供给您调用的程序的 argv 向量,按照惯例,该向量中的第一个元素是被调用程序的名称。如果您忽略该约定,echo 将会非常混乱。

【讨论】:

  • 我明白了。指针一直是我的死因。我将如何修复 cmdargs 以向 execvp 发送正确的参数?
【解决方案2】:

cmdargs 被定义为一个包含 100 个字符串指针的数组,但您似乎将它用作单个字符串的一个 100 字节缓冲区。我也不明白你为什么要特别处理 token[1]。只有 token[0] 是特殊的,就是命令,其他都是参数。处理参数应该是一个循环

while (cmdargs[i++] = strtok(NULL, " "))

然后在 cmdargs 中为 execvp() 关闭 NULL 指针:

cmdargs[i] = NULL;

这是一个 shell,你也忘了等待子进程。在最后一个子进程完成之前,您将提示用户输入。最终的 switch-case 应该如下所示:

pid_t child_pid;
switch (child_pid = fork())
{
case -1:
    perror("Fork failed");
    return 1;
case 0:
    // child
    printf("\nHERE\n"); // for debugging
    execvp(tokens[0], cmdargs);
    perror("Exec failed");
    exit(1);
default:
    // parent
    int status;
    wait(&status);
    break;
}

【讨论】:

  • execvp() 不会正常返回,因为您的整个子进程开始运行新程序。如果它确实返回,这是一个错误,例如未找到要执行的程序,因此这是一条错误消息。子进程也必须退出。
  • perror 不是每次都打印吗?即使 execvp 有效?
  • 您将system()execvp() 混淆了。 system() 创建一个执行指定程序的子进程。在execvp() 的情况下,您已经使用fork() 创建了子进程,同样的子进程丢弃了您的程序,并开始执行指定的程序。它无法返回您的程序,因为它已经被丢弃了。
  • 明白了。感谢您的帮助。
  • 酷! fork/exec 系统调用首先很难理解,但是当你理解它时,它非常漂亮。 :-) 记住:fork() 返回两次,而 execvp() 根本不返回。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-07-01
  • 2021-04-15
  • 1970-01-01
  • 2016-07-12
  • 2011-04-25
  • 1970-01-01
相关资源
最近更新 更多