我在这里如何错误地使用malloc?

时间:2019-04-05 18:11:51

标签: c arrays struct segmentation-fault malloc

我正在尝试学习如何在C99的运行时正确分配内存。

我只写了一个最小的例子,因为我认为这对我想做的事情很有启发性。出于某种原因,对malloc的“内部”调用只分配了数组中第一个元素所期望的(即分配了内存),其中分配了大小为sizeof(letter_t)的内存块。

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

typedef struct letter_t {
    char *from;
    int lines;
} letter_t;

typedef struct letterbox_t {
    char *name;
    int n_letters;
    struct letter_t **letters;
} letterbox_t;

int main() {

    char *name[]    = { "amy", "bob", "claud" };
    int n_letters[] = { 1,     3,     2 };

    // layout memory and populate letterbox_t array
    struct letterbox_t *letterboxes;
    letterboxes = malloc(sizeof(letterbox_t) * 3);

    for (int i = 0; i < 3; i++) {
        letterboxes[i].name = name[i];
        letterboxes[i].n_letters = n_letters[i];

        struct letter_t *letters[n_letters[i]];
        for (int j = 0; j < n_letters[i]; j++) {
            letters[j] = malloc(sizeof(letter_t));
        }
        letterboxes[i].letters = letters;
    }

    // populate letter_t array for each letterbox_t
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < letterboxes[i].n_letters; j++) {
            // =========================================
            letterboxes[i].letters[j]->from = "spammer";
            // =========================================
            // the above line fails for i = 1, j = 1
        }
    }

    for (int i = 0; i < 3; i++) {
        printf("%s has %d letters from\n", letterboxes[i].name, letterboxes[i].n_letters);
        for (int j = 0; j < letterboxes[i].n_letters; j++) {
            printf("  %s\n", letterboxes[i].letters[j]->from);
        }
    }
    return 0;
};

当内部循环上的j到达1时,我看到的只是垃圾内存。这是一些GDB输出作为说明。

Breakpoint 1, main () at example.c:40
40            letterboxes[i].letters[j]->from = "spammer";
(gdb) p i
$1 = 1
(gdb) p j
$2 = 1
(gdb) p letterboxes[i].letters[j]
$3 = (struct letter_t *) 0x400604 <main+228>
(gdb) p *letterboxes[i].letters[j]
$4 = {from = 0x904d8b48ac7d6348 <error: Cannot access memory at address 0x904d8b48ac7d6348>, lines = -117143224}

2 个答案:

答案 0 :(得分:1)

您有很多小问题。首先,如我的评论中所述,您为struct letter_t* letters[n_letters[i]];进行分配的尝试与struct letter_t** letters;

不匹配

接下来,在进一步探讨之前,'*'属于变量名,而不是大多数情况下的类型。为什么?

int* a, b, c;

以上,您肯定不是在int中声明3个指针。相反,您声明了整数指针a和整数b, c。更清晰的写为:

int *a, b, c;

分配内存时,您必须验证分配是否成功-每次,例如

    size_t n_people = sizeof name / sizeof *name;

    // layout memory and populate letterbox_t array
    struct letterbox_t *letterboxes;
    /* allocate letterboxes for each of the people */
    letterboxes = malloc (sizeof *letterboxes * n_people);
    if (!letterboxes) {     /* validate Every allocation */
        perror ("malloc-letterboxes");
        return 1;
    }

您现在已分配了3个letterbox_t的存储空间,可以开始处理其中的内容。您可以为每个名称分配字母的名称和数量:

    for (size_t i = 0; i < n_people; i++) {

        /* assigning pointer to string literal */
        letterboxes[i].name = name[i];
        letterboxes[i].n_letters = n_letters[i];    /* int assignment */

注意:,请当心。请理解,您正在将name[i]中的 String Literal 分配给每个letterboxes[i].name。这意味着{{1} }无法修改,也不应释放。通常应为letterboxes[i].name分配存储空间并进行复制)

name是指向letterboxes[i].letters pointer-to-pointer 。意味着您必须先分配指针,然后为每个letter_t分配存储空间,并将该内存块的起始地址分配给每个指针,例如letter。例如:

letterboxes[i].letters[j]

现在,所有存储空间都已正确分配 并经过验证 ,您可以向每个人填充每个字母,然后输出结果,例如

        /* allocate letterboxes[i].n_letters pointers */
        letterboxes[i].letters =
            malloc (sizeof *letterboxes[i].letters * letterboxes[i].n_letters);
        if (!letterboxes[i].letters) { /* validate allocation */
            perror ("malloc-letterboxes.letters");
            return 1;
        }

        /* allocate letters per-pointer */
        for (int j = 0; j < letterboxes[i].n_letters; j++) {
            letterboxes[i].letters[j] = 
                            malloc (sizeof *letterboxes[i].letters[j]);
            if (!letterboxes[i].letters[j]) {
                perror ("malloc-letterboxes[i].letters[j]");
                return 1;
            }
        }

