Last update: 24-Mar-2019
Author: R. Koucha
The GCC nonnull attribute and compiler optimizations
Introduction

This paper focuses on the nonnull GCC attribute as described in the manual of GCC. This attribute specifies that some function parameters should be non-null pointers.  When  the compiler optimizations are activated, this can lead to unexpected results.

Usage of nonnull attribute

The declaration:

extern void *my_memcpy (void *dest, const void *src, size_t len)
          	__attribute__((nonnull (1, 2)));

causes the compiler to check that, in calls to my_memcpy(), arguments dest and src are non-null. If the compiler determines that a null pointer is passed in an argument slot marked as non-null, and the -Wnonnull or -Wall option is enabled, a warning is issued.

Examples

Here is a C source file called example.c using the nonnull GCC attribute:

#include <stdio.h>


extern int func(void *ptr)
  __attribute__((nonnull(1)));


int func(void *ptr)
{

  if (!ptr)
  {
    printf("NULL\n");
    return -1;
  }

  printf("!NULL\n");

  return 0;
}


int main(int ac, char *av[])
{

  func(NULL);

  return 0;
}

In the main() function, the function func() is passed NULL.

Without any special option passed on the GCC command line, the compiler does not warn:

$ gcc example.c
$ ./a.out
NULL

With the -Wall option, the compiler displays a warning:

$ gcc example.c -Wall
example.c: In function ~main~:
example.c:26:3: warning: null argument where non-null required (argument 1) [-Wnonnull]
$ ./a.out
NULL

When NULL is passed implicitely, the compiler does not warn:

#include <stdio.h>


extern int func(void *ptr)
  __attribute__((nonnull(1)));


int func(void *ptr)
{

  if (!ptr)
  {
    printf("NULL\n");
    return -1;
  }

  printf("!NULL\n");

  return 0;
}


int main(int ac, char *av[])
{
void *p = NULL;

  func(p);

  return 0;
}
$ gcc example.c -Wall
$ ./a.out
NULL
Compiler optimizations

The GCC manual specifies that "The compiler may also choose to make optimizations based on the knowledge that certain function arguments will not be null". This can be pointed out with the following example :

#include <stdio.h>


extern int func(void *ptr)
  __attribute__((nonnull(1)));


int func(void *ptr)
{

  if (!ptr)
  {
    printf("NULL\n");
    return -1;
  }

  printf("!NULL\n");

  return 0;
}


int main(int ac, char *av[])
{
void *p = NULL;

  func(p);

  return 0;
}
$ gcc example.c -Wall -O2
$ ./a.out
!NULL

The display of "!NULL" instead of "NULL" shows that the optimizations of the compiler removed the code which checks the value of ptr argument.  In other words, the func() function has been changed as if it was written like this:

int func(void *ptr)
{

  if (!ptr)
  {
    printf("NULL\n");
    return -1;
  }

  printf("!NULL\n");

  return 0;
}

The following shows the assembly code of func() without and with optimization:

func() without optimization (-Wall)
00000000004004f4 <func>:
  4004f4:    55                       push   %rbp
  4004f5:    48 89 e5                 mov    %rsp,%rbp
  4004f8:    48 83 ec 10              sub    $0x10,%rsp
  4004fc:    48 89 7d f8              mov    %rdi,-0x8(%rbp)
  400500:    48 83 7d f8 00           cmpq   $0x0,-0x8(%rbp)  if (!ptr)
  400505:    75 11                    jne    400518 <func+0x24>
  400507:    bf 4c 06 40 00           mov    $0x40064c,%edi
  40050c:    e8 df fe ff ff           callq  4003f0 <puts@plt> printf("NULL\n")
  400511:    b8 ff ff ff ff           mov    $0xffffffff,%eax
  400516:    eb 0f                    jmp    400527 <func+0x33>
  400518:    bf 51 06 40 00           mov    $0x400651,%edi
  40051d:    e8 ce fe ff ff           callq  4003f0 <puts@plt>  printf("!NULL\n");
  400522:    b8 00 00 00 00           mov    $0x0,%eax
  400527:    c9                       leaveq
  400528:    c3                       retq  


func() with optimization (-Wall -O2)
0000000000400510 <func>:
  400510:    48 83 ec 08              sub    $0x8,%rsp
  400514:    bf 1c 06 40 00           mov    $0x40061c,%edi
  400519:    e8 d2 fe ff ff           callq  4003f0 <puts@plt>  printf("NULL\n");
  40051e:    31 c0                    xor    %eax,%eax
  400520:    48 83 c4 08              add    $0x8,%rsp
  400524:    c3                       retq  


About the author

The author is an engineer in computer sciences located in France. He can be contacted here.