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.
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.
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 |
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 |
The author is an engineer in computer sciences located in France. He can be contacted here.