使用完分配的内存后,您可以确定是否已正确释放它。 (随着程序的增长以及您开始在函数内进行分配,这变得至关重要)。无法释放您使用的内容会导致程序中的内存泄漏。为此,编写一个简单的函数来完全释放 // populate letter_t array for each letterbox_t for (size_t i = 0; i < n_people; i++) { for (int j = 0; j < letterboxes[i].n_letters; j++) { letterboxes[i].letters[j]->from = "spammer"; /* added lines just to complete assignments */ letterboxes[i].letters[j]->lines = letterboxes[i].n_letters * 10; } } // output all letterboxes and letters for (size_t i = 0; i < n_people; i++) { printf("%s has %d letters from\n", letterboxes[i].name, letterboxes[i].n_letters); for (int j = 0; j < letterboxes[i].n_letters; j++) { printf(" %s %d\n", letterboxes[i].letters[j]->from, letterboxes[i].letters[j]->lines); } } 是有意义的,例如

letterbox_t

然后,当您用完内存后,可以/* simple function to free single letterbox_t completely */ void freeletterbox (letterbox_t *l) { for (int i = 0; i < l->n_letters; i++) free (l->letters[i]); free (l->letters); } ,例如

free()

完全将其放入,您可以这样做:

    for (size_t i = 0; i < n_people; i++)   /* free each letterbox */
        freeletterbox (&letterboxes[i]);
    free (letterboxes);                     /* free pointers */

使用/输出示例

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

typedef struct letter_t {
    char *from;
    int lines;
} letter_t;

typedef struct letterbox_t {
    char *name;
    int n_letters;
    struct letter_t **letters;
} letterbox_t;

/* simple function to free single letterbox_t completely */
void freeletterbox (letterbox_t *l)
{
    for (int i = 0; i < l->n_letters; i++)
        free (l->letters[i]);

    free (l->letters);
}

int main (void) {

    char *name[]    = {"amy", "bob", "claud"};
    int n_letters[] = {1,     3,     2};
    size_t n_people = sizeof name / sizeof *name;

    // layout memory and populate letterbox_t array
    struct letterbox_t *letterboxes;
    /* allocate letterboxes for each of the people */
    letterboxes = malloc (sizeof *letterboxes * n_people);
    if (!letterboxes) {     /* validate Every allocation */
        perror ("malloc-letterboxes");
        return 1;
    }

    for (size_t i = 0; i < n_people; i++) {

        /* assigning pointer to string literal */
        letterboxes[i].name = name[i];
        letterboxes[i].n_letters = n_letters[i];    /* int assignment */

        /* allocate letterboxes[i].n_letters pointers */
        letterboxes[i].letters =
            malloc (sizeof *letterboxes[i].letters * letterboxes[i].n_letters);
        if (!letterboxes[i].letters) { /* validate allocation */
            perror ("malloc-letterboxes.letters");
            return 1;
        }

        /* allocate letters per-pointer */
        for (int j = 0; j < letterboxes[i].n_letters; j++) {
            letterboxes[i].letters[j] = 
                            malloc (sizeof *letterboxes[i].letters[j]);
            if (!letterboxes[i].letters[j]) {
                perror ("malloc-letterboxes[i].letters[j]");
                return 1;
            }
        }
    }

    // populate letter_t array for each letterbox_t
    for (size_t i = 0; i < n_people; i++) {
        for (int j = 0; j < letterboxes[i].n_letters; j++) {
            letterboxes[i].letters[j]->from = "spammer";
            /* added lines just to complete assignments */
            letterboxes[i].letters[j]->lines = letterboxes[i].n_letters * 10;
        }
    }

    // output all letterboxes and letters
    for (size_t i = 0; i < n_people; i++) {
        printf("%s has %d letters from\n", 
                        letterboxes[i].name, letterboxes[i].n_letters);
        for (int j = 0; j < letterboxes[i].n_letters; j++) {
            printf("  %s  %d\n", letterboxes[i].letters[j]->from,
                                letterboxes[i].letters[j]->lines);
        }
    }

    for (size_t i = 0; i < n_people; i++)   /* free each letterbox */
        freeletterbox (&letterboxes[i]);
    free (letterboxes);                     /* free pointers */

    return 0;
}

内存使用/错误检查

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

当务之急是使用一个内存错误检查程序来确保您不会尝试访问内存或在已分配的块的边界之外/之外进行写入,不要试图以未初始化的值读取或基于条件跳转,最后,以确认您释放了已分配的所有内存。

对于Linux,$ ./bin/letters amy has 1 letters from spammer 10 bob has 3 letters from spammer 30 spammer 30 spammer 30 claud has 2 letters from spammer 20 spammer 20 是正常选择。每个平台都有类似的内存检查器。它们都很容易使用,只需通过它运行程序即可。

valgrind

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

仔细检查一下,如果还有其他问题,请告诉我。

答案 1 :(得分:0)

更改此:

struct letter_t* letters[n_letters[i]];

对此:

struct letter_t** letters = malloc(n_letters[i] * sizeof(struct letter_t*));

因为,正如@TomKarzes所评论的那样,您在for循环的主体内创建了letters,因此一旦循环终止,它就超出了范围。

因此,您需要为其动态分配内存,以便在循环终止后不会释放内存。

PS:不要忘了在程序结束时释放内存,遵循与动态分配内存时所遵循的顺序相反的顺序。