views:

1056

answers:

11

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.

What I want to know is 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?

+7  A: 

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.

Floetic
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
@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
+3  A: 

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

lothar
+5  A: 

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
+2  A: 

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
For some odd reason your 2nd link doesn't work: http://doc.cat-v.org/henry_spencer/ten-commandments
Klelky
@Klelky: You were right. Fixed the link.
dirkgently
A: 

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
+10  A: 

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);
 }
Charlie Martin
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 <stdlib.h>.
dirkgently
+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
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
@Charlie: That was in pre-ANSI days. That is the exact reason why void * was introduced.
dirkgently
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
Dammit, *here* it won't interpret HTML entities. "Bottom".
Charlie Martin
A: 

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
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
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
+1  A: 

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
+3  A: 

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
A: 

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
A: 

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