【问题标题】:How am I using malloc incorrectly here?我如何在这里错误地使用 malloc?
【发布时间】:2019-04-05 18:11:50
【问题描述】:

我正在尝试学习如何在 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}

【问题讨论】:

  • struct letter_t* letters[n_letters[i]];struct letter_t** letters; 不匹配 由于lettersstruct letter_t**,因此在为每个分配和分配内存块的地址之前,您需要分配所需的pointers 的数量letters[i].
  • 问题出在这里:struct letter_t* letters[n_letters[i]];这是本地存储。超出范围时未定义。您需要为此使用malloc

标签: c arrays struct segmentation-fault malloc


【解决方案1】:

你有很多小问题。首先,正如我在评论中提到的,您分配给struct letter_t* letters[n_letters[i]]; 的尝试与struct letter_t** letters; 不匹配

接下来,在继续之前,一个 nit,'*' 属于变量名,而不是大多数情况下的类型。为什么?

int* a, b, c;

在上面,您肯定没有将三指针声明为int。相反,您声明整数指针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 */

(注意:小心。了解您将 String Literalname[i] 分配给每个letterboxes[i].name。这意味着letterboxes[i].name 不能被修改并且应该不会被释放。一般你应该为name分配存储空间并复制)

letterboxes[i].letters 是一个指向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;
}

使用/输出示例

$ ./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

内存使用/错误检查

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

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

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

$ valgrind ./bin/letters
==4735== Memcheck, a memory error detector
==4735== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==4735== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==4735== Command: ./bin/letters
==4735==
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
==4735==
==4735== HEAP SUMMARY:
==4735==     in use at exit: 0 bytes in 0 blocks
==4735==   total heap usage: 10 allocs, 10 frees, 216 bytes allocated
==4735==
==4735== All heap blocks were freed -- no leaks are possible
==4735==
==4735== For counts of detected and suppressed errors, rerun with: -v
==4735== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

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

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

【讨论】:

    【解决方案2】:

    改变这个:

    struct letter_t* letters[n_letters[i]];
    

    到这里:

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

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

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

    PS:不要忘记在程序结束时释放内存,按照与动态分配内存时的顺序相反的顺序。

    【讨论】:

      猜你喜欢
      • 2020-01-11
      • 2021-12-15
      • 2019-05-17
      • 1970-01-01
      • 1970-01-01
      • 2017-01-20
      • 1970-01-01
      • 2019-06-15
      • 2021-10-25
      相关资源
      最近更新 更多