首页 > 解决方案 > 如何修复 C 中的“从 0x4000000000000000 读取内存失败(读取 4 个字节中的 0 个)”错误

问题描述

我有一个简单的 C 练习要做。但是当我用 malloc 初始化我的变量图时,它没有正确执行操作。这是我的代码:

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

typedef int Weight;

typedef struct aux {
    int vDest;
    Weight weight;
    struct aux * next;
} TypeEdge;

typedef TypeEdge* TypePointer;

typedef struct {
    TypePointer * listAdj;
    int numVertices;
    int numEdges;
} TypeGraph;

typedef int* TipoInt;

bool initializeGraph(TypeGraph *graph, int nv) {
    if (nv < 0) {
        return false;
    }

    graph->numVertices = nv;
    graph->numEdges = 0;
    int i;
    for (i = 0; i < nv; i++) {
        graph->listAdj = (TypePointer*) malloc(sizeof(TypePointer));
    }
    for (i = 0; i < nv; i++) {
        graph->listAdj[i] = NULL;
    }
    return true;
}

void insertEdge(int v1, int v2, Weight weight, TypeGraph *graph) {
    if (v1 < 0 || v1 > graph->numVertices || v2 < 0 || v2 > graph->numVertices) {
        return;
    }
    TypePointer actual = graph->listAdj[v1];
    while (actual->next) {
        actual = actual->next;
    }
    TypePointer pNew = (TypePointer) malloc(sizeof(TypeEdge));
    pNew->vDest = v2;
    pNew->weight = weight;
    pNew->next = NULL;
    actual->next = pNew;
}

int main() {
    TypeGraph graph;
    bool result = initializeGraph(&graph, 100);
    if (result) {
        insertEdge(2, 3, 1, &graph);
    }
    return 0;
}

问题在于,它没有初始化大小为 100 的 TypePointer 的图形,而是仅初始化大小为 2 的图形并且不执行任何操作。当我尝试对其进行调试时,Clion它会显示此错误消息:read memory from 0x4000000000000000 failed (0 of 4 bytes read). 如果我只运行代码,它会返回代码十一。

请问,有人可以帮我吗?

标签: cmalloc

解决方案


你的手上真的是一团糟。由您typedef的指针造成,但主要是因为您未能分配nv指针,而是分配相同的listAdj指针,然后立即用NULL创建 100 内存泄漏覆盖指针。

想一想:

    for (i = 0; i < nv; i++) {
        graph->listAdj = (TypePointer*) malloc(sizeof(TypePointer));
    }

(没有必要强制返回malloc,这是不必要的。请参阅:我是否强制转换 malloc 的结果?并且...总是验证每个分配)

graph->listAdj是一个单指针,每次迭代都保存新分配的内存块的地址,然后被每个后续分配覆盖。

此外,您然后尝试取消引用被覆盖的指针并将不存在的指针设置为NULL,例如:

    for (i = 0; i < nv; i++) {
        graph->listAdj[i] = NULL;
    }

您只尝试分配一个graph->listAdj(尽管 100 次),而不是nv您想要的指针。nv您必须立即为指针分配存储空间。

现在让我们从头开始,通过 remove ALL 清理一切typedef,并简单地使用intfor bool,例如

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

typedef struct aux {
    int vDest;
    int weight;
    struct aux *next;
} TypeEdge;

typedef struct {
    TypeEdge **listAdj;
    int numVertices;
    int numEdges;
} TypeGraph;

现在,任何查看您的代码的人都清楚地知道这是listAdj一个指向TypeEdge. 无需猜测,也无需再四处寻找,想知道TypePointer300 行之后TypeEdgeTypeEdge什么,仅此而已。

当你initializeGraph,你需要分配所有nv的指针是一个单一的调用malloc,而不是在一个循环中。然后你可以循环设置它们的指针NULL,例如

int initializeGraph (TypeGraph *graph, int nv)
{
    if (nv < 0)
        return 0;

    graph->numVertices = nv;
    graph->numEdges = 0;
    int i;

    if ((graph->listAdj = malloc(sizeof *graph->listAdj * nv)) == NULL) {
        perror ("malloc-graph->listAdj");
        return 0;
    }

    for (i = 0; i < nv; i++)
        (graph->listAdj)[i] = NULL;

    return 1;
}

接下来insertEdge(),您必须处理为该顶点插入第一条边的情况,或者是否需要迭代到列表末尾并插入到那里。您还需要调整迭代到最后的方式,以确保您不会尝试访问actual->nextif actualis NULL。把它放在一起,你可以这样做:

TypeEdge *insertEdge (int v1, int v2, int weight, TypeGraph *graph) 
{
    if (v1 < 0 || v1 > graph->numVertices || 
        v2 < 0 || v2 > graph->numVertices) {
        return NULL;
    }

    TypeEdge *actual = graph->listAdj[v1];
    while (actual && actual->next)
        actual = actual->next;

    TypeEdge *pNew = malloc(sizeof *pNew);
    if (!pNew) {
        perror ("malloc-pNew");
        return NULL;
    }

    pNew->vDest = v2;
    pNew->weight = weight;
    pNew->next = NULL;

    if (!actual)
        graph->listAdj[v1] = pNew;
    else
        actual->next = pNew;

    return (pNew);
}

注意:函数的返回类型如何更改为TypeEdge *from ,void因此您有一个有意义的返回,可以指示您尝试插入边缘的成功/失败。永远不要在void没有向调用函数指示的情况下在函数内分配是否分配(和其余关键步骤)成功或失败)

