我正在用C编写一个shell。虽然我不希望其他人使用它,但我还是想练习编写可维护且组织良好的代码。我在一些函数中注意到了以下模式,所以在它固化之前,我希望它能够经过全面审查。
例如,请考虑以下功能:
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;
}
注意事项:
三个阶段:声明,启动/修改,清理
新分配的结构通常从函数返回,作为其他对象的修改副本
不需要大量对象,因此内存使用不是问题
目前,这三个部分相对不同。这允许我匹配第一个和最后一个部分并确保一切都被考虑在内。现在我想知道一个更好的风格是否可以在不需要时立即解除分配。这样做的动机可能是最小化代码部分有意义的上下文。
您对资源重新分配的方法是什么?给定策略的优点是什么?
清除任何关于功能行为的混淆:
/**
* 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);
答案 0 :(得分:4)
就个人而言,我的偏好是在我使用它们之后直接释放对象,并且只在我需要之前直接分配。这迫使我理解我的程序实际使用的内存。此技术的另一个好处是,如果在方法释放内存后分配额外的内存,它可以减少总内存消耗。
答案 1 :(得分:3)
有两种不同的情况需要考虑:
(1)在本地范围内创建一个对象,在本地范围之外不需要它。
在这种情况下,您可以使用 calloc alloca()或使用 RAII 方法分配存储空间。使用 calloc alloca()有一个很大的好处,你不必关心调用free(),因为当剩下本地范围时,自动释放分配的内存。
(2)在本地范围内创建一个对象,在本地范围之外需要它。 在这种情况下,没有一般性建议。当不再需要该对象时,我会释放内存。
编辑:使用 alloca ()而不是calloc()
答案 2 :(得分:2)
我倾向于在最后组合释放,除非我重用变量并且需要先释放它。这样就可以更清楚地确定需要销毁的内容,如果你考虑早期return
或者函数有点复杂,这会很有用。通常你的函数会有一些不同的控制流,你想确保它们最后都清理完了,这在清理代码结束时更容易看到。
答案 3 :(得分:2)
我通常喜欢最小的范围,因此我尽可能晚地创建对象,并尽可能早地释放(免费)。
我倾向于:
char * foo;
/* some work */
{
foo = create();
/* use foo */
destroy(foo);
}
/* some other work */
{
foo = create();
/* use foo */
destroy(foo);
}
即使我可以重用内存,我更喜欢将它分配两次然后释放两次。大多数情况下,这种技术的性能影响非常小,因为大多数情况下两个对象无论如何都是不同的,如果这是一个问题,我倾向于在开发过程中最近优化它。
现在,如果你有两个具有相同范围的对象(或者你的例子有三个),那就是同样的事情:
{
foo1 = create();
foo2 = create();
foo3 = create();
/* do something */
destroy(foo1);
destroy(foo4);
destroy(foo3);
}
但是这个特定的布局仅在三个对象具有相同范围时才相关。
我倾向于避免这种布局:
{
foo1 = create();
{
foo2 = create();
/* use foo2 */
}
destroy(foo1);
/* use foo2 again */
destroy(foo2);
}
我认为这已经破了。
当然{}仅用于示例,但您也可以在实际代码中使用它们,或者使用vim折叠或任何表示范围的内容。
当我需要更大的范围(例如全局或共享)时,我使用引用计数和保留释放机制(将retain替换为retain并使用release释放),这始终确保了一个简单的内存管理。 / p>
答案 4 :(得分:2)
通常,动态分配的内存具有较长的生命周期(比函数调用更长),因此谈论在函数中解除分配的位置毫无意义。
如果仅在函数范围内需要内存,则根据语言应该在堆栈上静态分配(在函数中声明为局部变量,它将在函数时分配)当函数退出时调用并释放,如另一张海报的示例所示。)
就命名而言,只需要特别命名分配内存并返回它的函数。其他任何事情都不打扰说“modfiier” - 使用该字母空间来描述函数的功能。即默认情况下,假设它没有分配内存,除非特别命名(即createX,allocX等)。
在静态alllocation不合适的语言或情境中(即提供程序中其他地方的代码的一致性),然后通过在函数调用开始时分配并在结束时释放来模仿堆栈分配模式
为清楚起见,如果您的函数只是修改了对象,请根本不使用函数。使用程序。这使得绝对清楚的是没有分配新的内存。换句话说,消除你的指针b和c - 它们是不必要的。他们可以在不返回值的情况下修改指向的内容。
从代码的外观来看,要么释放已经释放的内存,要么bar_modifier具有误导性,因为它不是简单地修改a所指向的内存,而是创建全新的动态分配内存。在这种情况下,它们不应该被命名为bar_modifier而是create_SomethingElse。
答案 5 :(得分:1)
你为什么要解救3次?
如果bar_creator()
是唯一一个动态分配内存的函数,则只需要释放指向该内存区域的一个指针。
答案 6 :(得分:1)
完成后!
不要让便宜的内存价格促进懒惰的编程。
答案 7 :(得分:1)
您需要注意内存分配失败时会发生什么。因为C不支持异常,所以我使用goto
来管理错误时的展开动态状态。这是对原始函数的一种微不足道的操作,展示了这种技术:
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;
}
使用这种技术时,我总是希望在错误标签之前有一个return
语句,以便在视觉上区分正常的返回情况和错误情况。现在,这假设您使用风/展开范例来动态分配内存...您正在做的事情看起来更顺序,所以我可能会更接近以下内容:
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;
答案 8 :(得分:0)
让编译器为你清理堆栈?
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;
}
答案 9 :(得分:0)
对于复杂的代码,我会使用结构图来显示子程序一起工作的方式,然后用于分配/解除分配我尝试使它们在给定对象的图表中大致相同的水平上发生。
在你的情况下,我可能想要定义一个名为bar_destroyer的新函数,在函数foo的末尾调用3次,并在那里执行free()。
答案 10 :(得分:0)
考虑使用不同的模式。如果合理的话,在堆栈上分配变量(使用声明,而不是alloca)。考虑将bar_creator设为bar_initialiser,它采用结构栏*。
然后你可以让你的bar_modifier看起来像
void bar_modifier(const struct bar * source, struct bar *dest);
然后你不必太担心内存分配。
一般来说,让C调用者分配内存而不是被调用者更好 - 因此在我看来strcpy是一个“更好”的函数,而不是strdup。