Массив, изменяющий значения после fork () - PullRequest
2 голосов
/ 07 декабря 2011

Я пытаюсь сделать простую реплику оболочки (в 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 () в методе execute, массив строк input не имеет тех же значений, что и перед разветвлением. Если я попытаюсь распечатать строки, хранящиеся во вводе, перед разветвлением, все будет в порядке, но после разветвления ввод больше не будет содержать строк, которые он должен иметь, и поэтому execvp () не будет работать должным образом.

Есть ли что-то, что я пропустил или неправильно понял, как работает fork () и т. Д.? Из того, что я могу сказать, приведенный выше код должен делать то, что я хочу.

Пожалуйста, совет, спасибо.

Ответы [ 3 ]

5 голосов
/ 07 декабря 2011

Ваша проблема не имеет ничего общего с разветвлением.

char in[70]; в стеке.check_input обрабатывает его, но после его возврата следующий вызов функции перезапишет эту ячейку памяти и, следовательно, перезапишет токены.Используйте malloc для выделения памяти.

1 голос
/ 07 декабря 2011

Никогда возвращает указатель на локальную переменную обратно в вызывающую функцию.

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 и кадр стека будет уничтожен.

Сохранение указателя на любую из этих переменных, которые больше не существуют, приведет к неопределенному поведению. Как вы заметили, когда эта позиция в стеке повторно используется для чего-либо еще (других переменных, вызовов функций и т. Д.), Ваши (недопустимые) строки будут испорчены.

Альтернативой является использование strdup на tok_inline, в котором malloc используется для создания дубликата строки.

РЕДАКТИРОВАТЬ: Вы также должны проверять возвращаемое значение strtok (то есть. tok_inline) в состоянии вашего for цикла, и завершаться также, если возвращается NULL это означает, что токенов больше нет.

Измените свою подпись цикла for на: for(i=0; i < NUMBEROFARGUMENTS && tok_inline; i++).

Кроме того, рассмотрите возможность использования fgets(in, 70, stdin); вместо gets, который небезопасен и может вызвать переполнение буфера, если размер ввода больше, чем размер вашего буфера. Также обратите внимание, что символ новой строки '\n' сохраняется в конце буфера, если он подходит.

EDIT2: Кроме того, второй аргумент (argv), переданный в execvp, представляет собой массив строк с окончанием NULL, т.е. {"ls", NULL }. Для этого:

  • Добавить input[i] = NULL; после цикла for в check_input
  • Измените char* input[NUMBEROFARGUMENTS]; на char* input[NUMBEROFARGUMENTS + 1];, чтобы у вас был еще 1 элемент в массиве для NULL.

Также, если вы решите использовать 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 завершили наш массив, просто добавьте следующее в любое место в блоке else execute:

while (*input)
    free(*input);
0 голосов
/ 07 декабря 2011

Будьте внимательны, скопировав ваши данные из gets () в переменную стека. Линия

 input[i] = tok_inline;

Это где ваши проблемы лежат. Вы должны скопировать строку tok_inline в место, которое будет после разветвления. Как бы то ни было, это может произойти, когда вы выполняете printf () в своем родителе, но это не будет в вашем дочернем процессе.

Вы должны сделать что-то вроде этого (не проверено):

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

Это довольно ужасный способ сделать это, так как вы потеряете память, но это понимает мою точку зрения.

...