First of all I’m talking specifically about C, not C++. However some of this may also apply to C++.
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 */
(s);
free= NULL;
s }
int main(void)
{
struct s *s = malloc(sizeof(*s));
("s: %p\n", s);
printf(s);
f("s: %p\n", s);
printf
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 */
(*s);
free*s = NULL;
}
then in main() we pass in s like
(&s); f
which now produces
$ ./pass-by-value
s: 0x8092a0
s: (nil)
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.
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
It’s superfluous. Why clutter the code with unnecessary casts?
It can actually hide bugs. OK, how you might ask…
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.