A function’s return value and parameters may be decorated with attributes to convey additional information to the compiler and/or other
developers.
A commonly used attribute is nonnull
which can be used to mark a function’s return value and parameters as shown in the following:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
__attribute__((returns_nonnull)) int *
make_array_copy(int *src, size_t len) __attribute__((nonnull(1)));
int *make_array_copy(int *src, size_t len) {
int *dst = (int *)malloc(len * sizeof(int));
if (dst == NULL) {
perror("malloc failed");
exit(1);
}
memcpy(dst, src, len);
return dst;
}
The nonnull
attribute is meant for other developers and as a hint for compilers. Values marked as nonnull
are assumed to
have non-null values.
However, developers may accidentally break the nonnull
attribute as shown in the following code snippet:
__attribute__((returns_nonnull))
int *foo(__attribute__((nonnull)) int *x) {
x = 0; // This is compliant but might be surprising, use with caution
foo(0); // Noncompliant: null is passed as an argument marked as "nonnull"
return 0; // Noncompliant: return value is marked "nonnull" but null is returned
}
Failing to adhere to the attribute may introduce serious program errors. In particular, the compiler does not enforce that values marked as
nonnull
are indeed non-null at runtime; it is the developers' responsibility to adhere to the attribute. These values are typically
not null-checked before use. Passing null (i.e., NULL
, 0
or nullptr
) as an argument to a parameter
that is marked as nonnull
or returning null from a function marked as returns_nonnull
is, hence, likely to cause a
null-pointer dereference. Compilers may even apply optimizations based on this attribute and might, for instance, remove an explicit
null-check if the parameter is declared as nonnull
— even in code outside of the function with the attribute.
Note that the nonnull
attribute is a GNU extension (see nonnull and returns_nonnull) which many
compiler vendors have implemented.
Note that it is allowed to assign null to a parameter marked as nonnull
. This attribute is only concerned with the function
call contract and does not control the evolution of the parameter variable. For example, a linked-list search could be implemented as follows:
struct List {
int value;
List *next; // nullptr for a tail node.
};
List *findElement(List *l, int elem) __attribute__((nonnull(1)));
List *findElement(List *l, int elem) {
while(l && l->value != elem)
l = l->next;
return l;
}