C: создание массива строк из исходной строки с разделителями - PullRequest
5 голосов
/ 31 января 2010

Каким будет эффективный способ преобразования строки с разделителями в массив строк в C (не в C ++)?Например, у меня может быть:

char *input = "valgrind --leak-check=yes --track-origins=yes ./a.out"

Исходная строка всегда будет иметь только один пробел в качестве разделителя.И я хотел бы получить массив malloc'ов строк malloc char *myarray[] такой, что:

myarray[0]=="valgrind"
myarray[1]=="--leak-check=yes"
...

Edit Я должен предположить, что в произвольном количестве токеновinputString, поэтому я не могу просто ограничить его до 10 или чего-то еще.

Я пытался найти грязное решение с помощью strtok и связанного списка, который я реализовал, но valgrind жаловался так сильно, что я далup.

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

Ответы [ 5 ]

2 голосов
/ 31 января 2010

если у вас есть все входные данные в input для начала, то вы никогда не сможете иметь больше токенов, чем strlen(input). Если вы не разрешите "" в качестве токена, то вы никогда не сможете получить более strlen(input)/2 токенов. Поэтому, если input не равно огромно , вы можете смело писать.

char ** myarray = malloc( (strlen(input)/2) * sizeof(char*) );

int NumActualTokens = 0;
while (char * pToken = get_token_copy(input))
{ 
   myarray[++NumActualTokens] = pToken;
   input = skip_token(input);
}

char ** myarray = (char**) realloc(myarray, NumActualTokens * sizeof(char*));

В качестве дальнейшей оптимизации вы можете сохранить input и просто заменить пробелы на \ 0 и поместить указатели в буфер input в myarray []. Нет необходимости в отдельном malloc для каждого токена, если только по какой-то причине вам не нужно для их отдельного освобождения.

2 голосов
/ 31 января 2010

Что-то вроде:

char* string = "valgrind --leak-check=yes --track-origins=yes ./a.out";
char** args = (char**)malloc(MAX_ARGS*sizeof(char*));
memset(args, 0, sizeof(char*)*MAX_ARGS);

char* curToken = strtok(string, " \t");

for (int i = 0; curToken != NULL; ++i)
{
  args[i] = strdup(curToken);
  curToken = strtok(NULL, " \t");
}
1 голос
/ 31 января 2010

Из справочной страницы strsep(3) в OSX:

   char **ap, *argv[10], *inputstring;

   for (ap = argv; (*ap = strsep(&inputstring, " \t")) != NULL;)
           if (**ap != '\0')
                   if (++ap >= &argv[10])
                           break;

Отредактировано для произвольного числа токенов:

char **ap, **argv, *inputstring;

int arglen = 10;
argv = calloc(arglen, sizeof(char*));
for (ap = argv; (*ap = strsep(&inputstring, " \t")) != NULL;)
    if (**ap != '\0')
        if (++ap >= &argv[arglen])
        {
            arglen += 10;
            argv = realloc(argv, arglen);
            ap = &argv[arglen-10];
        }

Или что-то похожее на это.Выше может не работать, но если нет, то это не за горами.Построение связанного списка было бы более эффективным, чем постоянный вызов realloc, но это, действительно, не в том, дело в том, как лучше всего использовать strsep.

1 голос
/ 31 января 2010

Вы помните, чтобы malloc выделил дополнительный байт для завершающего нуля, который обозначает конец строки?

0 голосов
/ 31 января 2010

Глядя на другие ответы, для новичка в C это выглядело бы сложным из-за небольшого размера кода, я подумал, что я бы вставил это для новичка, возможно, было бы проще фактически проанализировать строку вместо использования strtok ... как то так:

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

char **parseInput(const char *str, int *nLen);
void resizeptr(char ***, int nLen);

int main(int argc, char **argv){
    int maxLen = 0;
    int i = 0;
    char **ptr = NULL;
    char *str = "valgrind --leak-check=yes --track-origins=yes ./a.out";
    ptr = parseInput(str, &maxLen);
    if (!ptr) printf("Error!\n");
    else{
        for (i = 0; i < maxLen; i++) printf("%s\n", ptr[i]);
    }
    for (i = 0; i < maxLen; i++) free(ptr[i]);
    free(ptr);
    return 0;
}

char **parseInput(const char *str, int *Index){
    char **pStr = NULL;
    char *ptr = (char *)str;
    int charPos = 0, indx = 0;
    while (ptr++ && *ptr){
        if (!isspace(*ptr) && *ptr) charPos++;
        else{
            resizeptr(&ptr, ++indx);
            pStr[indx-1] = (char *)malloc(((charPos+1) * sizeof(char))+1);
            if (!pStr[indx-1]) return NULL;
            strncpy(pStr[indx-1], ptr - (charPos+1), charPos+1);
            pStr[indx-1][charPos+1]='\0';
            charPos = 0;
        }
    }
    if (charPos > 0){
        resizeptr(&pStr, ++indx);
        pStr[indx-1] = (char *)malloc(((charPos+1) * sizeof(char))+1);
        if (!pStr[indx-1]) return NULL;
        strncpy(pStr[indx-1], ptr - (charPos+1), charPos+1);
        pStr[indx-1][charPos+1]='\0';
    }
    *Index = indx;
    return (char **)pStr;
}

void resizeptr(char ***ptr, int nLen){
    if (*(ptr) == (char **)NULL){
        *(ptr) = (char **)malloc(nLen * sizeof(char*));
        if (!*(ptr)) perror("error!");
    }else{
        char **tmp = (char **)realloc(*(ptr),nLen);
        if (!tmp) perror("error!");
        *(ptr) = tmp;
    }
}

Я немного изменил код, чтобы сделать его проще. Единственная строковая функция, которую я использовал, была strncpy .. конечно, она немного затянута, но она динамически перераспределяет массив строк вместо использования жестко закодированного MAX_ARGS, что означает, что двойной указатель уже перегружает память когда только 3 или 4 будет делать, что также сделало бы использование памяти эффективным и крошечным, используя realloc, простой синтаксический анализ покрывается использованием isspace, поскольку он выполняет итерацию с использованием указателя. Когда встречается пробел, он realloc съедает двойной указатель и malloc смещение для хранения строки.

Обратите внимание, как тройные указатели используются в функции resizeptr. На самом деле, я подумал, что это послужит отличным примером простой программы на C, указателей, realloc, malloc, передачи по ссылке, базового элемента парсинг строки ...

Надеюсь, это поможет, С наилучшими пожеланиями, Том.

...