Попытка создать программу для подсчета количества сравнений, сделанных с strcmp при перемещении текущего узла в начало. Однако у меня есть проблемы с алгоритмом ... иногда он "работает", а иногда дает мне бесконечный цикл. Пробовал lldb в течение нескольких часов и помещал сообщения printf буквально каждые 2 строки кода, но я понятия не имею, как определить проблему. Я предполагаю, что это где-то в алгоритме, но я не вижу в этом ничего плохого.

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

typedef struct Node2
{
    char* word;
    struct Node2 *next;
} Node2;

Node2* head = NULL;
Node2* now = NULL;
int uniqueWords = 0;
int totalMTFRComparisons = 0;


Node2* iNode(char* word)
{   
    Node2* ptr = malloc(sizeof(Node2));
    Node2* tmp = head;
    Node2* prev = head;

    while (tmp)
    {
        totalMTFRComparisons++;
        printf("Current word: %s", tmp->word);
        if (strcmp(tmp->word, word) == 0)
        {
            prev->next = tmp->next;
            tmp->next = head;
            return tmp;
        }
        prev = tmp;
        tmp = tmp->next;
        printf("tmp incremented.\n");
    }

    ptr->word = malloc(strlen(word) + 1);
    strcpy(ptr->word, word);
    ptr->next = NULL;
    if (head == NULL)
    {
        head = now = ptr;
        return ptr;
    }
    else
    {
        ptr->next = head;
        head = ptr;
    }
    return ptr;
}

char* getString()
{
    static char buffer[128];
    while (fgets(buffer, 128, stdin) != NULL)
    {
        iNode(buffer);
    }
    return buffer;
}

int main()
{
    getString();
    printf("Total words: %d, total MTFR comparisons: %d\n", uniqueWords, totalMTFRComparisons);

    Node2* ptr2 = head;
    Node2* tmp2 = head;
    while (ptr2 != NULL)
    {
        tmp2 = ptr2->next;
        free(ptr2->word);
        free(ptr2);
        ptr2 = tmp2;
    }
}

Моя консоль просто забита спамом: tmp incremented. Но это происходит не всегда - это случается только иногда, поэтому я понятия не имею, чем это вызвано.

Пример ввода и вывода: http://pastebin.com/QfuCm7gt

0
Sleepless 21 Окт 2015 в 07:40

2 ответа

Лучший ответ

Вы выделяете слишком мало памяти для своих строк:

ptr->word = malloc(strlen(word));
strcpy(ptr->word, word);

Вам необходимо выделить strlen(word) + 1 байтов, чтобы разрешить использование нулевого терминатора.

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

Подумайте, можете ли вы использовать функцию strdup(). Если нет, подумайте, стоит ли вам написать и использовать свою собственную версию. Не забудьте проверить, было ли выделение памяти успешным.

(Пока вы не исправите неопределенное поведение, действительно нет смысла гадать, что еще не так, если что-то еще не так.)


Я написал этот код, чтобы посмотреть, смогу ли я смоделировать вашу проблему. Я не могу:

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

typedef struct Node
{
    char *word;
    struct Node *next;
} Node;

int totalMTFRComparisons = 0;

Node* head = NULL;
static Node* iNode(char* word)
{   
    Node* ptr = malloc(sizeof(Node));
    Node* tmp = head;
    Node* prev = head;

    while (tmp)
    {
        totalMTFRComparisons++;
        if (strcmp(tmp->word, word) == 0)
        {
            prev->next = tmp->next;
            tmp->next = head;
            //tmp->next = prev;

/* JL: Still a memory leak here. */
/* Either free(ptr) or don't allocate until after the loop */

            return tmp;
        }
        prev = tmp;
        tmp = tmp->next;
        printf("tmp incremented.\n");
    }
    ptr->word = malloc(strlen(word) + 1);
    strcpy(ptr->word, word);
    ptr->next = NULL;
    if (head == NULL)
    {
        head = ptr;
        return ptr;
    }
    else
    {
        ptr->next = head;
        head = ptr;
    }
    return ptr;
}

static void dump_list(Node *node)
{
    while (node != 0)
    {
        printf("Node word: [[%s]]\n", node->word);
        node = node->next;
    }
}

static void free_list(Node *node)
{
    printf("Freeing list\n");
    while (node != 0)
    {
        Node *next = node->next;
        printf("Freeing: [[%s]]\n", node->word);
        free(node->word);
        free(node);
        node = next;
    }
}

int main(void)
{
    char line[4096];

    while (fgets(line, sizeof(line), stdin) != 0)
    {
        printf("Line: [[%s]]\n", line);
        char *ptr = line;
        char *tok;
        while ((tok = strtok(ptr, "\n\t ")) != 0)
        {
            printf("Word: [[%s]]\n", tok);
            iNode(tok);
            ptr = NULL;
        }
        dump_list(head);
    }
    free_list(head);
    return 0;
}

Это более или менее MCVE. Функции дампа и освобождения просто позволяют мне убедиться, что я вижу, что находится в списке, и освободить всю память.

Помимо исправления проблемы перезаписи памяти, я предоставил только определение типа Node, поставил static перед функцией (чтобы избежать одного из предупреждений компиляции, которые я бы иначе получил) и добавил две вспомогательные функции и main(); В противном случае я не менял ваш код. (Между прочим, единственная жалоба на первую компиляцию была связана с cur2, который я заметил, но забыл удалить - это очень хорошо; немногие программы, даже те, которые я пишу, проходят так чисто.)

