【问题标题】:Array changing values after fork()数组在 fork() 之后改变值
【发布时间】:2011-12-06 22:10:22
【问题描述】:

出于学习目的,我正在尝试制作一个简单的 shell 副本(用 C 语言)。下面的代码部分显示了我现在的位置:

#define TRUE ( 1 )
#define NUMBEROFARGUMENTS ( 5 )

void execute(char** input){

    pid_t pid;
    pid = fork();   

    if (pid == 0){
        printf("Spawned foreground process pid: %d\n", getpid());
        execvp(input[0], input);
        _exit(1);
    } else {
        waitpid(pid, NULL, 0);
        printf("Foreground process %d terminated.\n", pid);
    }

}

void check_input(char** input){

    char in[70];
    char *tok_inline;

    gets(in);   
    tok_inline = strtok(in," ");

    int i;
    for(i=0; i < NUMBEROFARGUMENTS; i++){
        input[i] = tok_inline;
        tok_inline = strtok(NULL," ");
    }

}

int main(){

    char* input[NUMBEROFARGUMENTS];

    printf("MiniShell v2.5\n");

    while ( TRUE ){

        printf("--> ");

        check_input(input);

        if ( strcmp ( input[0], "cd" ) == 0 ){
            chdir(input[1]);
        } else if ( strcmp( input[0], "exit" ) == 0 ){
            exit(0);
        } else {    
            execute(input);
        }

    }
        exit(0);
}

然而,我遇到了一个我似乎无法找到答案的问题。在执行方法中执行 fork() 之后,字符串数组 input 的值似乎与分叉之前的值不同。如果我尝试在分叉之前打印出存储在输入中的字符串,一切似乎都井井有条,但在分叉后输入不再包含它应该具有的字符串,因此 execvp() 无法正确执行。

我是否遗漏了什么或者我误解了 fork() 等的工作原理?据我所知,上面的代码应该做我想做的事。

请多指教,谢谢。

【问题讨论】:

  • 不要使用gets。验证您的输入。检查您的返回值。
  • 如果您所说的平台是指操作系统,那么它就是 Windows Vista。
  • Windows 不会分叉。你可能在上面使用了一些 i-wish-i-was-*nix 层,比如 Cygwin。
  • 是的,我用的是Cygwin,用g++编译。
  • OP:您应该在check_input 内构建for 循环,以便在strtok 返回NULL 时它也会终止。您还应该考虑 fgets 而不是 gets。在我的答案中添加了一条可能值得一读的注释。

标签: c arrays string exec fork


【解决方案1】:

你的问题与分叉无关。

char in[70]; 在堆栈上。 check_input 处理它,但在它返回后,下一个函数调用将开始覆盖该内存位置,从而覆盖令牌。使用malloc 进行内存分配。

【讨论】:

  • 有没有一种方法可以在不使用 malloc 的情况下构建程序?
  • @bzander:在堆栈上分配的固定大小的缓冲区,其中有 tok_inline 复制到其中。
  • 全局变量,main() 中的变量都应该没问题。只是不要返回局部变量。
【解决方案2】:

从不将指向局部变量的指针返回给函数的调用者。

char in[70];
/* ... */
tok_inline = strtok(in," ");

int i;
for(i=0; i < NUMBEROFARGUMENTS; i++){
    input[i] = tok_inline;
    /* ... */

strtok 的返回值是指向in 内部位置的指针,而不是副本或类似的东西。由于in 是在堆栈上分配的,而不是动态使用malloc,所以当check_input 返回并且堆栈帧被销毁时,它和那些指针将不再有效。

保留指向这些不再存在的任何变量的指针将导致未定义的行为。正如您所注意到的,当堆栈上的该位置被重用于其他任何东西(其他变量、函数调用等)时,您的(无效)字符串将会被弄乱。

另一种方法是在tok_inline 上使用strdup,它使用malloc 来复制字符串。

编辑:您还应该在for 循环的条件下检查strtok 的返回值(即tok_inline),如果返回NULL,则也终止- 表示没有更多的令牌。

将您的 for 循环签名更改为:for(i=0; i &lt; NUMBEROFARGUMENTS &amp;&amp; tok_inline; i++)

另外,考虑使用fgets(in, 70, stdin); 而不是gets,这是不安全的,如果输入的大小大于缓冲区的大小,可能会导致缓冲区溢出。另请注意,换行符 '\n' 会存储在缓冲区的末尾(如果合适)。

EDIT2:另外,传递给execvp 的第二个参数(argv)是一个以NULL 结尾的字符串数组,即。 {"ls", NULL }。为此:

  • check_input 中的for 循环之后添加input[i] = NULL;
  • char* input[NUMBEROFARGUMENTS]; 更改为char* input[NUMBEROFARGUMENTS + 1];,这样NULL 的数组中就多了1 个元素。

另外,如果您决定使用fgets,则需要从缓冲区末尾删除'\n'(如果存在)。您的 check_input 函数可能类似于:

void check_input(char** input){

    char in[70];
    char *tok_inline;
    size_t len;

    fgets(in, 70, stdin);
    len = strlen(in);
    if (in[len - 1] == '\n')
        in[len - 1] = '\0';

    tok_inline = strtok(in," ");

    int i;
    for(i=0; i < NUMBEROFARGUMENTS && tok_inline; i++){
        input[i] = strdup(tok_inline);
        tok_inline = strtok(NULL," ");
    }

    input[i] = NULL;
}

EDIT3:关于您关于内存泄漏的最终查询,是的,您应该释放内存。您不需要在子进程中,因为当它调用 exec 时,它的进程将被新进程的进程替换。

但是,您应该释放父级分配的内存。既然我们已经 NULL 终止了我们的数组,只需将以下内容添加到 executeelse 块中的任何位置:

while (*input)
    free(*input);

【讨论】:

  • @bzander:另外,execvp 需要一个 NULL 终止数组 - 添加了另一个编辑。
  • 谢谢,这似乎很有魅力。您的解决方案也解决了我的后续问题(我的“读者”似乎没有正确处理“。”)。只是一个问题,您不打算在某些时候释放使用 strdup() 时分配的内存以避免可能的内存泄漏吗?
  • @bzander:是的,你应该为父母。检查最新的编辑。
【解决方案3】:

注意将输入从 gets() 复制到堆栈变量。线

 input[i] = tok_inline;

是您的问题所在。你必须将tok_inline 字符串复制到一个fork 后会出现的地方。就目前而言,当您在父进程中执行 printf() 时,它可能仍然存在,但不会在您的子进程中。

你必须这样做(未验证):

input[i] = malloc((strlen(tok_inline)+1)*sizeof(*tok_inline));
strcpy(input[i], tok_inline);

这是一种相当糟糕的方法,因为你会泄漏内存,但它明白了我的意思。

【讨论】:

  • strdup 将更有效地执行上述操作的意义上,这只是“可怕的”。您需要malloc 内存才能使 OP 的代码正常工作。
  • .. 或使用堆栈的有效部分。
  • 那么你需要在堆栈上分配一个固定大小的缓冲区来传递给check_input,如果你不想要这个或者你的输入太大怎么办?实际上inchar[70],所以固定大小的缓冲区不会太糟糕,但是要让OP 的当前代码工作,您仍然需要malloc 内存来存储input 指向的字符串。跨度>
猜你喜欢
  • 2021-08-02
  • 1970-01-01
  • 2016-01-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多