Я попытался разобрать строку в разные слова, позже попытался освободить их, но 2 строки имеют одинаковые байтовые адреса - PullRequest
0 голосов
/ 14 мая 2019

В настоящее время я пытаюсь разобрать строку в массив строк.

До сих пор я считаю, что мне удалось разделить строку, вставив '\0' после каждого слова "чанк".

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

Это код моего парсера, я прошу прощения за его грязную форму:

/*
 * parser()
 * 
 * Parses a given string into different words and returns a list with the words.
 * If there is a non-space and non-alphabetic character an error is recorded.
 */ 
void parser(char* str, char** actualList, char** freeingList,char* error, int* length){
    // initialize variables
    bool chara = false;
    bool beginning = true;
    int size = strlen(str);
    bool nonAlphaSpace = false;

    // iterate through the entire string
    for(int i = 0; i < size; i++){
        // if the character is not either a space or an alphabetic character
        if(isspace(str[i])==0 && isalpha(str[i])==0 && !nonAlphaSpace){
            *error = str[i];
            nonAlphaSpace = true;
        }
    }
    // if there was no irregular character
    if(!nonAlphaSpace){
        for(int j = 0; j < size; j++){
            // if the character is the beginning of the current string
            if(beginning){
                // record this string into the list of words
                freeingList[*length] = &str[j];
                (*length)++;
                // set the status of any alphabetic character being present to false;
                chara = false;
                // if the current character is an alphabetic character
                if(isalpha(str[j])!=0){
                    chara = true;
                }
                beginning = false;
            }
            // if the character is a space
            else if(isspace(str[j])!=0){
                // if there was a character beforehand
                if(chara){
                    // get the pointer to the next character
                    char* new = &str[j+1];
                    // change the current character to a null
                    str[j] = '\0';
                    // realign the pointer to the string to rest of the string
                    str = new;
                    j = -1;
                    size = strlen(str);
                    beginning = true;
                }
            }
            // if the character is an alphabetic character
            else{
                chara = true;
            }
        }
        // if the last chunk of string left didn't contain any characters
        if(!chara){
            free(str);
        }
        // for every word extracted
        for(int k = 0; k < *length; k++){
            int newSize = strlen(freeingList[k]);
            bool first = true;
            // get the pointer to the first character in the word, i.e. not the first few spaces
            for(int l = 0; l < newSize; l++){
                if(isspace(freeingList[k][l])==0 && first){
                    actualList[k] = &freeingList[k][l];
                    first = false;
                }
            }
        }
    }
}

Это когда я пытаюсь освободить его:

// free the current collection of strings
for(int j = 0; j < size; j+=2){
    free(words[j]);
}

Когда явведите "home or for" в анализатор и позже попытайтесь освободить его, адрес "home" равен 0x7fffffffe840, а адрес "for" равен 0x7fffffffe848.Это заставляет меня поверить, что освобождение home также освобождает or, что позже вызывает ошибку SIGABRT.

Верно ли это предположение?Как я могу преодолеть это двойное освобождение?

Ответы [ 3 ]

1 голос
/ 14 мая 2019

Хотя это не парсер. Больше токенизатор.

#include <assert.h>
#include <errno.h>
#include <stddef.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>

bool parser(char const *str, char ***words, size_t *num_words, size_t *error)
{  //                             ^^^ gaaaah! *)
    assert(words);

    errno = 0;
    size_t length = strlen(str);

    for (size_t i = 0; i < length; ++i) {
        if (!isalnum(str[i]) && !isspace(str[i])) {
            *error = i;    // the position is most likely more meaningful than the character
            return false;  // get outta here!!
        }
    }

    char const *begin;
    char const *end;

    *num_words = 0;
    *words = NULL;

    bool in_word = false;
    for (size_t i = 0; i <= length; ++i) {
        if (!in_word && isalnum(str[i])) {  // word begins
            begin = str + i;
            in_word = true;
        }
        else if (in_word && !isalnum(str[i])) {  // word ended
            end = str + i;
            char *word = calloc(end - begin + 1, sizeof *word);
            if (!word) {
                for (size_t i = 0; i < num_words; ++i)
                    free((*words)[i]);
                free(*words);
                errno = ENOMEM;
                return false;               
            }

            memcpy(word, begin, end - begin);

            char **tmp = realloc(*words, (*num_words + 1) * sizeof *tmp);
            if (!tmp) {
                free(word);
                for (size_t i = 0; i < num_words; ++i)
                    free((*words)[i]);
                free(*words);
                errno = ENOMEM;
                return false;               
            }
            *words = tmp;
            tmp[(*num_words)++] = word;
            in_word = false;
        }
    }

    return true;
}