虽然您main()尝试insertEdge()这样做,但它并没有给您任何关于发生了什么的迹象。除非您有某种方法可以验证您是否实际插入了边缘,否则您会感到疑惑。只需编写一组简短的print函数来处理为每个具有它们的顶点输出边列表,例如

void prnedge (const TypeEdge *e)
{
    do
        printf (" %3d %3d\n", e->vDest, e->weight);
    while ((e = e->next));
}

void print_edge (const TypeEdge *e, int edge)
{
    printf ("\nedge %d\n", edge);
    prnedge (e);
}

void print_graph (const TypeGraph *g)
{
    for (int i = 0; i < g->numVertices; i++)
        if (g->listAdj[i])
            print_edge (g->listAdj[i], i);
}

如果您已分配内存,则还需要能够释放该内存。一组类似的短函数可以处理释放每个列表,例如

void freelist (TypeEdge *l)
{
    while (l) {
        TypeEdge *victim = l;
        l = l->next;
        free (victim);
    }
}

void free_graphlists (TypeGraph *g)
{
    for (int i = 0; i < g->numVertices; i++)
        if (g->listAdj[i])
            freelist (g->listAdj[i]);

    free (g->listAdj);
}

您可以调用main()如下:

int main (void) {

    TypeGraph graph;
    int result = initializeGraph (&graph, 100);

    if (result) {
        insertEdge (2, 3, 1, &graph);
        insertEdge (2, 4, 1, &graph);
    }

    print_graph (&graph);
    free_graphlists (&graph);

    return 0;
}

总而言之,你可以这样做:

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

typedef struct aux {
    int vDest;
    int weight;
    struct aux *next;
} TypeEdge;

typedef struct {
    TypeEdge **listAdj;
    int numVertices;
    int numEdges;
} TypeGraph;

int initializeGraph (TypeGraph *graph, int nv)
{
    if (nv < 0)
        return 0;

    graph->numVertices = nv;
    graph->numEdges = 0;
    int i;

    if ((graph->listAdj = malloc(sizeof *graph->listAdj * nv)) == NULL) {
        perror ("malloc-graph->listAdj");
        return 0;
    }

    for (i = 0; i < nv; i++)
        (graph->listAdj)[i] = NULL;

    return 1;
}

TypeEdge *insertEdge (int v1, int v2, int weight, TypeGraph *graph) 
{
    if (v1 < 0 || v1 > graph->numVertices || 
        v2 < 0 || v2 > graph->numVertices) {
        return NULL;
    }

    TypeEdge *actual = graph->listAdj[v1];
    while (actual && actual->next)
        actual = actual->next;

    TypeEdge *pNew = malloc(sizeof *pNew);
    if (!pNew) {
        perror ("malloc-pNew");
        return NULL;
    }

    pNew->vDest = v2;
    pNew->weight = weight;
    pNew->next = NULL;

    if (!actual)
        graph->listAdj[v1] = pNew;
    else
        actual->next = pNew;

    return (pNew);
}

void prnedge (const TypeEdge *e)
{
    do
        printf (" %3d %3d\n", e->vDest, e->weight);
    while ((e = e->next));
}

void print_edge (const TypeEdge *e, int edge)
{
    printf ("\nedge %d\n", edge);
    prnedge (e);
}

void print_graph (const TypeGraph *g)
{
    for (int i = 0; i < g->numVertices; i++)
        if (g->listAdj[i])
            print_edge (g->listAdj[i], i);
}

void freelist (TypeEdge *l)
{
    while (l) {
        TypeEdge *victim = l;
        l = l->next;
        free (victim);
    }
}

void free_graphlists (TypeGraph *g)
{
    for (int i = 0; i < g->numVertices; i++)
        if (g->listAdj[i])
            freelist (g->listAdj[i]);

    free (g->listAdj);
}

int main (void) {

    TypeGraph graph;
    int result = initializeGraph (&graph, 100);

    if (result) {
        insertEdge (2, 3, 1, &graph);
        insertEdge (2, 4, 1, &graph);
    }

    print_graph (&graph);
    free_graphlists (&graph);

    return 0;
}

示例使用/输出

$ ./bin/edgetype

edge 2
   3   1
   4   1

内存使用/错误检查

在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您有 2 个责任:(1)始终保留指向内存块起始地址的指针,(2)它可以在它不存在时被释放更需要。

您必须使用内存错误检查程序来确保您不会尝试访问内存或写入超出/超出分配块的范围,尝试读取或基于未初始化的值进行条件跳转,最后确认释放所有分配的内存。

对于 Linuxvalgrind是正常的选择。每个平台都有类似的内存检查器。它们都易于使用,只需通过它运行您的程序即可。

$ valgrind ./bin/edgetype
==21679== Memcheck, a memory error detector
==21679== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==21679== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==21679== Command: ./bin/edgetype
==21679==

edge 2
   3   1
   4   1
==21679==
==21679== HEAP SUMMARY:
==21679==     in use at exit: 0 bytes in 0 blocks
==21679==   total heap usage: 3 allocs, 3 frees, 832 bytes allocated
==21679==
==21679== All heap blocks were freed -- no leaks are possible
==21679==
==21679== For counts of detected and suppressed errors, rerun with: -v
==21679== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

始终确认您已释放所有已分配的内存并且没有内存错误。

如果您还有其他问题,请仔细查看并告诉我。


推荐阅读