Вывод strtok () дает разное поведение каждый раз, когда я запускаю свою программу.

Часть моей задачи программирования на C89 включает чтение содержимого текстового файла, содержащего неизвестное количество слов в одной строке, в связанный список. Слова разделяются пробелом. Думаю, введите стрток.

Моя функция еще не завершена на 100%, но она может правильно захватывать слова из буфера (ранее считанные посимвольно с помощью fgetc) и вставлять их в поле 'word' узла связанного списка, и я просто проверяю, могу ли я снова распечатать слова, прежде чем продолжить.

Например. если: here are some words in the user file\0 - это содержимое буфера, то вывод будет выглядеть так:

head->word = here
head->word = are
head->word = some
head->word = words
...

Как и ожидалось. Однако иногда при запуске вывод бывает немного странным, обычно проявляясь в словах, пропускающих один или два символа либо с начала, либо с конца, либо с того и другого. Иногда кажется, что слово разрезано пополам с частями в двух строках вывода. Это из того же исполняемого файла! Я прочитал несколько других тем за последний час, которые ни к чему не пришли, и я полностью озадачен - я подумал, что лучше сначала исправить эту проблему, прежде чем продолжить, на случай, если весь мой подход потребуется изменить.

Мой код ниже, и любая помощь приветствуется в этом неприятном вопросе ... пора получить еще один Red Bull, как мне кажется.

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

#include "nodestruct.h"

char** readUserfile(char** userFilename, int* numUserwords)
{
    /* Open user file for reading */
    FILE* userPtr = fopen(*userFilename, "r");

    char ch = (char)fgetc(userPtr); /* 'ch' temporarily stores input taken by fgetc() */

    int length = 0; /* Eventually the length of the file's line, +1 for a null terminator */

    char **userArray = NULL;

    /* Creating a temporary buffer to store the contents of the user file. The buffer is allowed to grow
    until it holds the contents of the file, as we are told the user file has all it's words on one line,
    separated by whitespace, however we do not know the total number of words preventing us from using
    fgets() to read in the line (unless we make an arbitrary decision on a very big 'size' argument) */
    int BUFFER_SIZE = 128;
    char* buffer = (char*)malloc( sizeof(char) * BUFFER_SIZE );

    /* Delimiter for call to strtok() below: the whitespace that separates words in the user file */
    char delim = ' ';
    const char* delimPtr = &delim;
    /* String to catch return value of strtok() so we can check if non-null (i.e. returns a word) or null
    (end the linked list at this word) */
    char* strtokReturn;

    /* Read user file: */
    /* While still contents to be read */
    while(ch != EOF)
    {
        if(length != BUFFER_SIZE)
        {
            buffer[length] = ch;
            length++;
        }
        else    /* Buffer is full, so realloc, doubling the size each time */
        {
            BUFFER_SIZE *= 2;
            buffer = realloc(buffer, BUFFER_SIZE);
            buffer[length] = ch;
            length++;
        }
        /* Read the next character. If EOF reached, the while loop condition will stop the read */
        ch = (char)fgetc(userPtr);
    }

    /* Trim the buffer to only contain the information that was read + '\0' */
    buffer = realloc(buffer, (sizeof(char) * (length+1)) );
    buffer[length] = '\0';

    /* Just a note in case the user file was empty */
    if(length == 0)
    {
        printf("Note: no words found in user file.\n");
    }

    /* Create a linked list containing all the words read by the above method */
    Node* head = (Node*)malloc(sizeof(Node));

    /* Enter the first word into the list - strtok() is used here to begin breaking up the contents of the
    buffer read previously into separate strings (words). The delimiter is the whitespace character ' ' */

    strtokReturn = strtok(buffer, delimPtr);
    strcpy(head->word, strtokReturn); 
    printf("head->word = <<%s>>\n", head->word);
    head->next = NULL;

    strtokReturn = strtok(NULL, delimPtr);
    strcpy(head->word, strtokReturn); 
    printf("head->word = <<%s>>\n", head->word);
    head->next = NULL;

    strtokReturn = strtok(NULL, delimPtr);
    strcpy(head->word, strtokReturn); 
    printf("head->word = <<%s>>\n", head->word);
    head->next = NULL;

    strtokReturn = strtok(NULL, delimPtr);
    strcpy(head->word, strtokReturn); 
    printf("head->word = <<%s>>\n", head->word);
    head->next = NULL;

    strtokReturn = strtok(NULL, delimPtr);
    strcpy(head->word, strtokReturn); 
    printf("head->word = <<%s>>\n", head->word);
    head->next = NULL;

    strtokReturn = strtok(NULL, delimPtr);
    strcpy(head->word, strtokReturn); 
    printf("head->word = <<%s>>\n", head->word);
    head->next = NULL;

    strtokReturn = strtok(NULL, delimPtr);
    strcpy(head->word, strtokReturn); 
    printf("head->word = <<%s>>\n", head->word);
    head->next = NULL;

    strtokReturn = strtok(NULL, delimPtr);
    strcpy(head->word, strtokReturn); 
    printf("head->word = <<%s>>\n", head->word);
    head->next = NULL;

    strtokReturn = strtok(NULL, delimPtr);
    strcpy(head->word, strtokReturn); 
    printf("head->word = <<%s>>\n", head->word);
    head->next = NULL;

    strtokReturn = strtok(NULL, delimPtr);
    strcpy(head->word, strtokReturn); 
    printf("head->word = <<%s>>\n", head->word);
    head->next = NULL;

    strtokReturn = strtok(NULL, delimPtr);
    strcpy(head->word, strtokReturn); 
    printf("head->word = <<%s>>\n", head->word);
    head->next = NULL;



    free(head);
    head = NULL;

    free(buffer);
    buffer = NULL;

    fclose(userPtr);
    return userArray;
}/* End readUserfile() */

