简体   繁体   English

我应该在哪里释放函数内存?

[英]Where should I deallocate memory within functions?

I'm writing a shell in C. While I don't expect many other people to use it, I'd like to practice writing maintainable and well-organized code. 我正在用C语言编写一个shell。虽然我不希望其他人使用它,但我还是想练习编写可维护且组织良好的代码。 I've noticed the following pattern in a number of my functions, so before it solidifies, I'd like it to be fully vetted. 我在一些函数中注意到了以下模式,所以在它固化之前,我希望它能够经过全面审查。

As an example, consider the following function: 例如,请考虑以下功能:

int foo(int param...) {
  // declare variables
  struct bar *a, *b, *c;

  // do some work
  a = bar_creator();
  b = bar_modifier(a);
  c = bar_modifier(b);

  // cleanup
  free(a);
  free(b);
  free(c);

  return 1;
}

Things to note: 注意事项:

  • three phases: declaration, initiation/modification, cleanup 三个阶段:声明,启动/修改,清理

  • newly allocated structures are often returned from functions as modified copies of other objects 新分配的结构通常从函数返回,作为其他对象的修改副本

  • a huge number of objects are not needed, so memory usage is not an issue 不需要大量的对象,因此内存使用不是问题

As it stands, the three sections have been relatively distinct. 目前,这三个部分相对不同。 This allows me to match up the first and last sections and ensure everything is accounted for. 这允许我匹配第一个和最后一个部分并确保一切都被考虑在内。 Now I wonder if a better style might be to deallocate something as soon as it is not needed. 现在我想知道一个更好的风格是否可以在不需要时立即解除分配。 A motivation for this might be to minimize the context within which a code section makes sense. 这样做的动机可能是最小化代码部分有意义的上下文。

What is your approach to deallocation of resources? 您对资源重新分配的方法是什么? What are the advantages of the given strategy? 给定策略的优点是什么?

edit 编辑

To clear up any confusion as to the behavior of functions: 澄清关于函数行为的任何混淆:

/**
* returns a newly created bar
*/
struct bar *bar_creator();

/**
* takes a bar, and returns a _new_ copy of it that may have been modified.
* the original is not modified.
*/
struct bar *bar_modifier(struct bar *param);

Personally, my preference is to free objects directly after I'm done using them, and only allocate directly before I need them. 就个人而言,我的偏好是在我使用它们之后直接释放对象,并且只在我需要之前直接分配。 This forces me to understand what memory my program is actually using. 这迫使我理解我的程序实际使用的内存。 Another benefit of this technique is that it reduces total memory consumption if you allocate additional memory after you free memory in the method. 此技术的另一个好处是,如果在释放方法中的内存后分配额外内存,则可以减少总内存消耗。

there are two different situations to consider: 有两种不同的情况需要考虑:

(1) an object is created in the local scope and it is not needed outside this local scope. (1)在本地范围内创建一个对象,在本地范围之外不需要它。 in this case you could allocate storage with calloc alloca() or with a RAII approach. 在这种情况下,您可以使用calloc alloca()RAII方法分配存储。 Using calloc alloca() has the big advantage that you don't have to care about calling free() because the allocated memory is automatically freed when the local scope is left. 使用calloc alloca()有一个很大的好处,你不必关心调用free(),因为当剩下本地范围时,自动释放分配的内存。

(2) an object is created in the local scope and it is needed outside this local scope. (2)在本地范围内创建一个对象,在本地范围之外需要它。 In this case there is no general advice. 在这种情况下,没有一般性建议。 I would free the memory when the object is no longer needed. 当不再需要该对象时,我会释放内存。

EDITED: use alloca () instead of calloc() 编辑:使用alloca ()而不是calloc()

I tend to group frees at the end unless I am reusing a variable and need to free it first. 我倾向于在最后组合释放,除非我重用变量并且需要先释放它。 This way it's clearer what exactly needs to be destroyed, which is helpful if you are considering an early return or if the function is a bit more complex. 通过这种方式可以更清楚地确定需要销毁的内容,如果您考虑提前return或者功能有点复杂,这将非常有用。 Often your function will have a few different control flows and you want to be sure they all hit the clean up at the end, which is easier to see when the cleanup code is at the end. 通常,您的函数将具有一些不同的控制流,并且您希望确保它们都在最后清理,这在清理代码结束时更容易看到。

