15

What's the best-practice in handling memory in C?

There are no classes with constructors/destructors to handle this for me.

  • Is it good to allocate memory in the beginning of a function or use a function that creates it for me? And how should I free them again?
  • These are broad questions and they differ from situation to situation but how do you prefer to handle it?
  • What tips and lessons can you give?

I think I've got a good grasp on how to handle memory in C++ but doing it in C is different I'm a bit off.

In C++ I've got constructors and destructors, I've got the pretty straightforward new and delete and I know how to encapsulate it using RAII, using with smart pointers and within classes.

However in C I can't handle malloc and free the same way. I don't know how to hide them and how to automate things. All I can figure is using functions for initiating and destroying my pointers. But how should I structure my memory handling?

While writing this I've realized this is more a question about me understanding the flow of C than anything else, but one question at a time.

Edit: Thanks for the answers but I need to rephrase myself.

When I say that I use RAII and smart pointers for C++ I don't want the same for C, I know it's not the same. But how I handle memory allocation in C++ is connected to these techniques.

For example in my classes I dynamically add and destroy the memory my class uses. This way I can achieve a sort of encapsulation, I don't need to know when/how/why the class handles it's memory, it just does. This means I can "hide" the lower memory handling and just focus on a few "bigger" classes.

Federico Baù
  • 6,013
  • 5
  • 30
  • 38
Jonas
  • 1,532
  • 3
  • 16
  • 20

12 Answers12

23

Part of the confusion is that it is inherently more difficult in C. malloc and free are similar to new and delete: malloc allocates new memory, and returns a pointer to that memory. free makes that memory available again, so long as it's memory that was allocated using malloc. Otherwise, it just makes hash of some chunk of memory. It doesn't care.

The important thing with malloc/free is to decide on and consistently maintain a disciplined use. Here are some hints:

ALWAYS check the returned pointer from malloc for NULL

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

For belt and suspenders safety, set a pointer to NULL after freeing it.

free(p);
p = NULL ;

try to malloc and free a chunk of memory within the same scope if possible:

 {  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 */
 }
   

When you can't, make it clear that what you're returning is malloc'ed memory, so the caller can free it.

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


 
Federico Baù
  • 6,013
  • 5
  • 30
  • 38
Charlie Martin
  • 110,348
  • 25
  • 193
  • 263
  • 1
    You don't need the cast(except when passing directly a malloc() call as a varargs function argument or using pre-ANSI compilers). That's actually inducing bad habbit because it hides a missing #include . – dirkgently Apr 06 '09 at 20:25
  • +1 for malloc/free in same scope and for p=NULL after free(). malloc/free in same scope is good localization, and note that "do your work" may include function calls that take pointers. Best answer by far at this time. – dwc Apr 06 '09 at 20:27
  • that's another stylistic thing, Dirk. I prefer to have the explicit cast, because that's part of my own type-reasoning that I go through while writing C. I see "(char *) malloc" as an abstract operating that returns a malloc'ed char chunk of memory. – Charlie Martin Apr 06 '09 at 21:51
  • @Charlie: That was in pre-ANSI days. That is the exact reason why void * was introduced. – dirkgently Apr 06 '09 at 22:24
  • No, that's just my point. void * is ⊥ in the type lattice; you can assign it to *any* pointer. By making the cast explicit, I'm *telling* what type I think it is. – Charlie Martin Apr 07 '09 at 04:55
  • Dammit, *here* it won't interpret HTML entities. "Bottom". – Charlie Martin Apr 07 '09 at 04:55
  • 1
    I tend to define a macro FREE that frees something and sets it to NULL. Makes things a little easier. – TLW Sep 16 '17 at 20:20
  • Yeah, there were a lot of those tricks around, ditto MALLOC macros that signaled or something if malloc returned null. Back in the old days, C++ around cfront 1.2 make life easier. That was before Bjarne read Hoare's argument that Ada was too complicated, and said "hold my beer". – Charlie Martin Sep 17 '17 at 01:43
12

While writing this I've realized this is more a question about me understanding the flow of C than anything else, but one question at a time.

I honestly think you should read up on K&R if you haven't.

Brian Liang
  • 7,734
  • 10
  • 57
  • 72
  • 2
    Why people still suggest that ancient book? Sure it is remarkable in C history, written by its authors, but its last ed is 20 years old, doesn't cover ISO C99 and even less current practices, like OO and smart memory mgmt provided by libraries like GLib (from GNOME). – Juliano Apr 06 '09 at 20:52
  • 6
    @Juliano: I don't see a problem with recommending an old book for learning an old language. _The C Programming Language_ is remarkably well written, and people keep recommending it because it does a good job of teaching exactly what it is intended to. Supposedly popular practices and specific 3rd party libraries are outside the scope of a book about the language itself. C99 doesn't break anything significant, and its new features aren't well supported by plenty of compilers anyway. If you are trying to learn C (and not GLib or OO practices or ...) there really is no better textbook. – Steve S Aug 05 '10 at 21:55
10

