GCC 4 warnings about sentinels

Null-terminate parameter list

Some functions have a variable number of arguments, where the argument list has to be terminated with a NULL value. To solve this warning, null-terminate the list of parameters.

Introduction

When compiling using GCC 4, you may get one of the following warnings:

warning: missing sentinel in function call
warning: not enough variable arguments to fit a sentinel

This document explains what these warnings mean and how to resolve them.

Sentinels & warnings

A sentinel is a special value which indicates the end of a series of data. In the context of GCC, it is a null pointer which ends a list of parameters to a function.

The function

execl(const char * path, const char * arg, ...)

takes a variable amount of parameters. To know where to stop reading parameters, the last parameter must be a null pointer. When you do not end the list with a null pointer, execl will not stop scanning, which will result in errors. The following code, therefore, is wrong:

#include <unistd.h>

int main() {
	execl("/bin/ls", "ls", "-l");
	return 0;
}

The execl function call is not terminated by a null pointer and GCC will give a warning. The following code is better:

#include <unistd.h>

int main() {
	execl("/bin/ls", "ls", "-l", NULL);
	return 0;
}

As you can see, the execl function is now terminated by a null pointer and it will stop scanning it's arguments after that. However, GCC 4 will still issue a warning:
warning: missing sentinel in function call
This is because NULL is not of the right type: it is defined as integer 0 instead of a pointer with the value 0. Doing an explicit cast can make the warning go away:

#include <unistd.h>

int main() {
	execl("/bin/ls", "ls", "-l", (char *)NULL);
	return 0;
}

Using sentinels

One can specify that a function uses a sentinel by declaring it as follows:

char * build_path(const char * str, ...) __attribute__((__sentinel__(0)));

This indicates that the parameter list is ended with the special value 0, which must be a char pointer.

On most systems, there is no difference between 0 and (char *)0. On 64 bit systems, however, the integer 0 is 32 bits and the pointer 0 is 64 bits. The compiler does not know whether it is an integer or a pointer, and defaults for the integer. This will not clear the upper 32 bits and the function will not stop scanning its parameters.