I usually favor the smallest scope as possible, thus I create object as late as possible, and I release (free) them as early as possible. 我通常喜欢尽可能小的范围,因此我尽可能晚地创建对象,并尽可能早地释放(免费)它们。

I will tend to have: 我会倾向于:

char * foo;
/* some work */
{
foo = create();
/* use foo */
destroy(foo);
}
/* some other work */
{
foo = create();
/* use foo */
destroy(foo);
}

Even if I could have reused the memory, I prefer to alloc it twice and release it twice. 即使我可以重用内存,我更喜欢将其分配两次并释放两次。 Most of the time the performance hit of this technique is very little, as most of the time the two objects are different anyway, and if it's a problem, I tend to optimize this very lately in the dev process. 大多数情况下,这种技术的性能影响非常小,因为大多数时候两个对象都是不同的,如果这是一个问题,我倾向于在开发过程中最近优化它。

Now if you have 2 objects with the same scope (or three as your example), it's the same thing: 现在,如果你有两个具有相同范围的对象(或者你的例子有三个),那就是同样的事情:

{
foo1 = create();
foo2 = create();
foo3 = create();
/* do something */
destroy(foo1);
destroy(foo4);
destroy(foo3);
}

But this particular layout is only relevant when the three objects have the same scope. 但是这种特殊布局仅在三个对象具有相同范围时才相关。

I tend to avoid this kind of layout: 我倾向于避免这种布局:

{
foo1 = create();
{
    foo2 = create();
    /* use foo2 */
}
destroy(foo1);
/* use foo2 again */
destroy(foo2);
}

As I consider this broken. 我认为这打破了。

Of course the {} are only here for the example, but you can also use them in the actual code, or vim folds or anything that denote scope. 当然{}仅用于示例,但您也可以在实际代码中使用它们,或者使用vim折叠或任何表示范围的内容。

When I need a larger scope (eg global or shared), I use reference count and a retain release mechanism (replace create with retain and destroy with release), and this has always ensured me a nice and simple memory management. 当我需要更大的范围(例如全局或共享)时,我使用引用计数和保留释放机制(使用retain替换create和destroy以及释放),这总是确保了我一个简单的内存管理。

  1. Usually dynamically allocated memory has a long lifetime (longer than a function call) so it is meaningless to talk about where within a function it is deallocated. 通常,动态分配的内存具有较长的生命周期(比函数调用更长),因此谈论在函数中解除分配的位置是没有意义的。

  2. If memory is only needed for within the scope of a function, depending on the language it should be statically allocated if appropriate on the stack (declared as a local variable in the function, it will be allocated when the function is called and freed when the function exits, as shown in an example by another poster). 如果只在函数范围内需要内存,则根据语言,如果适当的话,应该在堆栈上静态分配(在函数中声明为局部变量,它将在调用函数时分配,并在函数时释放)函数退出,如另一张海报的例子所示)。

  3. As far as naming is concerned, only functions that allocate memory and return it need to be specially named. 就命名而言,只需要特别命名分配内存并返回它的函数。 Anything else don't bother saying "modfiier" - use that letterspace for describing what the function does. 其他任何事情都不打扰说“modfiier” - 使用该字母空间来描述函数的功能。 Ie by default, assume that it is not allocating memory unless specifically named so (ie createX, allocX, etc.). 即默认情况下,假设它没有分配内存,除非特别命名(即createX,allocX等)。

  4. In languages or situations (ie to provide consistency with code elsewhere in the program) where static alllocation is not appropriate, then mimic the stack allocation pattern by allocating at the beginning of the function call, and freeing at the end. 在静态alllocation不合适的语言或情境中(即提供程序中其他地方的代码的一致性),然后通过在函数调用开始时分配并在结束时释放来模仿堆栈分配模式。

  5. For clarity, if your function simply modifies the object, don't use a function at all. 为清楚起见,如果您的函数只是修改了对象,则根本不要使用函数。 Use a procedure. 使用程序。 This makes it absolutely clear that no new memory is being allocated. 这使得绝对清楚的是没有分配新的内存。 In other words, eliminate your pointers b and c - they are unnecessary. 换句话说,消除你的指针b和c - 它们是不必要的。 They can modify what a is pointing to without returning a value. 他们可以在不返回值的情况下修改指向的内容。

  6. From the looks of your code, either you are freeing already freed memory, or bar_modifier is misleading named in that it is not simply modifying the memory pointed to by a, but creating brand new dynamically allocated memory. 从代码的外观来看,要么释放已经释放的内存,要么bar_modifier具有误导性,因为它不是简单地修改a所指向的内存,而是创建全新的动态分配内存。 In this case, they shouldn't be named bar_modifier but create_SomethingElse. 在这种情况下,它们不应该命名为bar_modifier而是create_SomethingElse。