Unfortunately there are limited strategies for automating memory allocation and deallocation in C. The C++ compiler generates a lot of code behind the scenes for you -- it keeps track of each variable on the stack and makes sure that the appropriate destructor is called when the stack is cleaned up. This is actually a fairly sophisticated type of code generation, especially when you throw exceptions into the mix.

C on the other hand is much simpler, which is why it's sometimes called "high level assembly language". C doesn't have any mechanism to guarantee that a particular bit of code is called when a function exits or a variable is popped off the stack, so it's up to you to keep track of each bit of memory you allocate and every file or network socket you open and clean them up at the appropriate point. There's no practical way to build an automatic smart pointer in C.

One concept you should look at is "memory pools". Basically, rather that try to keep track of every individual block of memory you allocate, you create a pool, do some chunk of work, placing every memory block you allocate into the pool, then free the whole pool when you're done. You trade off a little bit of performance and control here in order to ease the cognitive load on the programmer, but most of the time it's well worth it.

You should take a peek at the Apache Portable Runtime project. They have a memory pool library (docs at http://apr.apache.org/docs/apr/1.3/group__apr__pools.html ). If APR is too much for you to dive into, you can implement a very simple memory pool using three functions and a linked list data structure. Pseudocode would be something like:

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 */
}

Using the pool is something like this:

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;
}
Don McCaughey
  • 9,532
  • 3
  • 30
  • 36
8

The sad truth is that C isn't really designed to encapsulate all those memory management issues.

If you look at fairly high quality APIs like POSIX, you'll see that the common pattern is that you pass a pointer to a pointer to a function, which then allocates the memory, and that you later pass it again to a function that destroys it.

It's not necessarily elegant, but I don't think there are many ways to make it really elegant without simulating OOP in C.

Uri
  • 88,451
  • 51
  • 221
  • 321
3

I know this is an old post but there hasn't really been much of a comprehensive answer to best practice in terms of style, which I think is what the op really wanted, so here is my take on memory allocation in C. Note I am more of a C++ person so much of my thoughts come from that attitude.

It is often handy to know whether your pointer is allocated, so always assign NULL to a pointer when you declare it. You can also create yourself a safe free function which frees the memory and then assigns NULL to it so you don't have to worry.

If you allocate memory in one C file then you should free it in the same file. This is perhaps more restrictive than needed, however if you are writing a library then you should definitely free any memory within your library that is malloc'd in your library. This is because on Windows dlls have a different heap to the exe, so mallocing memory in a dll and freeing it in the exe corrupts your heap.

By extension and for the sake of symmetry this means if you have a function which returns a pointer to allocated memory then you should have a function which frees that memory. This is why many librarys have an initialisation function which returns a pointer to some data (generally cast as a void *) then a cleanup function which will free the library's resources. If you can malloc and free within the same function then that is good as it makes it easy for you to keep track of things.

Do not try to allocate all your memory at the beginning of a function and then free it at the end. This just means that if you want to return part way through the function you have to free all the memory, whereas if you malloc and free memory as you go you will have fewer pointers to free.

If you often have functions which allocate many pointers, then consider creating and array which holds pointers to all your pointers at the beginning of the function, then have a function that frees them all. This will save you the inevitable "I'll come back and sort my memory leaks later" syndrome if you want to return mid function.

The concept of factories is useful. A factory would be a function which mallocs the memory for a struct, assigns function pointer to the struct, initialises its variables and then returns the pointer to it. If the first of those was a destructor or an array of specific functions then you can have a generic destroy function that can call any struct's destructor, then free the struct's memory. You can also hide some of the internal details of the class by having different inward and outward facing definition of the struct. COM is built upon these principles.

So these are just the ways I look at memory in C. It isn't as elegant as in C++, but as you are relying on humans to deal with it, there are strategies like those above that can make things as simple as possible for them.

Note also that there are always exceptions to every rule - these are just things that I think about when I use C. I'm sure other people have other ideas.

Phil

Phil Rosenberg
  • 1,597
  • 1
  • 14
  • 22
3

Well in C you have to do all you memory management manually as you already have discovered. That should be no surprise.

lothar
  • 19,853
  • 5
  • 45
  • 59
2

There's a lot you can do to make your life easier. You already seems to have hit on the idea of creating factories/constructors for your C-objects. That's a good start follow up on it.

Some other ideas to consider.

  1. don't settle for the standard malloc/free. go looking for a better one that's been opensourced or write one that suites the memory use of the objects that you are creating. also, we're talking C here, you're going to overwrite your objects free more and once and forget to free some, so build some debugging support into your malloc. writing your own is not hard if you cannot find one that meets your needs.

  2. use more than one heap. use one per class of object you create, use temporary heaps if you know you you are going to have a large number of transient objects that are related, this keeps memory fragmentation down and allows you to manage memory according to use.

  3. look at strategies like Objective-C's pools

  4. if you think you understand how C++ works, then adding constructor behavior to memory allocation in an object factory is not so hard to do and using a custom built free can then provide you the capability to call a destructor on the object being free'd giving you back some of the C++ behavior you liked

jottos
  • 20,098
  • 9
  • 30
  • 27
1

I don't know how to hide them and how to automate things.

