Строковые массивы C - PullRequest
       11

Строковые массивы C

3 голосов
/ 22 января 2011

Я только что написал функцию разделения строк:

typedef enum {
    strspl_allocation_error = 1
} strspl_error;


int strspl(const char *string, const char separator, char ***result) {

    const int stringLength = strlen(string);
    int lastSplit = 0;
    int numberOfComponents = 1;

    // Compute the number of components
    for (int i = 0; i <= stringLength; i++) {
        if (string[i] == separator)
            numberOfComponents++;
    }

    // Allocate space to hold pointers to each component
    *result = (char **) malloc(numberOfComponents * sizeof(char *));
    if (result == NULL)
        return strspl_allocation_error;

    numberOfComponents = 0;

    for (int i = 0; i <= stringLength; i++) {
        char c = string[i];
        if (c == separator || i == stringLength) {

            const int componentLength = i - lastSplit;

            // Allocate space to hold the component
            char *component = (char *) malloc(componentLength * sizeof(char));
            if (component == NULL)
                return strspl_allocation_error;

            // Copy the characters from the string into the component
            for (int j = 0; j < componentLength; j++)
                component[j] = string[lastSplit + j];
            component[componentLength] = '\0';

            // Put the component into the array
            *result[numberOfComponents] = component;

            lastSplit = i + 1;
            numberOfComponents++;
        }
    }

    return numberOfComponents;
}

Пример:

char **result;
int r = strspl("aaaBcBddddeeBk", 'B', result);

for (int i = 0; i < r; i++)
    printf("component: %s\n", result[i]);

должен вывести:

component: aaa
component: c
component: ddddee
component: k

Но когда я ее запускаю, оналибо вылетает, либо возвращает значения мусора.Я не понимаю, где я делаю ошибку ...

Обновление: вот, надеюсь, версия без ошибок:

int strspl(const char *string, const char separator, char ***results) {

    const char *separatorString = (char[]){separator, '\0'};
    int numberOfComponents = 0;
    int stringLength = strlen(string);

    int lastCharacterWasSeparator = 1;

    // Compute the number of components
    for (int i = 0; i < stringLength; i++) {
        if (string[i] != separator) {
            if (lastCharacterWasSeparator)
                numberOfComponents++;
            lastCharacterWasSeparator = 0;
        }
        else
            lastCharacterWasSeparator = 1;
    }

    // Allocate space to hold pointers to components
    *results = malloc(numberOfComponents * sizeof(**results));

    char *stringCopy = strdup(string); // A reference to the copy of the string to modify it and to free() it later.
    char *strptr = stringCopy; // This will be used to iterate through the string.
    int componentLength = 0;
    int component = 0;

    while (component < numberOfComponents) {

        // Move to the startpoint of the next component.
        while (componentLength == 0) {
            componentLength = strcspn(strptr, separatorString);

            // Break out the while loop if we found an actual component.
            if (componentLength != 0)
                break;

            // If we found two adjacent separators, we just "silently" move over them.
            strptr += componentLength + 1;
        }

        // Replace the terminating separator character with a NULL character.
        strptr[componentLength] = '\0';

        // Copy the new component into the array.
        (*results)[component++] = strdup(strptr);

        // Move the string pointer ahead so we can work on the next component.
        strptr += componentLength + 1;

        componentLength = 0;
    }

    // Free the copy of the string.
    free(stringCopy);

    return numberOfComponents;
}

Ответы [ 3 ]

3 голосов
/ 22 января 2011

Извините, все, что мы вам предложили сделать, чтобы исправить это, объединило и сломало снова!Исходя из исходного кода, вот необходимые вам корректировки:

  1. Подпись функции должна быть char ***result вместо char **result.
  2. Распределение массива должно быть *result = malloc(...) вместо result = malloc(...).
  3. Указатель компонента никогда не сохранялся в массиве результатов, после строки component[componentLength] = '\0'; следует поставить строку (*result)[numberOfComponents] = component; (скобка необходима, поскольку параметр результата былизменено на char***).
  4. И, наконец, вызов функции должен выглядеть примерно так: strspl(..., &result); вместо strspl(..., result);