Why are you freeing it 3 times? 你为什么要解救3次?

If bar_creator() is the only function that allocates memory dynamically you only need to free one of the pointers that point to that area of memory. 如果bar_creator()是唯一一个动态分配内存的函数,则只需要释放一个指向该内存区域的指针。

When you have finished with it! 当你完成它!

Do not let cheap memory prices promote lazy programming. 不要让便宜的内存价格促进懒惰的编程。

You need to be careful about what happens when the memory allocation fails. 您需要注意内存分配失败时会发生什么。 Because C doesn't have support for exceptions, I use goto to manage unwinding dynamic state on error. 因为C不支持异常,所以我使用goto来管理错误时的展开动态状态。 Here's a trivial manipulation of your original function, demonstrating the technique: 这是对原始函数的一个简单操作,演示了这种技术:

int foo(int param...) {
  // declare variables
  struct bar *a, *b, *c;

  // do some work
  a = bar_creator();
  if(a == (struct bar *) 0)
    goto err0;
  b = bar_modifier(a);
  if(b == (struct bar *) 0)
    goto err1;
  c = bar_modifier(b);
  if(c == (struct bar *) 0)
    goto err2;

  // cleanup
  free(a);
  free(b);
  free(c);

  return 1;

err2:
  free(b);
err1:
  free(a);
err0:
  return -1;
}

When using this technique, I always want to have a return statement preceding the error labels, to visually distinguish the normal return case from the error case. 使用这种技术时,我总是希望在错误标签之前有一个return语句,以便在视觉上区分正常的返回情况和错误情况。 Now, this presumes that you use a wind / unwind paradigm for your dynamically allocated memory... What you're doing looks more sequential, so I'd probably have something closer to the following: 现在,这假设您使用风/展开范例来动态分配内存...您正在做的事情看起来更顺序,所以我可能会有更接近以下内容:

  a = bar_creator();
  if(a == (struct bar *) 0)
    goto err0;
  /* work with a */
  b = bar_modifier(a);
  free(a);
  if(b == (struct bar *) 0)
    goto err0;
  /* work with b */
  c = bar_modifier(b);
  free(b);
  if(c == (struct bar *) 0)
    goto err0;
  /* work with c */
  free(c);

  return 1;
err0:
  return -1;

Let the compiler clean the stack for you? 让编译器为你清理堆栈?

int foo(int param...) {
  // declare variables
  struct bar a, b, c;

  // do some work
  bar_creator(/*retvalue*/&a);
  bar_modifier(a,/*retvalue*/&b);
  bar_modifier(b,/*retvalue*/&c);

  return 1;
}

For complicated code I would use structure charts to show the way the subroutines work together and then for allocation/deallocation I try to make these occur at roughly the same level in the charts for a given object. 对于复杂的代码,我会使用结构图来显示子程序一起工作的方式然后用于分配/解除分配我尝试使它们在给定对象的图表中大致相同的级别上发生。

In your case, I might be tempted to define a new function called bar_destroyer, call this 3 times at the end of function foo, and do the free() in there. 在你的情况下,我可能想要定义一个名为bar_destroyer的新函数,在函数foo结束时调用它3次,并在那里执行free()。

Consider using a different pattern. 考虑使用不同的模式。 Allocate variables on the stack if it is reasonable to do so (using declarations, not alloca). 如果合理的话,在堆栈上分配变量(使用声明,而不是alloca)。 Consider making your bar_creator a bar_initialiser which takes a struct bar *. 考虑让你的bar_creator成为一个带有结构栏*的bar_initialiser。

Then you can make your bar_modifier look like 然后你可以使你的bar_modifier看起来像

void bar_modifier(const struct bar * source, struct bar *dest);

Then you don't need to worry so much about memory allocation. 那你就不用太担心内存分配了。

In general it is nicer in C to have the caller allocate memory, not the callee - hence why strcpy is a "nicer" function, in my opinion, than strdup. 一般来说,让C调用者分配内存而不是被调用者更好 - 因此在我看来strcpy是一个“更好”的函数,而不是strdup。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM