0

我正在学习 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;
}

Valgrind 报告:

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

先感谢您。

编辑:修复了function2参数,返回和其他东西。我很抱歉,写它是为了说明我关于释放内存的主要问题。感谢您的更正提示,但真正的代码实际上是正确编译的。

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

==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)
4

3 回答 3

7

代码分析

让我们一次剖析一下:

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]是 a struct Person(尽管您拼写i(*index)++)。
  • 因此,您必须为其分配一个struct Person
  • prsn是一个struct Person *
  • 因此*prsn也是一个struct Person
  • 因此,分配是“正确的”。
  • 它也给你泄漏。
  • 您已经用 的内容覆盖了list[i]的内容*prsn
  • 您还没有将指针保存到prsn任何地方。
  • 因此,当您从函数返回时会泄漏内存。

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

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

你必须修改电话;我会在解剖时回到那个main()

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

您的函数被声明为返回 anint但您显示 no 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);to call function2now 给了我以下警告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;
}
于 2012-10-22T00:13:33.970 回答
1

我认为你function2是真的

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

你会在那里保存你的 malloc'ed 指针

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

然后main你会释放列表

while(index)
    free(list[--index]);
于 2012-10-21T23:36:49.170 回答
0

在面向对象的编程语言中,对象数组实际上只是指向对象的指针数组。因此,如果一个指针占用 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 分配的内存地址不会在该行中复制,但内存的内容是。这就是为什么它是内存泄漏,当函数退出时地址会丢失,因为它从未被放入数组中。

于 2013-09-15T08:15:10.320 回答