在C中处理内存分配的最佳方法是什么?

Jon*_*nas 13 c memory-management

我想我已经很好地掌握了如何在C++中处理内存,但是在C中做它是不同的我有点不对劲.

在C++中,我有构造函数和析构函数,我有非常简单的new和delete,我知道如何使用RAII封装它,使用智能指针和类.

但是在CI中无法以同样的方式处理malloc和free.我不知道如何隐藏它们以及如何自动化.我所能想到的只是使用函数来启动和销毁我的指针.但是我应该如何构建我的内存处理?

写这篇文章的时候,我意识到这更像是一个关于我理解C流程的问题,而不是其他任何问题,但一次只能提出一个问题.

编辑:谢谢你的答案,但我需要改写自己.

当我说我使用RAII和C++的智能指针时,我不希望C相同,我知道它不一样.但是我如何处理C++中的内存分配与这些技术有关.

例如,在我的类中,我动态地添加和销毁我的类使用的内存.这样我就可以实现一种封装,我不需要知道什么时候/如何/为什么类处理它的内存,它就是这样.这意味着我可以"隐藏"较低的内存处理,只关注一些"​​更大"的类.

我想知道的是在C中处理内存的最佳做法是什么?没有带有构造函数/析构函数的类来为我处理这个问题.在函数的开头分配内存或使用为我创建它的函数是否合适?我应该如何再次释放他们?

这些是广泛的问题,它们因情况而异,但您更喜欢处理它?您可以提供哪些提示和课程?

Cha*_*tin 19

令人困惑的部分原因是它本身在C中更加困难,malloc并且free类似于newdelete:malloc分配新内存,并返回指向该内存的指针. free使内存再次可用,只要它是使用malloc分配的内存.否则,它只会产生一些内存的哈希值.它并不关心.

malloc/free的重要一点是决定并始终如一地保持纪律用途.以下是一些提示:

始终检查malloc返回的指针是否为NULL

if((p = (char *) malloc(BUFSIZ)) == NULL {
   /* then malloc failed do some error processing. */
}
Run Code Online (Sandbox Code Playgroud)

对于安全带和吊带安全,请在释放后将指针设置为NULL.

free(p);
p = NULL ;
Run Code Online (Sandbox Code Playgroud)

尝试malloc并尽可能在同一范围内释放一块内存:

 {  char * p ;
   if((p = malloc(BUFSIZ)) == NULL {
       /* then malloc failed do some error processing. */
   }

 /* do your work. */

   /* now you're done, free the memory */

   free(p);
   p = NULL ;  /* belt-and suspenders */
 }
Run Code Online (Sandbox Code Playgroud)

当你不能的时候,要明确你所返回的内容是malloc"内存",这样调用者就可以释放它.

 /* foo: do something good, returning ptr to malloc memory */
 char * foo(int bar) {
     return (char *) malloc(bar);
 }
Run Code Online (Sandbox Code Playgroud)


Bri*_*ang 10

写这篇文章的时候,我意识到这更像是一个关于我理解C流程的问题,而不是其他任何问题,但一次只能提出一个问题.

老实说,如果你没有,我应该读一读K&R.

  • @Juliano:我没有看到推荐一本旧书来学习一门旧语言的问题._C编程语言_写得非常好,人们不断推荐它,因为它能很好地教授它的目的.据推测,流行的做法和特定的第三方库超出了本书关于语言本身的范围.C99并没有破坏任何重要的东西,并且它的新功能无论如何都没有得到很多编译器的良好支持.如果您正在尝试学习C(而不是GLib或OO实践或......),那么真的没有更好的教科书. (4认同)
  • 为什么人们还推荐那本古书?当然,它在 C 历史上是非凡的,由其作者编写,但它的最后一个版本已经有 20 年了,没有涵盖 ISO C99 甚至更少的当前实践,例如 GLib(来自 GNOME)等库提供的 OO 和智能内存管理。 (2认同)

Don*_*hey 7

遗憾的是,在C中自动执行内存分配和释放的策略有限.C++编译器为您生成了大量代码 - 它跟踪堆栈中的每个变量并确保在堆栈时调用相应的析构函数被清理干净了.这实际上是一种相当复杂的代码生成类型,特别是当您将异常抛入混合时.

另一方面,C更简单,这就是为什么它有时被称为"高级汇编语言".C没有任何机制来保证在函数退出或从堆栈弹出变量时调用特定的代码位,因此您可以跟踪分配的每个内存位以及每个文件或网络打开插座并在适当的位置清理它们.在C中构建自动智能指针没有实用的方法.

您应该关注的一个概念是"内存池".基本上,尝试跟踪您分配的每个单独的内存块,您可以创建一个池,执行一些工作,将您分配的每个内存块放入池中,然后在完成后释放整个池.你可以在这里进行一些性能和控制,以减轻程序员的认知负担,但大多数时候它是值得的.

您应该看看Apache Portable Runtime项目.他们有一个内存池库(http://apr.apache.org/docs/apr/1.3/group__apr__pools.html上的文档).如果APR对于您来说太过分了,您可以使用三个函数和一个链表数据结构来实现一个非常简单的内存池.伪代码类似于:

struct Pool {
  void* memoryBlock;
  struct Pool *next;
}

struct Pool *createPool(void) {
  /* allocate a Pool and return it */
}

void addToPool(struct Pool *pool, void *memoryBlock) {
  /* create a new Pool node and push it onto the list */
}

void destroyPool(struct Pool *pool) {
  /* walk the list, free each memory block then free its node */
}
Run Code Online (Sandbox Code Playgroud)

使用池是这样的:

int main(void) {
  struct Pool *pool = createPool();
  /* pool is empty */

  doSomething(pool);

  /* pool full of crap, clean it up and make a new one */
  destroyPool(pool);
  pool = createPool();
  /* new pool is empty */

  doMoreStuff(pool);
  destroyPool(pool);

  return 0;
}
Run Code Online (Sandbox Code Playgroud)


Uri*_*Uri 6

可悲的事实是,C并非真正设计用于封装所有这些内存管理问题.

如果你看一下相当高质量的API,比如POSIX,你会看到常见的模式是你将一个指针传递给一个函数的指针,然后该函数会分配内存,然后再将它传递给一个破坏的函数.它.

它不一定优雅,但我不认为有很多方法可以让它真正优雅而不用C模拟OOP.