Указатели всегда были одной из самых сложных вещей для понимания при работе в C / C ++ ... Позвольте мне посмотреть, могу ли я объяснить это:

Допустим, у нас есть вызывающая сторона со стеком, подобным этому:

Address     -  Data        -  Description
0x99887760  -  0xbaadbeef  -  caller-result variable (uninitialized garbage)

Когда вызов выполняется следующим образом: strspl(..., result);, компилятор копирует локальный указатель (0xbaadbeef) в стек strspl:

Address     -  Data        -  Description
0x99887750  -  0xbaadbeef  -  strspl-result variable (copy of uninitialized garbage)
...
0x99887760  -  0xbaadbeef  -  caller-result variable (uninitialized garbage)

Теперь, когда мы вызываем result = malloc(...) и копируем результат в локальную переменную strspl-result, мы получаем:

Address     -  Data        -  Description
0x99887750  -  0x01000100  -  strspl-result variable (new array)
...
0x99887760  -  0xbaadbeef  -  caller-result variable (uninitialized garbage)

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

Если вместо этого мы вызываем адрес переменной результата: strspl(..., &result);, мы получаем это:

Address     -  Data        -  Description
0x99887750  -  0x99887760  -  strspl-result variable (pointer to the caller's result)
...
0x99887760  -  0xbaadbeef  -  caller-result variable (uninitialized garbage)

И затем, когда мы вызываем result = malloc(...), мы получаем это:

Address     -  Data        -  Description
0x99887750  -  0x01000100  -  strspl-result variable (new array)
...
0x99887760  -  0xbaadbeef  -  caller-result variable (uninitialized garbage)

Все еще не совсем то, что мы хотим, потому что вызывающая сторона никогда не получает указатель на массив.

Если вместо этого мы вызываем *result = malloc(...), мы получаем это:

Address     -  Data        -  Description
0x99887750  -  0x99887760  -  strspl-result variable (pointer to the caller's result)
...
0x99887760  -  0x01000100  -  caller-result variable (new array)

Таким образом, когда мы возвращаемся, мы перезаписываем мусор вызывающей стороны нашим новым malloc 'd массивом.

Как видите, компилятор копирует адрес переменной вызывающего в стек вызываемой функции.Поскольку она скопирована, функция не может изменить ее, пока вызывающая сторона не передаст указатель на ее переменную (именно поэтому она должна быть char***, а не char**).

Я надеюсь, что все проясняется, не делая этотруднее понять!: -Р

1 голос
/ 22 января 2011
  1. Вы никогда не назначаете компонент строки в массив result
  2. При добавлении компонента malloc у вас возникает ошибка, потому что вы не учитываете NUL
  3. Вам нужен еще один слой косвенности, чтобы вы могли изменить char **result, передав его в свою функцию
  4. Не приводите malloc() возвращаемое значение в C. См. здесь , почему

См. ЭТО ССЫЛКА для работающей реализации.

Обновление

Вот как бы я реализовал strspl()

int strspl(const char *string, const int separator, char ***result)
{
    int len, n=1, i=0;
    char *strptr = *copy = strdup(string);

    while(*strptr++) {
        if (*strptr == separator){ n++; }
    }

    *result = malloc(n * sizeof(**result));

    //Reset Pointer to Beginning of string
    strptr = copy;

    while(len = strcspn(strptr, (char []){separator, '\0'})) {
        strptr[len] = '\0';
        (*result)[i++] = strdup(strptr);
        strptr += len + 1;
    }

    free(copy);
    return n;
}

выход

component: aaa
component: c
component: ddddee
component: k
1 голос
/ 22 января 2011
  1. Прежде всего правильнее использовать for(int i = 0; i < stringLength; i++), но это не должно быть большой ошибкой в ​​вашем случае.
  2. Если вы хотите вернуть массив char *, выдолжен передать char *** result и назначить *result = (char**) malloc(...).
  3. Я не вижу, куда вы сохраняете указатель component в свой массив result
  4. Я думаю, что лучше использовать strdupили strcpy для копирования строк
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...