C - Перераспределение указателя на компонент структуры (указателя типа) прервано (дамп памяти) - PullRequest
0 голосов
/ 18 января 2020

Я хочу запрограммировать график, но у меня возникают проблемы, когда я хочу добавить вершины. Когда я хочу перераспределить память, программа останавливается, и консоль просто говорит «aborted (core dumped)».

Вот мой код

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

struct Graph
{
    int VertexCounter;
    struct Vertices *vertex;
    struct Edge **adjMat;
}MyGraph;

struct Vertices
{
    int id;
    char name[15];
    float xPos;
    float yPos;
};

struct Edge
{
    int id;
    struct Vertices *start;
    struct Vertices *end;
};


//Initializing a graph with memory for one Vertex and one 1x1 adjecency Matrix but setting the number of Vertices to zero
void initGraph(struct Graph *graph)
{
    graph = (struct Graph *) calloc(1, sizeof(struct Graph));
    graph->vertex = (struct Vertices *) calloc(1, sizeof(struct Vertices));
    graph->adjMat = (struct Edge **) calloc(1, sizeof(struct Edge *));
    *(graph->adjMat) = (struct Edge *) calloc(1, sizeof(struct Edge));

    graph->VertexCounter = 0;
    printf("Number of Vertices: %d\n", graph->VertexCounter);

}

//printing the number of Vertices
void test(struct Graph *graph)
{
    printf("Number of Vertices: %d\n", (*graph).VertexCounter);
}


//Reallocating the memory for an additional Vertex. 
//I multiply the VertexCounter - 1 because the struct Graph contains space for one pointer of the type (struct Vertices *)
void addVertex(struct Graph *graph)
{
    graph->VertexCounter++;
    graph = (struct Graph *) realloc(graph, sizeof(struct Graph) + 
                            (graph->VertexCounter - 1) * sizeof(struct Vertices));

}


int main()
{
    struct Graph *graphPointer;
    graphPointer = &MyGraph;
    initGraph(graphPointer);
    test(graphPointer);
    addVertex(graphPointer);
    test(graphPointer);
    return 0;
}

Вывод:

Количество вершин: 0

Количество вершин: 0

Прервано (сброшено ядро)

Таким образом, функция addVertex (struct Graph *) не работает.

Я еще не включил перераспределение для новых Edges.

Как я могу решить эту проблему?

Обновление:

void addVertex(struct Graph **graph)
{
    (*graph)->VertexCounter++;
    *graph = realloc(*graph, sizeof(struct Graph) + 
                ((*graph)->VertexCounter - 1) * sizeof(struct Vertices));

}


int main()
{
    struct Graph *graphPointer;
    graphPointer = &MyGraph;
    initGraph(graphPointer);
    test(graphPointer);
    addVertex(&graphPointer);
    test(graphPointer);
    return 0;
}

Это вернет тот же результат как и раньше

Обновление 2:

void initGraph(struct Graph **graph)
{
    (*graph) = (struct Graph *) calloc(1, sizeof(struct Graph *));
    (*graph)->vertex = (struct Vertices *) calloc(1, sizeof(struct Vertices));
    (*graph)->adjMat = (struct Edge **) calloc(1, sizeof(struct Edge *));
    *((*graph)->adjMat) = (struct Edge *) calloc(1, sizeof(struct Edge));

    (*graph)->VertexCounter = 0;
    printf("Number of Vertices: %d\n", (*graph)->VertexCounter);

}

//printing the number of Vertices
void test(struct Graph *graph)
{
    printf("Number of Vertices: %d\n", (*graph).VertexCounter);
}


//Reallocating the memory for an additional Vertex. 
//I multiply the VertexCounter - 1 because the struct Graph contains space for one pointer of the type (struct Vertices *)
void addVertex(struct Graph **graph)
{

    (*graph)->VertexCounter++;

    void *temp = realloc(temp, sizeof(struct Graph *));
    *graph = temp;

    if(graph == NULL)
        printf("Realloc failed");
}


int main()
{
    struct Graph *graphPointer;
    graphPointer = &MyGraph;
    initGraph(&graphPointer);
    test(graphPointer);
    addVertex(&graphPointer);
    test(graphPointer);
    return 0;
}

Я изменил initGraph и addVertex, но вывод не изменится.

Ответы [ 2 ]

0 голосов
/ 18 января 2020

ОК, я свел к минимуму то, что вы пытались сделать в своем примере. Одна проблема с логами c, с которой вы столкнулись, находится в initGraph. Там вы хотите выделить struct Graph, а не struct Vertices или struct Edge **adjMat;. Если вы не добавляете значение ни для одного из них, просто установите эти указатели на NULL до тех пор, пока вам фактически не потребуется выделить их для обоих.

