digital-domain.net

Some common C mistakes

First of all I’m talking specifically about C, not C++. However some of this may also apply to C++.

C only supports pass-by-value

C really only supports pass by value. Pass by reference can be accomplished by using pointers.

A fairly common thing I see in codebases is something like the following

#include <stdio.h>
#include <stdlib.h>

struct s {
        int i;
};

static void f(struct s *s)
{
        /* Do something with s */

        free(s);
        s = NULL;
}

int main(void)
{
        struct s *s = malloc(sizeof(*s));

        printf("s: %p\n", s);
        f(s);
        printf("s: %p\n", s);

        return 0;
}

Which produces the following output

$ ./pass-by-value 
s: 0x8092a0
s: 0x8092a0

In the function f() we pass a pointer to a struct. Something is done with s, then it’s free(3)’d.

So far, so good, but then a common thing to do is to NULL the free’d pointer.

However that will have no effect outside f(). The pointer address was passed to f() on the stack and now has no direct connection to s in main().

If you want to really NULL out the pointer, then you need to pass it in by reference by passing in a pointer to it. E.g

static void f(struct s **s)
{
        /* Do something with *s */

        free(*s);
        *s = NULL;
}

then in main() we pass in s like

        f(&s);

which now produces

$ ./pass-by-value
s: 0x8092a0
s: (nil)

Function prototypes with no arguments

It is quite common to see things like

void f()
{
        /* Do something */
}

where f() takes no arguments. Writing it like the above has different meanings in C and C++.

In C++ it means a function that takes no arguments. In C it means the number (and type) of arguments hasn’t been specified (note, this is different to using ‘…’ to specify a variable number of arguments).

The correct way to do this in C is

void f(void)
{
        /* Do something */
}

You can enable -Wstrict-prototypes in both gcc and clang to catch these. E.g

$ gcc -Wstrict-prototypes -c empty-func-args.c 
empty-func-args.c:1:6: warning: function declaration isn’t a prototype [-Wstrict-prototypes]
    1 | void f()
      |      ^
$ clang -Wstrict-prototypes -c empty-func-args.c
empty-func-args.c:1:7: warning: this old-style function definition is not preceded by a prototype [-Wstrict-prototypes]
void f()
      ^
1 warning generated.

Casting the return type of malloc et al

OK, so this one might be slightly controversial…

Another common thing I see is code like

        int *vals = (int *)malloc(sizeof(int) * nr_vals);

Note the casting of the return from malloc(3). While this may be required in C++, in C

Well lets say you forgot to

#include <stdlib.h>

so you don’t have the declaration of malloc(3), now the compiler may assume that the return type of malloc(3) is an int. Now you may be getting a truncated memory address returned.

NOTE: With current compilers this is not such a problem anymore and you will get a clear warning about the missing declaration. But the first point still stands.