views:

155

answers:

5

I am fairly comfortable coding in languages like Java and C#, but I need to use C for a project (because of low level OS API calls) and I am having some difficulty dealing with pointers and memory management (as seen here)

Right now I am basically typing up code and feeding it to the compiler to see if it works. That just doesn't feel right for me. Can anyone point me to good resources for me to understand pointers and memory management, coming from managed languages?

+5  A: 

k&r - http://en.wikipedia.org/wiki/The_C_Programming_Language_(book)

nuff said

gmarcotte
+3  A: 

One of the good resources you found already, SO.

Of course you are compiling with all warnings on, don't you?

Learning by doing largely depends on the quality of your compiler and the warnings / errors he feeds you. The best in that respect that I found in the linux / POSIX world is clang. Nicely traces the origin of errors and tells you about missing header files quite well.

Jens Gustedt
++ For "compile with all warnings on".
Jon Rodriguez
+1  A: 

Some tips:

  • By default varibles are stored in the stack.
  • Varibles are passed into functions by Value
  • Stick to the same process for allocating and freeing memory. eg allocate and free in the same the function
  • C's equivalent of

    Integer i = new Integer();
    
    
    i=5;
    

is

int *p;

p=malloc(sizeof(int));

*p=5;
  • Memory Allocation(malloc) can fail, so check the pointer for null before you use it.
  • OS functions can fail and this can be detected by the return values.
mikek3332002
A: 

Learn to use gdb to step through your code and print variable values (compile with -g to enable debugging symbols).

Use valgrind to check for memory leaks and other related problems (like heap corruption).

Burton Samograd
A: 

The C language doesn't do anything you don't explicitly tell it to do.

There are no destructors automatically called for you, which is both good and bad (since bugs in destructors can be a pain).

A simple way to get somewhat automatic destructor behavior is to use scoping to construct and destruct things. This can get ugly since nested scopes move things further and further to the right.

if (var = malloc(SIZE)) {  // try to keep this line
     use_var(var);
     free(var);  // and this line close and with easy to comprehend code between them
} else {
     error_action();
}
return; // try to limit the number of return statements so that you can ensure resources
       // are freed for all code paths

Trying to make your code look like this as much as possible will help, though it's not always possible.

Making a set of macros or inline functions that initialize your objects is a good idea. Also make another set of functions that allocate your objects' memory and pass that to your initializer functions. This allows for both local and dynamically allocated objects to easily be initialized. Similar operations for destructor-like functions is also a good idea.

Using OO techniques is good practice in many instances, and doing so in C just requires a little bit more typing (but allows for more control). Putters, getters, and other helper functions can help keep objects in consistent states and decrease the changes you have to make when you find an error, if you can keep the interface the same.

You should also look into the perror function and the errno "variabl".

Usually you will want to avoid using anything like exceptions in C. I generally try to avoid them in C++ as well, and only use them for really bad errors -- ones that aren't supposed to happen. One of the main reasons for avoiding them is that there are no destructor calls magically made in C, so non-local GOTOs will often leak (or otherwise screw up) some type of resource. That being said, there are things in C which provide a similar functionality.

The main exception like mechanism in C are the setjmp and longjmp functions. setjmp is called from one location in code and passed a (opaque) variable (jmp_buf) which can later be passed to longjmp. When a call to longjmp is made it doesn't actually return to the caller, but returns as the previously called setjmp with that jmp_buf. setjmp will return a value specified by the call to longjmp. Regular calls to setjmp return 0.

Other exception like functionality is more platform specific, but includes signals (which have their own gotchas).

Other things to look into are:

The assert macro, which can be used to cause program exit when the parameter (a logical test of some sort) fails. Calls to assert go away when you #define NDEBUG before you #include <assert.h>, so after testing you can easily remove the assertions. This is really good for testing for NULL pointers before dereferencing them, as well as several other conditions. If a condition fails assert attempts to print the source file name and line number of the failed test.

The abort function causes the program to exit with failure without doing all of the clean up that calling exit does. This may be done with a signal on some platforms. assert calls abort.

nategoose