Во-первых, давайте переупорядочим ваши определения struct, чтобы любая структура требовала как часть другой определяется до того, как она потребуется, например,

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

struct Vertices {
    int id;
    char name[15];
    float xPos;
    float yPos;
};

struct Edge {
    int id;
    struct Vertices *start;
    struct Vertices *end;
};

struct Graph {
    int VertexCounter;
    struct Vertices *vertex;
    struct Edge **adjMat;
};

( примечание: ваш экземпляр MyGraph удален - это не нужно, если вы выделяете для graph)

Давайте перейдем вперед и посмотрим, как ваш указатель будет обрабатываться в main(), например,

Изменить измененный параметр на addVertex

Обратите внимание, что во всем приведенном ниже коде параметр для addVertex был изменен с struct Graph **graph на struct Graph *graph, поскольку нет перераспределения graphpointer в addVertex.

int main (void) {

    struct Graph *graphPointer = NULL;

    initGraph (&graphPointer);
    test(graphPointer);

    addVertex (graphPointer);
    test(graphPointer);

    freeGraph (graphPointer);   /* don't forget to free what you allocate */
}

Выше, graphpointer - единственный необходимый указатель. Затем вы передаете адрес graphpointer на initGraph, чтобы initGraph мог работать с оригиналом, а выделение в initGraph было доступно обратно в main().

Чтобы это произошло, Вы можете сделать следующее в initGraph (примечание: выделение для любых других участников в struct Graph задерживается до тех пор, пока вам действительно не будет чего добавить). Если вы попытаетесь предварительно выделить что-то, что не произойдет до тех пор, пока в вашем коде не появится неизвестная точка - вы создадите рецепт для катастрофы. Выделите то, что вам нужно, когда вам это нужно. (это не мешает вам выделять для каждого блока и хранить счетчик для уменьшения необходимого количества realloc)

Ваш initGraph становится:

void initGraph (struct Graph **graph)
{
    *graph = calloc (1, sizeof **graph);
    if (!*graph) {  /* validate EVERY allocation */
        perror ("calloc-*graph");
        exit (EXIT_FAILURE);
    }

    (*graph)->vertex = NULL;    /* set pointers NULL until you add_vertex */
    (*graph)->adjMat = NULL;    /* or you add_adjmat */

    (*graph)->VertexCounter = 0;    /* initialize counter */
}

Выше вы инициализировали один struct Graph - больше ничего - как и должно быть. В то время как с initGraph вы должны были передать адрес graphpointer, чтобы распределение можно было увидеть в main(), с addVertex все, что вам нужно передать, это graphpointer - потому что адрес этого указателя делает НЕ изменяется в addVertex. Все, что вы делаете, это выделяете элемент struct Graph в graphpointer, который можно просто назначить указателю vertex. Теперь, когда вы готовы добавить вершину, вы можете сделать:

void addVertex (struct Graph *graph)
{
    /* always realloc using a temporary pointer */
    void *tmp = realloc (graph->vertex, (graph->VertexCounter + 1) * 
                            sizeof *graph->vertex);
    if (!tmp) { /* validate EVERY reallocation */
        perror ("realloc-(*graph)->vertex");
        exit (EXIT_FAILURE);
    }
    graph->vertex = tmp;

    graph->VertexCounter++;
}

Также отметьте в addVertex, что все, над чем вы работаете, это struct Vertices член graphpointer - также как и должно быть. Если у вас есть вершина для добавления в этой точке, рассмотрите возможность передачи ее в качестве параметра (или данных в качестве параметров), чтобы данные вершины можно было назначать / копировать в addVertex, чтобы хранить весь код добавления-вершины в одном месте. .

В любом написанном вами коде, который динамически распределяет память, у вас есть 2 обязанностей в отношении любого выделенного блока памяти: (1) всегда сохраняется указатель на начальный адрес для блока памяти, таким образом, (2) он может быть освобожден , когда он больше не нужен. Быстрая функция freeGraph для free, которая была выделена на данный момент, может быть:

void freeGraph (struct Graph *graph)
{
    free (graph->vertex);
    free (graph);
}

(когда вы добавляете выделение для adjMat, не забудьте добавить соответствующий free в freeGraph)

Теперь, в целом, вы можете сделать:

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

struct Vertices {
    int id;
    char name[15];
    float xPos;
    float yPos;
};

