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



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

我已经写了一个最少的例子,我认为这将对我想要做的事情有所指导。出于某种原因,"inner"调用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 fromn", letterboxes[i].name, letterboxes[i].n_letters);
for (int j = 0; j < letterboxes[i].n_letters; j++) {
printf("  %sn", 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;不匹配

接下来,在进一步讨论之前,一个 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 */

(注意:要小心。了解您要将字符串文本name[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 fromn", 
letterboxes[i].name, letterboxes[i].n_letters);
for (int j = 0; j < letterboxes[i].n_letters; j++) {
printf("  %s  %dn", 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 fromn", 
letterboxes[i].name, letterboxes[i].n_letters);
for (int j = 0; j < letterboxes[i].n_letters; j++) {
printf("  %s  %dn", 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)

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

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

更改以下内容:

struct letter_t* letters[n_letters[i]];

对此:

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

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

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

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

相关内容

  • 没有找到相关文章

最新更新