C and C++ are different languages. Now, say that a hundred times to yourself. Be loud.

What do you mean by hiding? What do you mean by automate? Can you post some examples? Why do you need to hide and/or automate.

Good places online to start off with C memory allocation are:

dirkgently
  • 108,024
  • 16
  • 131
  • 187
  • For some odd reason your 2nd link doesn't work: http://doc.cat-v.org/henry_spencer/ten-commandments – Klelky Apr 07 '09 at 15:17
1

One way I "hide" the memory allocation and de-allocation is to pass it off to custom containers. Pass the container an un-malloced object. Let it worry about malloc and when I remove the object let it worry about the free. Of course this only works if you are only storing an object in one container. If I have object references all over the place I will create the equivalent of constructor and destructor methods with c syntax:

 glob* newGlob(); 
 void freeGlob(glob* g);

(by object I mean anything you would point to - not c++ objects).

Nick Van Brunt
  • 15,244
  • 11
  • 66
  • 92
0

The usual way is

MyType *ptr = malloc(array_size * sizeof *ptr);

But if you want to be compatible with c++, do

MyType *ptr = (MyType*) malloc(array_size * sizeof *ptr);

You can also make a macro

#define MALLOC( NUMBER, TYPE ) ( TYPE * ) malloc( NUMBER * sizeof( TYPE ) )
MyType *ptr = MALLOC(10, MyType);

Of course, with no RAII, make sure sometime later you have

free(ptr);
rlbond
  • 65,341
  • 56
  • 178
  • 228
0

I'm not quite sure what you're asking, but C is pretty straightforward:

struct Foo *f0 = malloc(sizeof(*f));   // alloc uninitialized Foo struct
struct Foo *f1 = calloc(1,sizeof(*f)); // alloc Foo struct cleared to all zeroes

//You usually either want to clear your structs using calloc on allocation, or memset. If 
// you need a constructor, just write a function:
Foo *Foo_Create(int a, char *b)
{
   Foo *r = calloc(1,sizeof(*r));
   r->a = a;
   r->b = strdup(b);
   return r;
}

Here is a simple C workflow with arrays:
struct Foo **foos = NULL;
int n_foos = 0;
...
for(i = 0; i < n_foos; ++i)
{
   struct Foo *f = calloc(1,sizeof(*f));
   foos = realloc(foos,sizeof(*foos)*++n_foos); // foos before and after may be different
   foos[n_foos-1] = f;
}

If you get fancy, you can write macros to help:

#define MALLOCP(P) calloc(1,sizeof(*P)) // calloc inits alloc'd mem to zero

A couple of points:

  • malloc, calloc, realloc, etc. all use free(), so managing these things is easy. Just be consistant.
  • performance for mallocs can be slow. Someone posted a link on this above. These days fast multi-threaded allocations is key, see tcmalloc et al. You probably don't have to worry about this.
  • on a modern virtual memory architecture, malloc will almost never fail unless you are out of virtual address space. If this happens, switch to 64bit ;)
  • Make sure you're using a system that has bounds checking, wiped free values, leak tracking and all that good stuff (see valgrind, win32 debug heap, etc.)
aaron
  • 1,746
  • 1
  • 13
  • 24
-2

You may think that what I say is strange, but there is no big difference between C++ and C. C has RAII as well. RAII does not belong to C++ only.

Only thing you should have enough discipline to do management.

C++ class:

class foo {
public:
   ofstream ff;
   int x,y;
   foo(int _x) : x(_x),y(_x){ ff.open("log.txt"); }
   void bar() { ff<<x+y<<endl; }
};

int main()
{
   auto_ptr<foo> f(new foo);
   f->bar();
}

C object

typedef struct FOO {
    FILE *ff;
    int x,y;
} *foo_t;

foo_t foo_init(int x)
{
   foo_t p=NULL;
   p=malloc(sizeof(struct FOO)); // RAII
   if(!p) goto error_exit;
   p->x=x; p->y=x;
   p->ff=fopen("log.txt","w");   // RAII
   if(!p->ff) goto error_exit;
   return p;
error_exit:   // ON THROW
   if(p) free(p);
   return NULL;
}

void foo_close(foo_t p)
{
   if(p) fclose(p->ff);
   free(p);
}

void foo_bar(foo_t p)
{
   fprintf(p->ff,"%d\n",p->x+p->y);
}

int main()
{
  foo_t f=foo_init(1);
  if(!f) return 1;
  foo_bar(f);
  foo_close(f);
  return 0;
}
Artyom
  • 31,019
  • 21
  • 127
  • 215
  • This is not RAII. RAII is binding resources to objects on the stack, utilizing the destructor to handle release of the resource. C does not have constructor/destructor semantics. Moreover, this code is more inefficient than C++ because it tests for errors, while C++ treats them as exceptional. – rlbond Apr 06 '09 at 21:30
  • There are two points: a) C does not have exceptions they are "if" conditions. b) RAII is not about desructors but about properly initialization of resources and their release. It can be easily done in C over stack when you divide the code in 3 parts: initialization, work and finalization. – Artyom Apr 07 '09 at 04:23