Как заполнить массив указателей на символы аргументами, взятыми из scanf?

Я пытаюсь написать ОЧЕНЬ простую программу оболочки на C. Проблема, с которой я столкнулся, заключается в попытке заполнить мой массив указателей символов argv словами, взятыми из ввода. Когда я пытаюсь распечатать содержимое массива argv после попытки заполнить его с помощью функции parse() ниже, я получаю ошибку сегментации. Я знаю, что это означает, что я, вероятно, пытаюсь получить доступ к части массива argv, которая находится за пределами границ. Однако даже при предоставлении только одного аргумента для заполнения массива я все равно получаю segfault. Вызов printf, используемый для печати argc, возвращает правильное значение argc на основе ввода, но второй вызов printf с *argv[0] вызывает ошибку segfault. Мне интересно, связана ли моя ошибка с тем, как я пытаюсь распечатать содержимое argv, или ошибка связана с тем, что я пытаюсь неправильно заполнить argv.

РЕДАКТИРОВАТЬ: я должен добавить, что функция getword() принимает строку текста и возвращает первое слово, разделенное пробелами, и ряд других разделителей. Я могу опубликовать все разделители, на которые он разбивает слова, если это необходимо, но я не думаю, что проблема связана с getword().

РЕДАКТИРОВАТЬ 2: добавлено в заголовочный файл и включено выражение #include в main.

РЕДАКТИРОВАТЬ 3: добавлена ​​функция getword в main() и getword.h ниже p2.h

Вот p2.h, заголовочный файл, включенный в main:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "getword.h"
#include <signal.h>

#define MAXITEM 100

получитьслово.ч:

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

#define STORAGE 255

int getword(char *w);

int parse(char *, char *[]);

Вот основная функция:

#include "p2.h"
int main() {
    pid_t pid, child_pid;
    int argc, inputRedirect;
    char *devNull;
    devNull = (char *) malloc(10);
    strcpy(devNull, "/dev/null");
    char *argv[MAXITEM];
    char commandLine[STORAGE];


    for (;;) {
        printf("p2: ");
        scanf("%s", commandLine);
        argc = parse(commandLine, argv);
        printf("argc = %d\n", argc);

        if(argc == 0)
            continue;
        printf("*argv = %s\n", *argv[0]);
        child_pid = fork();
        if (child_pid < 0) {
            printf("Cannot fork! Terminating...");
            exit(1);
        } else if (child_pid == 0) {
            inputRedirect = open(devNull, O_RDONLY);
            dup2(inputRedirect, STDIN_FILENO);
            close(inputRedirect);
            execvp(*argv, argv);
        }
        else {
            for(;;) {
                pid = wait(NULL);
                if(pid == child_pid)
                   break;
            }
            printf("Child's pid is %d\n", child_pid);
        }
    }
    killpg(getpid(), SIGTERM);
    printf("p2 Terminated.\n");
    exit(0);
}

int parse(char *commandLine, char *argv[]) {
    int i, argc = 0;
    char *commandPointer = commandLine;
    while (*commandPointer != '\0') {
        *argv = commandPointer;
        argc++;
        getword(commandPointer);
    }
    *commandPointer = '\0';
    *argv = '\0';
    return argc;
}

получитьслово.с:

#include "getword.h"
#include <stdlib.h>

/*Function Prototypes*/
int tilde(char *p, int i);
int BSFollowedByMetaCharacter(int c, char *w);


