execvp()导致EXC_SOFTWARE和奇怪的cin.getline循环?

时间:2011-02-05 02:50:54

标签: c++ fork execvp

我在Mac OSX 10.6.6和XCode 3.2.4上运行了一些代码,我有一些非常标准的代码:fork(),如果pid == 0则execvp带有命令和args(args包括command作为数组中的第一个元素,并且数组以null结尾。)

我们在我的操作系统类中讨论这个问题,我们的任务是编写一个简单的shell。使用args和开关运行命令,重定向(<和>)和管道(|)。我遇到了几个问题。

1)有时我在调试时得到EXC_SOFTWARE信号(到目前为止,如果我在XCode之外运行应用程序,我还没有得到它,但我是Mac的新手,如果我不知道那会是什么样子所做的那样)

2)有时下一个命令的getline会被其他cout打印出来的垃圾。这开始永远循环,指数地打破。我已经测试了打印getpid()的每个提示,只有开始的过程打印出这些,我似乎没有意外的“叉炸弹”。

这是我到目前为止所拥有的:

#include <iostream>
#include <string>
#include <unistd.h>

using namespace std;

char** Split(char* buffer, int &count) {
    count = 1;
    for (int i = 0; i < strlen(buffer); i++) {
        if (buffer[i] == ' ') {
            count++;
        }
    }
    const char* delim = " ";
    char* t = strtok(buffer, delim);
    char** args = new char*[count + 1];
    for (int i = 0; i < count; i++) {
        args[i] = t;
        t = strtok(NULL, delim);
    }
    args[count] = 0;
    return args;
}

void Run(char** argv, int argc) {
    int pid = 0;
    if ((pid = fork()) == 0) {
        //for testing purposes, print all of argv
        for (int i = 0; i < argc; i++) {
            cout << "{" << argv[i] << "}" << endl;
        }
        execvp(argv[0], argv);
        cout << "ERROR 1" << endl;
        exit(1);
    } else if (pid < 0) {
        cout << "ERROR 2" << endl;
        exit(2);
    }
    wait(NULL);
}

int main(int argc, char * const argv[]) {
    char buffer[512];
    char prompt[] = ":> ";
    int count = 0;
    while (true) {
        cout << prompt;
        cin.getline(buffer, 512);
        char **split = Split(buffer, count);
        Run(split, count);
    }
}

这正是我所拥有的,你应该能够剪切,粘贴和构建。

我不是C ++中最好的,当我不删除split时可能会出现内存泄漏但我的主要关注点是EXC_SOFTWARE信号,看看我的循环问题出了什么问题。有什么想法吗?

编辑:

分配需要非常有限的错误检查,我假设所有输入都是正确的。通过正确我的意思是正确格式化和限制我的应用程序运行命令,即没有奇怪的空间计数,没有&amp;运行异步,没有多管道命令等。

2 个答案:

答案 0 :(得分:0)

您假设输入行包含一个比空格更多的标记。如果输入行为空,以空格结束或开始或包含多个连续空格,则此假设可能会失败。在这些情况下,对strtok的一个调用将返回NULL,当您尝试在Run中打印该参数时,这将使分叉进程崩溃。这是我遇到问题的唯一案例;如果您遇到其他人,请说明您的意见。

为了避免这种假设,你可以用strtok进行计数,就像你进行标记化一样。这通常是一个好主意:如果你需要两件事情重合并且你可以用同样的方式完成它们,那么如果你采用不同的方式,那么你会引入一些额外的错误来源。

答案 1 :(得分:0)

一个问题是你没有检查cin.getline()的返回,所以如果输入EOF,代码会进入紧密循环。你也在泄漏记忆。

尝试:

while (cout << prompt && cin.getline(buffer, sizeof(buffer))
{
    int count = 0;
    char **split = Split(buffer, count);
    Run(split, count);
    delete[] split;
}

Split()中的代码并不能真正处理空白行。当唯一的参数是空指针时,似乎需要运行execvp(),如果返回一个空行就会发生这种情况。


我能够运行多个简单的命令(例如'vim makefile'和'make shell'以及'ls -l'和'cat shell.cpp'等等 - 我甚至做了一些超过两个的命令参数)好吧,我可以用 Control-D 退出命令(shell),依此类推。我已修复它,因此编译时没有来自g++ -O -Wall -o shell shell.cpp的警告。我没有修复拆分代码,以便正确处理空行或所有空行。

#include <iostream>
#include <string>
#include <unistd.h>

using namespace std;

char** Split(char* buffer, int &count) {
    count = 1;
    for (size_t i = 0; i < strlen(buffer); i++) {  // #1
        if (buffer[i] == ' ') {
            count++;
        }
    }
    char** args = new char*[count + 1];
    const char* delim = " ";
    char* t = strtok(buffer, delim);
    for (int i = 0; i < count; i++) {
        args[i] = t;
        t = strtok(NULL, delim);
    }
    args[count] = 0;
    return args;
}

void Run(char** argv, int argc) {
    int pid = 0;
    if ((pid = fork()) == 0) {
        //for testing purposes, print all of argv
        for (int i = 0; i < argc; i++)
        {
            if (argv[i] != 0)  // #2
                cout << "{" << argv[i] << "}" << endl;
            else
                cout << "{ NULL }" << endl;  // #3
        }
        execvp(argv[0], argv);
        cout << "ERROR 1" << endl;
        exit(1);
    } else if (pid < 0) {
        cout << "ERROR 2" << endl;
        exit(2);
    }
    wait(NULL);
}

int main(int argc, char * const argv[]) {
    char buffer[512];
    char prompt[] = ":> ";
    while (cout << prompt && cin.getline(buffer, sizeof(buffer)))  // #4
    {
        int count = 0;
        char **split = Split(buffer, count);
        if (count > 0)  // #5
            Run(split, count);
        delete[] split;  // #6
    }
}

我已经标记了重大变化(它们大多不是那么大)。我正在使用Mac OS X 10.6.6上的GCC 4.2.1进行编译。

我无法轻易解释您在缓冲区中看到的垃圾字符。