C - 函数中的 malloc() 结构体,在结束程序之前 free()



我正在学习C的基础知识,现在使用malloc()。假设我有一个函数,它要求用户输入,用所述数据填充结构,并将该结构保存在数组中(全部通过引用从主函数传递)。

我通过Valgrind运行了该程序,我得到了"仍然可以访问"的字节(这应该没问题,不被认为是泄漏),但是保存的任何数据我都会丢失块。

程序运行完毕后,我将如何释放内存?此外,我在代码中有一些 (2) 个问题,只是为了澄清一些事情,如果有人能向我解释它们,我将不胜感激。

以下是一些类似于我正在尝试执行的操作的代码:

我声明了以下结构:

struct Person {
char name[MAX_INPUT];
int age;
};

我正在编写一个像这样的函数:

int function2(struct Person *list, int *index) {
struct Person *prsn = malloc(sizeof(struct Person)); 
// !Why do we sometimes cast the malloc or not? 
// I sometimes get errors when I do, sometimes when I don't, 
// while the surrounding code is pretty much the same.
assert(prsn != NULL);
// User input code goes here ... 
// Now to save the Person created
strcpy(prsn->name, nameInput);
prsn->age = ageInput;
list[(*index)++] = *prsn; 
// !Why use the dereferencing *prsn here? 
// why not directly prsn? Or is that saving the memory address and not very useful.
return 0;
}

这是我的主要功能:

int main(int argc, char *argv[]) { 
struct Person personList[MAX_SIZE];
int index;
function2(personList, &index);
// Before closing, I want to free any mallocs I have done here. free()
return 0;
}

瓦尔格林德报告:

LEAK SUMMARY:
==1766==    definitely lost: 44 bytes in 1 blocks
==1766==    indirectly lost: 0 bytes in 0 blocks
==1766==      possibly lost: 0 bytes in 0 blocks
==1766==    still reachable: 10,355 bytes in 34 blocks
==1766==         suppressed: 0 bytes in 0 blocks

提前谢谢你。

编辑:修复了函数2参数,返回和其他东西。我很抱歉,我写得很快是为了说明我关于释放记忆的主要问题。感谢您的更正提示,但真正的代码实际上是正确编译的,并且所有内容都正确。