int main(void)
{
    char const *foo = "slfkja     askdfj jk j aksjf lasjdflkjsdlf jask fdjl";
    char **words = NULL;
    size_t num_words = 0;
    size_t error = 0;
    if (!parser(foo, &words, &num_words, &error)) {
        if (errno == ENOMEM)
            fputs("Not enough memory. :(\n\n", stderr);
        else fprintf(stderr, "Error at position %zu: \"%s\"\n\n", error, foo + error);
        return EXIT_FAILURE;
    }

    puts("List of words:");
    for (size_t i = 0; i < num_words; ++i) {
        printf("\"%s\"\n", words[i]);
        free(words[i]);
    }
    free(words);
}

C должен быть переименован в brainf * ck ...


*) Трехзвездочный программист

1 голос
/ 14 мая 2019

Вы должны вызывать free() только для указателей, возвращаемых malloc(), calloc() или realloc(). Как это выглядит, вы делаете:

char *ptr = malloc(100);
char *ptr2 = &ptr[10];
free(ptr2);    // You can't do that.

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

void parser(char* str, char** actualList, int* length) {
    char *start = str;   // The start of the current string
    int count = 0;       // Number of strings copied
    while (*str) {
        if (isspace(*str)) {
            *str = '\0';
            actualList[count] = malloc(strlen(start) + 1);   // Allocate space for string
            strcpy(actualList[count++], start);   // Copy string
            start = str + 1;                          // Reset for next string
            if (count == *length - 1) break;      // Don't overflow pointer array
        }
        str++;
    }
    // Grab the final string
    actualList[count] = malloc(strlen(start) + 1);   // Allocate space for string
    strcpy(actualList[count++], start);              // Copy string

    *length = count;
}

Тогда назовите это как:

char input[] = "home or for";
char *words[5];
int max_words = 5;
parser(input, words, &max_words);
// max_words should be 3 now
for (int i = 0; i < max_words; i++) {
    printf("%s\n", words[i]);
}
// Clean up
for (int i = 0; i < max_words; i++) {
    free(words[i]);
}

Выход:

home
or
for
0 голосов
/ 17 мая 2019
  1. вы изменяете значение str в теле функции (в строке str = new; (не используйте new в качестве идентификатора, больше, если вы планируете использовать этот код в качестве кода C ++) , поскольку new является зарезервированным словом в C ++). Поскольку вы не вызываете malloc(3) в теле функции, вполне нормально, что вы получаете проблему из free(3), так как для свободного требуется , чтобы быть передал указатель, ранее сгенерированный с помощью malloc (и только один раз, поэтому вы не можете вызвать его дважды с одним и тем же указателем). Это причина того, что вы получили SIGABRT и т. п. Как правило, не вызывайте free(3) в функции вы также не вызываете malloc для того же указателя . Это использование подвержено ошибкам, и вы будете сталкиваться с проблемами чаще, чем раз в день, если будете настаивать на выполнении всего в одной функции.

  2. лучше, чем извиниться за грязную форму кода, пожалуйста, очистите его перед публикацией. Опубликовать минимальное значение (означающее минимальный код, который показывает ошибку), полное (означающее, что мы можем скомпилировать его и наблюдать результат, который вы публикуете как ошибочный), проверяемый (код, показывающий, что он дает наблюдаемый результат, а не ожидаемый) и завершить (это означает, что нам нечем заняться, кроме как скомпилировать и запустить) код (поэтому мы можем проверить его на неудачу, как вы говорите, без необходимости сначала исправлять его). Таким образом, мы можем сделать диагностику того, что происходит в вашем коде. Если нам нужно исправить код только для того, чтобы сделать его работоспособным, мы можем исправить основную проблему, которую вы наблюдаете, и не сможете увидеть ошибку. Ты видишь? :)

Примечание об использовании new в качестве идентификатора в коде C:

Во многих средах модульного тестирования требуется, чтобы ваш код был компилируемым в виде кода C ++, поэтому он может использоваться платформой (по крайней мере, это требуется для Google Test). Если вы планируете писать модульные тесты для своего кода, помните, что new зарезервированное слово в C ++ для оператора new, поэтому ваш код выдаст синтаксические ошибки, если вы попытаетесь скомпилировать его с помощью компилятора c ++. Лучше, если вы не используете его.

...