int getword(char *w) {

    int c;
    int index = 0;

    /*This while loop removes all leading blanks and whitespace characters
     * The if statement then tests if the first character is a new line or
     *  semicolon metacharacter*/
    while ((c = getchar()) == ' ' || c == '\t' || c == '\n' || c == ';') {
        if (c == '\n' || c == ';') {
            w[index] = '\0';
            return 0;
        }
    }

    /*This if statement calls ungetc() to push whatever character was taken
     * from the input stream in the previous while loop back to the input
     * stream. If EOF was taken from the input stream, ungetc() will return EOF,
     * which will then cause getword() to return -1, signalling that it reached
     * the End Of File. */
    if (ungetc(c, stdin) == EOF)
        return -1;

    /*This if statement deals with some of the "non-special" metacharacters.
     * If one of these metacharacters is pulled from the input stream by getchar(),
     * it is stored in w and null-terminated. getword() then returns the length of
     * the current string stored in w. If getchar() pulls anything besides one of the
     * specified metacharacters from the input stream, it is then returned using ungetc() after
     * the if statement.*/
    if ((c = getchar()) == '<' || c == '>' || c == '|' || c == '&') {
        w[index++] = c;
        int d = getchar();
        if (c == '>' && d == '>')
            w[index++] = d;
        else {
            ungetc(d, stdin);
        }
        w[index] = '\0';
        return index;
    }
    ungetc(c, stdin);

    /*This while statement handles plain text from the input stream, as well as a few 'special'
     * metacharacters. It also ensures that the word scanned is shorter than STORAGE-1 bytes.*/
    while ((c = getchar()) != ' ' && c != '<' && c != '>' && c != '|'
        && c != ';' && c != '&' && c != '\t' && c != '\n' && c != '\0'
        && index <= STORAGE - 1) {
        if (c == '~') {
            int *ip = &index;
            index = tilde(&w[index], *ip);
            continue;
        }/*END IF*/
        else if (c == '\\') {
            int d = c;
            c = getchar();
            if (BSFollowedByMetaCharacter(c, w)) {
                w[index++] = c;
                continue;
            } else {
                w[index++] = d;
            }

        }/*END ELSE IF*/
        w[index] = c;
        index++;
    }/*END WHILE*/

    ungetc(c, stdin);/*This final ungetc() call is used to push any meta characters*/
    w[index] = '\0'; /*used as delimiters back to the input stream, to be retrieved*/
    return index;    /*at the next call of getword().                                      */
}/*END getword()*/

int tilde(char *cp, int i) {
    int *ip;
    ip = &i;
    char *p = cp;
    char *o;
    o = (strcpy(p, getenv("HOME")));
    int offset = strlen(o);
    *ip = *ip + offset;
    return i;
}

int BSFollowedByMetaCharacter(int c, char *w) {
    if (c == '~' || c == '<' || c == '>' || c == '|' || c == ';' || c == '&'
        || c == ' ' || c == '\t' || c == '\\') {
        return 1;
    } else {
        return 0;
    }
}