struct Edge {
    int id;
    struct Vertices *start;
    struct Vertices *end;
};

struct Graph {
    int VertexCounter;
    struct Vertices *vertex;
    struct Edge **adjMat;
};

void initGraph (struct Graph **graph)
{
    *graph = calloc (1, sizeof **graph);
    if (!*graph) {  /* validate EVERY allocation */
        perror ("calloc-*graph");
        exit (EXIT_FAILURE);
    }

    (*graph)->vertex = NULL;    /* set pointers NULL until you add_vertex */
    (*graph)->adjMat = NULL;    /* or you add_adjmat */

    (*graph)->VertexCounter = 0;    /* initialize counter */
}

void test (struct Graph *graph)
{
    printf("Number of Vertices: %d\n", graph->VertexCounter);
}

void addVertex (struct Graph *graph)
{
    /* always realloc using a temporary pointer */
    void *tmp = realloc (graph->vertex, (graph->VertexCounter + 1) * 
                            sizeof *graph->vertex);
    if (!tmp) { /* validate EVERY reallocation */
        perror ("realloc-(*graph)->vertex");
        exit (EXIT_FAILURE);
    }
    graph->vertex = tmp;

    graph->VertexCounter++;
}

void freeGraph (struct Graph *graph)
{
    free (graph->vertex);
    free (graph);
}

int main (void) {

    struct Graph *graphPointer = NULL;

    initGraph (&graphPointer);
    test(graphPointer);

    addVertex (graphPointer);
    test(graphPointer);

    freeGraph (graphPointer);   /* don't forget to free what you allocate */
}

/* move to add_adjmat functions:

    (*graph)->adjMat = calloc (1, sizeof *(*graph)->adjMat);
    if (!(*graph)->adjMat) {
        perror ("calloc-(*graph)->adjMat");
        exit (EXIT_FAILURE);
    }

    *(*graph)->adjMat = calloc (1, sizeof **(*graph)->adjMat);

*/

( примечание: комментарии внизу к выделению для adjMat к какой функции будет иметь отношение add_adjmat(), et c ..)

Пример использования / Вывод

$ ./bin/graphalloc
Number of Vertices: 0
Number of Vertices: 1

Использование памяти / проверка ошибок

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

Для Linux valgrind это нормальный выбор. Есть похожие проверки памяти для каждой платформы. Все они просты в использовании, просто запустите вашу программу через него.

$ valgrind --leak-check=full ./bin/graphalloc
==19442== Memcheck, a memory error detector
==19442== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==19442== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==19442== Command: ./bin/graphalloc
==19442==
Number of Vertices: 0
Number of Vertices: 1
==19442==
==19442== HEAP SUMMARY:
==19442==     in use at exit: 0 bytes in 0 blocks
==19442==   total heap usage: 3 allocs, 3 frees, 1,076 bytes allocated
==19442==
==19442== All heap blocks were freed -- no leaks are possible
==19442==
==19442== For counts of detected and suppressed errors, rerun with: -v
==19442== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

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

Это подход, который позволит вам добавить то, что вам нужно, когда вам нужно. Обратите внимание, чтобы минимизировать перераспределение, вы можете добавить VertexAllocated член и выделить начальный блок 8, 16, 32, etc.., а затем, когда VertexCounter == VertexAllocated, вы можете позвонить realloc тогда и realloc 2 раза VertexAllocated и продолжать.

Посмотрите вещи и дайте мне знать, если у вас есть вопросы.

0 голосов
/ 18 января 2020

Как указано в комментарии выше, вам нужно следить за тем, когда вы меняете, или МОЖЕТЕ изменить graphPointer. Например, каждый раз, когда вы вызываете reallo c, система может быть вынуждена переместить вашу структуру и возвращает новый указатель на новое распределение. Опять же, как указано, этот новый указатель отбрасывается, например, в конце addVertex. Если вы собираетесь изменить указатель в функции, как и любой другой параметр «вызов по ссылке», передайте указатель на него. (Если вы абсолютно неукоснительно относитесь к типам и используете только тип, когда вы уверены, что вам это нужно, компилятор поможет с этим.)

Еще одна вещь, которую вам действительно нужно сделать, это проверить возвращаемые значения из ваших вызовов. Иногда вы думаете, что вызов работает, но это не так, и он всегда возвращает NULL, потому что ваши параметры неверны. В любом случае, вы не хотите, чтобы ваша программа когда-нибудь в будущем взломала sh без указания причины. Например, если происходит сбой reallo c, выведите как минимум ошибку reallo c и завершите работу.

...