Вывод (простите за длину, но в интересах полного раскрытия информации):

[Danyal@DanCentos assign]$ ./wordfixr userfile.txt
Spellrc opened/read successfully.
Dictionary file opened successfully.
Dictionary read successfully.
head->word = <<here>>
head->word = <<are>>
head->word = <<some>>
head->word = <<words>>
head->word = <<in>>
head->word = <<the>>
head->word = <<user>>
head->word = <<file>>
head->word = <<and>>
head->word = <<the>>
head->word = <<whole>>
[Danyal@DanCentos assign]$ ./wordfixr userfile.txt
Spellrc opened/read successfully.
Dictionary file opened successfully.
Dictionary read successfully.
head->word = <<h>>
head->word = <<r>>
head->word = <<ar>>
head->word = <<som>>
head->word = <<words>>
head->word = <<in>>
head->word = <<th>>
head->word = <<us>>
head->word = <<r>>
head->word = <<fil>>
head->word = <<and>>
[Danyal@DanCentos assign]$ ./wordfixr userfile.txt
Spellrc opened/read successfully.
Dictionary file opened successfully.
Dictionary read successfully.
head->word = <<here>>
head->word = <<are>>
head->word = <<some>>
head->word = <<words>>
head->word = <<in>>
head->word = <<the>>
head->word = <<user>>
head->word = <<file>>
head->word = <<and>>
head->word = <<the>>
head->word = <<whole>>
[Danyal@DanCentos assign]$ ./wordfixr userfile.txt
Spellrc opened/read successfully.
Dictionary file opened successfully.
Dictionary read successfully.
head->word = <<here>>
head->word = <<are>>
head->word = <<some>>
head->word = <<words>>
head->word = <<in>>
head->word = <<the>>
head->word = <<user>>
head->word = <<file>>
head->word = <<and>>
head->word = <<the>>
head->word = <<whole>>
[Danyal@DanCentos assign]$ ./wordfixr userfile.txt
Spellrc opened/read successfully.
Dictionary file opened successfully.
Dictionary read successfully.
head->word = <<here>>
head->word = <<are>>
head->word = <<some>>
head->word = <<words>>
head->word = <<in>>
head->word = <<the>>
head->word = <<user>>
head->word = <<file>>
head->word = <<and>>
head->word = <<the>>
head->word = <<whole>>
[Danyal@DanCentos assign]$ ./wordfixr userfile.txt
Spellrc opened/read successfully.
Dictionary file opened successfully.
Dictionary read successfully.
head->word = <<here>>
head->word = <<are>>
head->word = <<some>>
head->word = <<words>>
head->word = <<in>>
head->word = <<the>>
head->word = <<user>>
head->word = <<file>>
head->word = <<and>>
head->word = <<the>>
head->word = <<whole>>
[Danyal@DanCentos assign]$ ./wordfixr userfile.txt
Spellrc opened/read successfully.
Dictionary file opened successfully.
Dictionary read successfully.
head->word = <<here>>
head->word = <<are>>
head->word = <<some>>
head->word = <<words>>
head->word = <<in>>
head->word = <<the>>
head->word = <<user>>
head->word = <<file>>
head->word = <<and>>
head->word = <<the>>
head->word = <<whole>>
[Danyal@DanCentos assign]$ ./wordfixr userfile.txt
Spellrc opened/read successfully.
Dictionary file opened successfully.
Dictionary read successfully.
head->word = <<h>>
head->word = <<r>>
head->word = <<ar>>
head->word = <<som>>
head->word = <<words>>
head->word = <<in>>
head->word = <<th>>
head->word = <<us>>
head->word = <<r>>
head->word = <<fil>>
head->word = <<and>>
[Danyal@DanCentos assign]$ ./wordfixr userfile.txt
Spellrc opened/read successfully.
Dictionary file opened successfully.
Dictionary read successfully.
head->word = <<here>>
head->word = <<are>>
head->word = <<some>>
head->word = <<words>>
head->word = <<in>>
head->word = <<the>>
head->word = <<user>>
head->word = <<file>>
head->word = <<and>>
head->word = <<the>>
head->word = <<whole>>
[Danyal@DanCentos assign]$ ./wordfixr userfile.txt
Spellrc opened/read successfully.
Dictionary file opened successfully.
Dictionary read successfully.
head->word = <<here>>
head->word = <<are>>
head->word = <<some>>
head->word = <<words>>
head->word = <<in>>
head->word = <<the>>
head->word = <<user>>
head->word = <<file>>
head->word = <<and>>
head->word = <<the>>
head->word = <<whole>>
[Danyal@DanCentos assign]$ ./wordfixr userfile.txt
Spellrc opened/read successfully.
Dictionary file opened successfully.
Dictionary read successfully.
head->word = <<here>>
head->word = <<are>>
head->word = <<some>>
head->word = <<words>>
head->word = <<in>>
head->word = <<the>>
head->word = <<user>>
head->word = <<file>>
head->word = <<and>>
head->word = <<the>>
head->word = <<whole>>
[Danyal@DanCentos assign]$ ./wordfixr userfile.txt
Spellrc opened/read successfully.
Dictionary file opened successfully.
Dictionary read successfully.
head->word = <<here>>
head->word = <<are>>
head->word = <<some>>
head->word = <<words>>
head->word = <<in>>
head->word = <<the>>
head->word = <<user>>
head->word = <<file>>
head->word = <<and>>
head->word = <<the>>
head->word = <<whole>>
[Danyal@DanCentos assign]$ ./wordfixr userfile.txt
Spellrc opened/read successfully.
Dictionary file opened successfully.
Dictionary read successfully.
head->word = <<here>>
head->word = <<are>>
head->word = <<some>>
head->word = <<words>>
head->word = <<in>>
head->word = <<the>>
head->word = <<user>>
head->word = <<file>>
head->word = <<and>>
head->word = <<the>>
head->word = <<whole>>
[Danyal@DanCentos assign]$ ./wordfixr userfile.txt
Spellrc opened/read successfully.
Dictionary file opened successfully.
Dictionary read successfully.
head->word = <<here>>
head->word = <<are>>
head->word = <<some>>
head->word = <<wor>>
head->word = <<s>>
head->word = <<in>>
head->word = <<the>>
head->word = <<user>>
head->word = <<file>>
head->word = <<an>>
head->word = <<the>>

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