При запуске набрал:

abc def ghi
mno pqr stuvwxyz humongously-and-tempestuously-but-neither-abstemiously-nor-facetiously-long word!

И я получил результат:

abc def ghi
Line: [[abc def ghi
]]
Word: [[abc]]
Word: [[def]]
tmp incremented.
Word: [[ghi]]
tmp incremented.
tmp incremented.
Node word: [[ghi]]
Node word: [[def]]
Node word: [[abc]]
mno pqr stuvwxyz humongously-and-tempestuously-but-neither-abstemiously-nor-facetiously-long word!
Line: [[mno pqr stuvwxyz humongously-and-tempestuously-but-neither-abstemiously-nor-facetiously-long word!
]]
Word: [[mno]]
tmp incremented.
tmp incremented.
tmp incremented.
Word: [[pqr]]
tmp incremented.
tmp incremented.
tmp incremented.
tmp incremented.
Word: [[stuvwxyz]]
tmp incremented.
tmp incremented.
tmp incremented.
tmp incremented.
tmp incremented.
Word: [[humongously-and-tempestuously-but-neither-abstemiously-nor-facetiously-long]]
tmp incremented.
tmp incremented.
tmp incremented.
tmp incremented.
tmp incremented.
tmp incremented.
Word: [[word!]]
tmp incremented.
tmp incremented.
tmp incremented.
tmp incremented.
tmp incremented.
tmp incremented.
tmp incremented.
Node word: [[word!]]
Node word: [[humongously-and-tempestuously-but-neither-abstemiously-nor-facetiously-long]]
Node word: [[stuvwxyz]]
Node word: [[pqr]]
Node word: [[mno]]
Node word: [[ghi]]
Node word: [[def]]
Node word: [[abc]]
Freeing list
Freeing: [[word!]]
Freeing: [[humongously-and-tempestuously-but-neither-abstemiously-nor-facetiously-long]]
Freeing: [[stuvwxyz]]
Freeing: [[pqr]]
Freeing: [[mno]]
Freeing: [[ghi]]
Freeing: [[def]]
Freeing: [[abc]]

При запуске под Valgrind я получил несколько любопытных результатов, но это потому, что я обновил ОС (Mac OS X Yosemite до El Capitan) без обновления подавлений. Все утечки были в системном коде, а не в этом коде, AFAICT.


Пост-чат

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

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


typedef struct Node2
{
    char* word;
    struct Node2 *next;
} Node2;

Node2* head = NULL;
Node2* now = NULL;
int uniqueWords = 0;
int totalMTFRComparisons = 0;

bool DEBUG = true;

static
Node2* iNode(char* word)
{   
    if (DEBUG)
        printf("addNode2 called [[%s]].\n", word);
    Node2* tmp = head;
    Node2* prev = 0;

    while (tmp)
    {
        totalMTFRComparisons++;
        printf("Current word: %s\n", tmp->word);
        if (strcmp(tmp->word, word) == 0)
        {
            printf("Already entered: [[%s]]\n", tmp->word);
            if (prev != 0)
            {
                prev->next = tmp->next;
                tmp->next = head;
                head = tmp;
            }
            //tmp->next = prev;
            return tmp;
        }
        prev = tmp;
        tmp = tmp->next;
        printf("tmp incremented.\n");
    }

    Node2* ptr = malloc(sizeof(Node2));
    printf("New word: [[%s]]\n", word);
    uniqueWords++;
    ptr->word = malloc(strlen(word) + 1);
    strcpy(ptr->word, word);
    ptr->next = NULL;
    if (head == NULL)
    {
        head = now = ptr;
        return ptr;
    }
    else
    {
        ptr->next = head;
        head = ptr;
    }
    return ptr;
}

static
char* getString(void)
{
    static char buffer[128];
    while (fgets(buffer, 128, stdin) != NULL)
    {
        char *nl = strchr(buffer, '\n');
        if (nl != 0)
            *nl = '\0';
        printf("Line: [[%s]]\n", buffer);
        iNode(buffer);
    }
    return buffer;
}

int main(void)
{
    getString();
    printf("Total words: %d, total MTFR comparisons: %d\n", uniqueWords, totalMTFRComparisons);

    Node2* ptr2 = head;
    Node2* tmp2 = head;
    while (ptr2 != NULL)
    {
        printf("Freeing: [[%s]]\n", ptr2->word);
        tmp2 = ptr2->next;
        free(ptr2->word);
        free(ptr2);
        ptr2 = tmp2;
    }
}

Имеется диагностическая печать. Удаление новой строки означает, что диагностический вывод короче, чем вы увидите в чате, если вы потрудитесь посмотреть чат (где строки имели новую строку в конце - строки обрабатывались как слова, включая новую строку).

3
Jonathan Leffler 21 Окт 2015 в 05:55
ptr->word = malloc(strlen(word));

Над строкой: неверно, мало памяти Пример: strlen ("hello") = 5, а sizeof ("hello") даст вам 6

Изменил строку на

ptr->word = malloc(sizeof(word));

Тогда после того, как strcpy будет работать правильно

0
Sohil Omer 21 Окт 2015 в 05:01