person trawww    schedule 25.11.2013    source источник
comment
char *devNull; devNull = (char *) malloc(10); strcpy(devNull, "/dev/null"); это худшее, что вы можете сделать. Вы выделяете кучу без всякой причины, не можете освободить выделенную память, что приводит к ее утечке. Вы также возвращаете возвращаемое значение malloc(), что неверно. Почему бы просто не написать const char *devNull = "/dev/null";?   -  person    schedule 25.11.2013
comment
Спасибо за вклад @H2CO3, я внес предложенные вами изменения. Я думал, что, поскольку указатель devNull будет использоваться во всей программе, выделение пространства в куче будет иметь больше смысла, но упустил из виду идею const char *. Однако я все еще получаю segfault при попытке распечатать argv. Должен ли я попытаться напечатать его, дважды разыменовав argv[0]? например: printf(*argv = %s\n, **argv[0]);?   -  person trawww    schedule 25.11.2013
comment
Кстати, вы пытались запустить свой код в отладчике?   -  person    schedule 25.11.2013
comment
Поскольку вы пытаетесь опубликовать код, с которым работаете (вместо фрагмента кода), я предлагаю вам опубликовать полный код, который включает необходимые заголовки и определения макросов, и вы сможете для компиляции и запуска и воспроизведения вашей проблемы.   -  person starrify    schedule 25.11.2013
comment
@ H2CO3 Да, использование gdb в программе подтверждает, что segfault возникает при вызове printf при попытке распечатать содержимое argv[0]   -  person trawww    schedule 25.11.2013
comment
@trawww, тогда вы должны попробовать изменить эту строку printf на `printf(*argv = %s\n, argv[0]);`, поскольку argv[0] уже имеет тип char* и не должен разыменовываться при использовании для %s` `   -  person starrify    schedule 25.11.2013
comment
@starrify После внесения этого изменения и запуска p2 через gdb я все еще получаю segfault, но теперь он говорит, что он находится по адресу 0xff242cb0 в strlen () из /lib/libc.so.1. Единственный экземпляр strlen() во всей моей программе находится в функции getword(), которую я загружу после этого комментария, но она вызывается только тогда, когда во входных данных присутствует символ тильды. Я ввел hello world в качестве входных данных и получаю сообщение об ошибке, поэтому я не понимаю, почему вообще вызывается strlen.   -  person trawww    schedule 25.11.2013
comment
@trawww Ну, я пока не знаю причину, но предлагаю вам проверить возвращаемое значение getenv, поскольку оно может вернуть NULL, если элемент не найден, тогда strcpy может ничего не делать (однако я думаю, что это вызовет исключение) и оставьте строку p без изменений. Тогда strlen может выйти из строя, так как данные поступают из неинициализированного массива commandLine.   -  person starrify    schedule 25.11.2013
comment
В листинге кода вы не меняете *commandPointer. Таким образом, цикл должен быть бесконечным. Кроме того, в index = tilde(&w[index], *ip);, почему бы не отправить только ip и не обработать его в функции?   -  person asif    schedule 25.11.2013
comment
проверка возвращаемого значения getenv по-прежнему дает мне то же сообщение об ошибке, выполняемое через gdb, которое я копирую и вставляю ниже. Программа получила сигнал SIGSEGV, ошибка сегментации. 0xff242cb0 в strlen() из /lib/libc.so.1   -  person trawww    schedule 25.11.2013
comment
@asif Я думал, что вызов getword(commandPointer); продвигает указатель, который затем изменит commandPointer. Я успешно печатаю значение argc, которое происходит ПОСЛЕ вызова parse(commandLine, argv) в main(). Если бы цикл был бесконечным, разве он никогда не возвращался бы из синтаксического анализа? Или это неправильно?   -  person trawww    schedule 25.11.2013


Ответы (1)


Функции в getword.c кажутся правильными. Ваша проблема в функции parse.

Чтобы использовать execvp, содержимое argv должно быть следующим (ввод: "hello world"):

argv[0] -> "hello"
argv[1] -> "world"
argv[2] -> NULL

Здесь argv — это массив указателей на символы. Но в функции parse вы обрабатываете argv как простые указатели символов здесь:

*argv = commandPointer;

и тут:

*argv = '\0';

Измените свою функцию синтаксического анализа на что-то вроде этого:

int parse(char *commandLine, char *argv[]) {
    int argc = 0;
    char *commandPointer;
    argv[argc++] = commandLine;

    do{
        commandPointer = (char*)malloc(sizeof(char) * STORAGE);
        argv[argc++] = commandPointer;
        getword(commandPointer);
    }while(*commandPointer != '\0');
    argv[argc] = NULL;
    return argc;
}

Теперь вы должны освободить выделенную память после дерева if-else, например:

for(int i = 0; i < argc; i++) free(argv[i]);
person asif    schedule 25.11.2013
comment
я реализовал функцию разбора дословно, но все равно получил segfault. После изменения - person trawww; 25.11.2013
comment
Какая функция дает вам segfault сейчас? Правильно ли он возвращает форму parse? Он по-прежнему печатает правильное значение argc? - person asif; 25.11.2013
comment
удаление первого символа * в *argv[argc++] = commandPointer; вроде делал. argv не заполнен какими-либо входными данными, которые я ввожу в main(), и я могу выполнять итерацию и печатать каждую запись массива. Спасибо @asif! - person trawww; 25.11.2013
comment
извините, мой плохой ... :( Отредактировано и исправлено. - person asif; 26.11.2013