Edit2:在 main 末尾添加一个简单的循环(例如建议使用 free() 后,我收到以下错误。

==2216== LEAK SUMMARY:
==2216==    definitely lost: 44 bytes in 1 blocks
==2216==    indirectly lost: 0 bytes in 0 blocks
==2216==      possibly lost: 0 bytes in 0 blocks
==2216==    still reachable: 10,355 bytes in 34 blocks
==2216==         suppressed: 0 bytes in 0 blocks
==2216== 
==2216== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
==2216== 
==2216== 1 errors in context 1 of 2:
==2216== Invalid free() / delete / delete[] / realloc()
==2216==    at 0x563A: free (in /usr/local/Cellar/valgrind/3.8.1/lib/valgrind/vgpreload_memcheck-amd64-darwin.so)
==2216==    by 0x10000194E: main (in ./test.out)
==2216==  Address 0x7fff5fbf9dd0 is on thread 1's stack
==2216== 
==2216== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

代码分析

让我们一次剖析一下:

int function2(struct Person *list) {

正如 Iserni 在他的回答中指出的那样,这个定义与你对函数的调用不一致。 我总体上同意他对现有代码的更正(但我建议尽快修改它):

int function2(struct Person *list, int *index)

struct Person *prsn = malloc(sizeof(struct Person)); 
// !Why do we sometimes cast the malloc or not? 
// I sometimes get errors when I do, sometimes when I don't, 
// while the surrounding code is pretty much the same.

这取决于你是用 C 还是C++;在 C++ 中,你必须从malloc()转换返回值(如果你使用malloc();你通常不应该在 C++ 中)。 在 C 语言中,强制转换是可选的。 有一种思想流派认为,省略演员表会揭示插入演员表可以隐藏的错误。 我不赞同;我相信malloc()应该通过<stdlib.h>声明,如果范围内没有声明,编译器应该发出警告,如果范围内有声明,演员表就无法掩盖罪恶。 另一个可能的问题是将指针分配给非指针;这也是编译器应该抱怨的错误。

assert(prsn != NULL);

这通常不被认为是处理内存分配错误的明智长期方法。

// User input code goes here ... 
// Now to save the Person created
strcpy(prsn->name, nameInput);
prsn->age = ageInput;
list[(*index)++] = *prsn; 
// !Why use the dereferencing *prsn here?

因为:

  • list是一个struct Person *.
  • 因此list[i]是一个struct Person(尽管你i拼写为(*index)++)。
  • 因此,您必须为其分配struct Person
  • prsn是一个struct Person *.
  • 因此*prsn也是一个struct Person
  • 因此,作业是"正确的"。
  • 它还会给您泄漏。
  • 您已用*prsn的内容覆盖了list[i]的内容。
  • 您尚未将指针保存到任何位置prsn
  • 因此,当您从函数返回时,您会泄漏内存。

解决这个问题所需的手术是不可忽视的:

int function2(struct Person **item)
...
*item = prsn;

你必须修改电话;在剖析main()时,我会回到这一点.

// why not directly prsn? Or is that saving the memory address and not very useful.
}

您的函数被声明为返回一个int但您没有显示return。 如果不打算返回值,请将函数声明为void,特别是如果您要像main()中的代码那样忽略返回值。

您的最后一条评论主要包含在上面的讨论中;保存内存地址对于阻止泄漏至关重要,因此非常有用。

这是我的主要功能:

int main(int argc, char *argv[]) { 
struct Person personList[MAX_SIZE];
int index;

使用未初始化的变量是个坏消息。 它充其量只是意外归零。 您不能将随机值用作数组索引;显式初始化它。 此外,我们将需要一个指针数组,而不是结构数组:

struct Person *personList[MAX_SIZE];
int index = 0;
...other code...
function2(personList, &index);

使用修改后的功能:

function2(&personList[index++]);

这是可取的;这意味着function2()不需要知道数组;你只需向它传递分配的内存指针的地址。 这减少了main()函数和function2()之间的耦合,这使得代码更加简单。

// Before closing, I want to free any mallocs I have done here. free()

所以你写:

for (int i = 0; i < index; i++)
free(personList[i]);

这将释放分配的所有内存。

return 0;
}

我喜欢在main()末尾看到明确的回报,即使 C99 说这不是 100% 必要的。

确保在启用足够警告的情况下进行编译。 如果您使用的是 GCC,则gcc -Wall应该是您运行时使用的最低编译警告级别(并且这样做时代码中不应有任何警告)。 我带着更严厉的警告跑步:通常gcc -std=c99 -Wall -Wextra -Wmissing-prototypes -Wold-style-definition -Wstrict-prototypes -Wshadow。 您需要包含-O3(或某种程度的优化)才能获得一些警告。 关于原型的东西反映了对我使用的旧代码库的偏执,该代码库仍然有K&R函数定义。


回答评论

当我回顾您的详细信息并尝试时,第一个问题:结构数组和指针数组之间是否存在内存影响?

是的,但这可能不是你想的。 如果使用结构数组,则无需为结构分配内存动态,因为编译器已为您分配了内存。 由于练习的目的是使用指针和malloc(),因此最好使用指针。 在空间方面,指针数组使用的总内存会略多一些(但泄漏的内存会更少)。

我正在尝试更改我的代码以使用指针数组。但是现在使用function2(personList, &index);来呼叫function2给了我以下警告:incompatible pointer types passing 'struct Person *[512]' to parameter of type 'struct Person *'。如果我在我的主要问题中编写额外的代码来详细介绍,可以吗?请注意,我正在尝试尽可能多地引用变量,以免暂时让程序将数据从一个函数复制到另一个函数。

如果您尚未进行所有更改,则编译器是正确的。 使用两个参数的代码在函数之间复制的数据比使用一个参数的代码多。

版本 1

下面的程序使用提议的单参数function2()来减少main()函数和function2()之间的耦合,简化两者。

此代码在 Mac OS X 10.7.5 上的 GCC 4.7.1 下使用命令行编译时没有警告:

gcc -O3 -g -std=c99 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes 
-Wold-style-definition mem.c -o mem

当在valgrind下运行时,它不会产生内存泄漏。

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
enum { MAX_INPUT = 28 };
enum { MAX_SIZE  = 3  };
struct Person
{
char name[MAX_INPUT];
int age;
};
static void function2(struct Person **list)
{
struct Person *prsn = malloc(sizeof(struct Person)); 
assert(prsn != NULL);
char *nameInput = "This is my name";
int ageInput = 29;    // Again!
strcpy(prsn->name, nameInput);
prsn->age = ageInput;
*list = prsn;
}
int main(void)
{
struct Person *personList[MAX_SIZE];
int index = 0;
function2(&personList[index++]);
function2(&personList[index++]);
function2(&personList[index++]);
for (int i = 0; i < index; i++)
free(personList[i]);
return 0;
}

版本 2

这会保留function2()的双参数版本,并让它执行main()应该自行执行的计数。 该程序还可以干净地编译并在valgrind下干净地运行。

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
enum { MAX_INPUT = 28 };
enum { MAX_SIZE  = 3  };
struct Person
{
char name[MAX_INPUT];
int age;
};
static void function2(struct Person **list, int *index)
{
struct Person *prsn = malloc(sizeof(struct Person)); 
assert(prsn != NULL);
char *nameInput = "This is my name";
int ageInput = 29;    // Again!
strcpy(prsn->name, nameInput);
prsn->age = ageInput;
list[(*index)++] = prsn;
}
int main(void)
{
struct Person *personList[MAX_SIZE];
int index = 0;
function2(personList, &index);
function2(personList, &index);
function2(personList, &index);
for (int i = 0; i < index; i++)
free(personList[i]);
return 0;
}

我假设你的function2真的是

int function2(struct Person *list, int *index)

你会把你的错误指针保存在那里

list[(*index)++] = prsn;

然后在main您将释放列表

while(index)
free(list[--index]);

在面向对象的编程语言中,对象数组实际上只是指向对象的指针数组。 因此,如果指针占用 4 个字节,而一个对象占用 5 个字节,则包含 10 个对象的数组实际上长度为 4*10 字节(加上开销)。

MyClass[] my_variable = new MyClass[10]; // this will allocate 10*pointersize bytes, plus overhead, you still need to allocate more space for the objects themselves

在 C 语言中,结构数组是结构数组。 如果一个结构需要 8 个字节,那么一个包含 10 个字节的数组需要 80 个字节。 您无需分配更多空间。 它已经在那里了。

struct data_t my_variable[10]; // this will allocate 10*sizeof(data_t) bytes

所以简单的答案是:

int function2(struct Person *list, int *index) {
char* nameInput;
// User input code goes here ...
// IMPORTANT: the user input code must allocate the space for nameInput
// if it doesn't, then a copy of the buffer must be made
// instead of the direct assignment below
// the memory for this instance of the person was already created when the
// array was created, so just save the values
list[*index].name = nameInput;
list[*index].age = ageInput;
*index += 1; 
return 0;
}

当代码执行此行时:

list[(*index)++] = *prsn; 

从字面上看,它是复制 8 个字节(在 32 位机器上,sizeof(int) + sizeof(char*)),因为我认为您注意到您的通讯"//!为什么在这里使用取消引用的 *prsn?

使用 malloc 分配的内存地址未在该行中复制,但内存的内容被复制。 这就是为什么它是内存泄漏,当函数退出时地址会丢失,因为它从未放入数组中。