Еще раз, заранее спасибо за любую помощь, я очень ценю это и искренне благодарен за то, что живу во время, когда такое место, как переполнение стека, существует бесплатно.


person Casowsky    schedule 25.10.2015    source источник
comment
Где определение Node? Вы выделяете память для head->word?   -  person Michael Foukarakis    schedule 25.10.2015
comment
Да, простите меня, объявление для узла находится в заголовочном файле nodestruct.h: #define MAX_STRING_LEN 51 typedef struct Node { char word[MAX_STRING_LEN]; struct Node* next; }Node*   -  person Casowsky    schedule 25.10.2015


Ответы (1)


Второй аргумент strtok должен содержать строку с нулевым символом в конце. strtok считает любой элемент этой строки допустимым разделителем. Если вам нужен только один разделитель, используйте строку длиной 1:

strtokReturn = strtok(buffer, " ");

Без '\0' для завершения вашего delimPtr, strtok смотрит на байты, следующие за delim. И, по-видимому, иногда он находит 'e' char перед '\0' char и в конечном итоге рассматривает 'e' как другой разделитель.

person aschepler    schedule 25.10.2015
comment
То, что вы говорите, имеет большой смысл, я изменил функцию, чтобы отразить это: char delim[] = " \0"; const char* delimPtr = delim; Однако, когда я запускаю программу на этот раз, похоже, получается тот же результат. Я должен отметить, однако, что значение int-указателя, говорящего мне, как долго мой связанный список был изменен с 20 (слишком длинный) до 19, что и должно быть (количество слов в исходном текстовом файле). - person Casowsky; 26.10.2015
comment
Любопытно ... Я заметил, что valgrind не сообщает об ошибках в утечке памяти, что странно, поскольку я еще не освободил должным образом свой недавно созданный связанный список. Когда у моей незавершенной функции был только «головной» узел, у меня был free(head) в конце, чтобы освободить его, но теперь, когда я подтвердил, что у меня есть связанный список из 19 узлов, я оставил единственный free(head), не освобождая весь список , Я удивлен, что valgrind не сказал мне, что я потерял столько блоков ... - person Casowsky; 26.10.2015