Requires:

  • compiler: gcc 3.1 later

Let’s take a look at __builtin_choose_expr that one of the gcc builtins.

This only include in C not C++. It behaves like a 3-way operator(? : operator) in C, However, this is determined at compile time, not runtime.

This was added by the ‘Aldy Hernandez’ patch in gcc-3.1.

Let’s look at the description in the gcc document.


Built-in Function: type __builtin_choose_expr (const_exp, exp1, exp2)
You can use the built-in function __builtin_choose_expr to evaluate code depending on the value of a constant expression. This built-in function returns exp1 if const_exp, which is an integer constant expression, is nonzero. Otherwise it returns exp2.

This built-in function is analogous to the ‘? :’ operator in C, except that the expression returned has its type unaltered by promotion rules. Also, the built-in function does not evaluate the expression that is not chosen. For example, if const_exp evaluates to true, exp2 is not evaluated even if it has side effects...

ref. https://gcc.gnu.org/onlinedocs/gcc-7.4.0/gcc/Other-Builtins.html#Other-Builtins

Looking at the gcc code will help you understand how this builtin function works.
Let’s look at gcc 7.4.

-> git checkout gcc-7_4_0-release

$ cat ./gcc/c/c-parser.c

	...
	case RID_CHOOSE_EXPR:
	  {
	    ...
	    e1_p = &(*cexpr_list)[0];
	    e2_p = &(*cexpr_list)[1];
	    e3_p = &(*cexpr_list)[2];

	    c = e1_p->value;
	    mark_exp_read (e2_p->value);
	    mark_exp_read (e3_p->value);
	    if (TREE_CODE (c) != INTEGER_CST
		|| !INTEGRAL_TYPE_P (TREE_TYPE (c)))
	      error_at (loc,
			"first argument to %<__builtin_choose_expr%> not"
			" a constant");
	    constant_expression_warning (c);
	    expr = integer_zerop (c) ? *e3_p : *e2_p;
	    set_c_expr_source_range (&expr, loc, close_paren_loc);
		...

If you look at the gcc implementation, you can see that it is implemented as a 3-way ooperator.

expr = integer_zerop (c) ? *e3_p : *e2_p;
-> 
	const_exp ? exp1 : exp2

Don’t confuse gcc builtin as a function called at compile time

The first argument must be const_exp. In other words, even variables that can be determined at compile time cannot be used as the first argument.

-> can we use the variables for the first argument?

#include <stdio.h>

int main(void) {
	const int cond = 1;
	char *boolean = __builtin_choose_expr(cond, "true", "false");

	printf("boolean: %s\n", boolean);

	return 0;
}

-> gcc version 7.4.0 –target=x86_64-linux-gnu

$ gcc -o cond_expr cond_expr.c

cond_expr.c: In function ‘main’:
cond_expr.c:5:18: error: first argument to ‘__builtin_choose_expr’ not a constant
  char *boolean = __builtin_choose_expr(cond, "true", "false");

Notice that a compile error occurs. That is, only const expr can be used as follows.

-> use const expr for the first argument

#define BOOL_TO_STR(__x) \
	__builtin_choose_expr(((__x)), "true", "false")

int main(void) {
	printf("%s\n", BOOL_TO_STR(0));
	printf("%s\n", BOOL_TO_STR(1));

	return 0;
}

-> gcc version 7.4.0 –target=x86_64-linux-gnu

$ gcc -o bool_to_str bool_to_str.c
$ ./bool_to_str

false
true

-> assembly: x86_64 AT&T

int main(void) {
  400526:       55                      push   %rbp
  400527:       48 89 e5                mov    %rsp,%rbp
        printf("%s\n", BOOL_TO_STR(0));
  40052a:       bf d4 05 40 00          mov    $0x4005d4,%edi
  40052f:       e8 cc fe ff ff          callq  400400 <puts@plt>
        printf("%s\n", BOOL_TO_STR(1));
  400534:       bf da 05 40 00          mov    $0x4005da,%edi
  400539:       e8 c2 fe ff ff          callq  400400 <puts@plt>

        return 0;
  40053e:       b8 00 00 00 00          mov    $0x0,%eax
}
  400543:       5d                      pop    %rbp
  400544:       c3                      retq   
  400545:       66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
  40054c:       00 00 00 
  40054f:       90                      nop
$ strings -t x bool_to_str
...
    5d4 false
    5da true

You can see that __builtin_choose_expr has been changed at compile time to exp1 or exp2 each.

You might think that this is a rather unnecessary builtin function due to the constraint that only const exp is available in first arugments.

However, There are many ways to use it.

For example, you can write the following to determine the behavior of a particular bit of its value at compile time.

-> e.g. masked

#define IS_MASKED(__value) \
	__builtin_choose_expr(((__value) & 0x1), 1, 0)

int main(void) {
	printf("masked: %d\n", IS_MASKED(0xff));
	printf("masked: %d\n", IS_MASKED(0x02));
	printf("masked: %d\n", IS_MASKED(0x03));
}

-> gcc version 7.4.0 –target=x86_64-linux-gnu

$ gcc -o masked masked.c
$ ./masked

masked: 1
masked: 0
masked: 1

Another example may be used with sizeof. In the eBPF code of the kernel, It used the following with sizeof:

-> e.g. sizeof in BPF

$ cat ./include/trace/bpf_probe.h

/* cast any integer, pointer, or small struct to u64 */
#define UINTTYPE(size) \
	__typeof__(__builtin_choose_expr(size == 1,  (u8)1, \
		   __builtin_choose_expr(size == 2, (u16)2, \
		   __builtin_choose_expr(size == 4, (u32)3, \
		   __builtin_choose_expr(size == 8, (u64)4, \
					 (void)5)))))
#define __CAST_TO_U64(x) ({ \
	typeof(x) __src = (x); \
	UINTTYPE(sizeof(x)) __dst; \
	memcpy(&__dst, &__src, sizeof(__dst)); \
	(u64)__dst; })
}

This builtin function has a slightly different mechanism than the 3-way operator that we know well.

For the 3-way operator, the return type will be type cast to larger type, but the return type of this builtin function is the type of each exp1 or exp2.

-> check return type

#include <stdio.h>

#define RET_TYPE(__x) \
	__builtin_choose_expr((__x), ret_int(), ret_char())

int ret_int(void) {
	return 1;
}

char ret_char(void) {
	return 'a';
}

int main(void) {
	/* return char */
	printf("sizeof: %lu\n", sizeof(RET_TYPE(0)));
	/* return int */
	printf("sizeof: %lu\n", sizeof(RET_TYPE(1)));

	return 0;
}

-> gcc version 7.4.0 –target=x86_64-linux-gnu

$ gcc -o return_type return_type.c

./return_type
sizeof: 1
sizeof: 4

we can make the sample code to more nice with __builtin_types_compatible_p, but I’ll save it for the next post :)

The __builtin_choose_expr is come into its own when combined with other built-in functions that return const exp. For example, In combination with __builtin_types_compatible_p, we can provide function overloading for type-arguments that were previously impossible in std C.

-> functon overloading

#include <stdio.h>

#define debug(var)																					\
	printf(																										\
		__builtin_choose_expr( 																	\
			__builtin_types_compatible_p(typeof(var), int) 				\
			,"%d\n", __builtin_choose_expr(												\
				__builtin_types_compatible_p(typeof(var), char []) 	\
				,"%s\n", "0x%x\n")), var);

int main(void) {
	debug(1234);
	debug("jooojub");

	return 0;
}

-> gcc version 7.4.0 –target=x86_64-linux-gnu

$ gcc -o function_overloading function_overloading.c 
$ ./function_overloading

1234
jooojub

A detailed description of __builtin_types_compatible_p will be given in the another post.

Also possible to overlading the number of arguments in std C using macro. If there is a chance, I’ll cover it in another post also.

This built-in function is simple to operate and understand, yet powerful. A good use will help remove unnecessary code and refactoring. So, please remember that there is __builtin_choose_expr function in gcc.

jooojub.

Requires:

  • compiler: gcc 2.8 later

Let’s take a look at __builtin_alloca that one of the gcc builtins.

The first thing to keep in mind is that many books and posts recommend that you do not use this alloca built-in function for security code. I hope you understand the reason in this post.

If you look at several open-source code, you’ll often find it called __builtin_alloca. For example, you can see it if you look at a macro called strdupa.

-> strdupa macro (glibc/string/string.h)

# define strdupa(s)							   			\
  (__extension__							     		\
    ({									      			\
      const char *__old = (s);							\
      size_t __len = strlen (__old) + 1;				\
      char *__new = (char *) __builtin_alloca (__len);	\
      (char *) memcpy (__new, __old, __len);			\
    }))

__builtin_alloca is a function of gcc that can be assigned to the stack rather than the heap for dynmaic variables. So, if you look at the code that uses strdupa, you will see that it doesn’t have a free(). Unlike malloc which assigns to heap, Its lifetime is a function block. Therefore restoration to the caller function’s stack pointer is enough. In conclusion, There is a speed and time advantage over malloc.

The __builtin_alloca function in gcc is a function called by the gcc compiler at compile-time, not a function called run-time of the process. It’s like a macro, but technically it’s not a macro. You can see that __builtin_alloca was replaced by code other than function calling code in assembly.

-> builtin_alloca was replaced to

void func(size_t n, const char* src) {
    ...
	char *val = (char *)__builtin_alloca(n);
 739:	48 8b 45 e8          	mov    -0x18(%rbp),%rax
 73d:	48 8d 50 0f          	lea    0xf(%rax),%rdx
 741:	b8 10 00 00 00       	mov    $0x10,%eax
 746:	48 83 e8 01          	sub    $0x1,%rax
 74a:	48 01 d0             	add    %rdx,%rax
 74d:	b9 10 00 00 00       	mov    $0x10,%ecx
 752:	ba 00 00 00 00       	mov    $0x0,%edx
 757:	48 f7 f1             	div    %rcx
 75a:	48 6b c0 10          	imul   $0x10,%rax,%rax
 75e:	48 29 c4             	sub    %rax,%rsp
 761:	48 89 e0             	mov    %rsp,%rax
 764:	48 83 c0 0f          	add    $0xf,%rax
 768:	48 c1 e8 04          	shr    $0x4,%rax
 76c:	48 c1 e0 04          	shl    $0x4,%rax
 770:	48 89 45 f0          	mov    %rax,-0x10(%rbp)

	memcpy(val, src, n);
	...

It isn’t calling function like callq __builtin_alloca

It is used when you want to assign a dynamic variable and it is guaranteed to be used only temporarily inside a function

Let’s look at the description in the gcc document.


Built-in Function: void *__builtin_alloca (size_t size)
The __builtin_alloca function must be called at block scope. The function allocates an object size bytes large on the stack of the calling function. The object is aligned on the default stack alignment boundary for the target determined by the __BIGGEST_ALIGNMENT__ macro. The __builtin_alloca function returns a pointer to the first byte of the allocated object. The lifetime of the allocated object ends just before the calling function returns to its caller. This is so even when __builtin_alloca is called within a nested block.

ref. https://gcc.gnu.org/onlinedocs/gcc-7.4.0/gcc/Other-Builtins.html#Other-Builtins

ref: I haven’t checked in detail which version of the option was added, but it was already included in gcc 2.8.

We can easily see how it compiles using sample code.

-> sample code: alloca.c

#include <stdio.h>
#include <string.h>

void func(const size_t n, const char* src) {
	char *val = (char *)__builtin_alloca(n);
	strncpy(val, src, n);
	val[n] = '\0';

	printf("val: %s\n", val);
}

int main(void) {
	func(3, "simple");

	return 0;
}

-> gcc version 7.4.0 –target=x86_64-linux-gnu

$ gcc -g -o alloca alloca.c
$ ./alloca
val: sim

-> assembly: x86_64 AT&T

void func(const size_t n, const char* src) {
 6fa:	55                   	push   %rbp
 6fb:	48 89 e5             	mov    %rsp,%rbp
 6fe:	48 83 ec 20          	sub    $0x20,%rsp
 702:	48 89 7d e8          	mov    %rdi,-0x18(%rbp)
 706:	48 89 75 e0          	mov    %rsi,-0x20(%rbp)
 70a:	64 48 8b 04 25 28 00 	mov    %fs:0x28,%rax
 711:	00 00 
 713:	48 89 45 f8          	mov    %rax,-0x8(%rbp)
 717:	31 c0                	xor    %eax,%eax
	char *val = (char *)__builtin_alloca(n);
 719:	48 8b 45 e8          	mov    -0x18(%rbp),%rax
 71d:	48 8d 50 0f          	lea    0xf(%rax),%rdx
 721:	b8 10 00 00 00       	mov    $0x10,%eax
 726:	48 83 e8 01          	sub    $0x1,%rax
 72a:	48 01 d0             	add    %rdx,%rax
 72d:	b9 10 00 00 00       	mov    $0x10,%ecx
 732:	ba 00 00 00 00       	mov    $0x0,%edx
 737:	48 f7 f1             	div    %rcx
 73a:	48 6b c0 10          	imul   $0x10,%rax,%rax
 73e:	48 29 c4             	sub    %rax,%rsp
 741:	48 89 e0             	mov    %rsp,%rax
 744:	48 83 c0 0f          	add    $0xf,%rax
 748:	48 c1 e8 04          	shr    $0x4,%rax
 74c:	48 c1 e0 04          	shl    $0x4,%rax
 750:	48 89 45 f0          	mov    %rax,-0x10(%rbp)
	strncpy(val, src, n);
 ...
 7a6:	c9                   	leaveq 
 7a7:	c3                   	retq   

The complicated align routines have been added, but the key is simply increase the size of the stack and then retq. And also we can see the scope of the variable specified in the document.

The lifetime of the allocated object ends just before the calling function returns to its caller

__builtin_alloca is defined as alloca from glibc 1.09 to avoid using the tremendous name.

-> __builtin_alloca is defined as alloca in glibc/stdlib/alloca.h

#ifdef	__GNUC__
# define alloca(size)	__builtin_alloca (size)
#endif /* GCC.  */

So if you include alloca.h, you can simply use alloca. But in my personal opinion, since gcc builtin functions are functions that are called at compile-time rather than we usually think of run-time function, it seems to be better to use __builtin prefix to represent this. But this is a just problmes of coding style. So, If it is a project where many people participate, it is enough to unify the style through appropriate discussion.

Although alloca seems to have the same functionality as a VLA1 supporting C99, lifetime is different from VLA.

VLA litftime is block scope but, alloca is function scope. In other words, it cann’t be solved by VLA in the following situations.

-> It is impossible in VLA

#define COUNT 10

struct sample {
	unsigned char *p_x;
};

void func(void) {
	struct sample val[COUNT];
	int i;

	for (i = 0; i < COUNT; i++) {
		/* Use VLA */
		unsigned char x[i];
		/* unsigned char *x = (unsigned char *)__builtin_alloca(i); */
		memset(x, 0, i);

		val[i].p_x = x;
		/* The lifetime of x is terminated */
	}

	sumthing_to_do(val);

	return;
}

Of course, many books and posts comments suggest that code that assigns variables to the stack, like alloca and VLA, should not be used as un-safe code, and I agree with them. Variables assigned to the stack cause stack overflow, which is a security hole. And also passing the negative number in alloca could be executed in not intended at all. In addition, this is different from the typical alloc/free sequence in Standard C, beginners can be confusing.
-> alloca is not a standrad - it is GNU extension…

For various reasons, The kernel has been working on eliminating code that uses VLAs, and all of them have been removed in 4.20 perfectly.

ref: https://www.phoronix.com/scan.php?page=news_item&px=Linux-Kills-The-VLA

In the GNU document, The advantages of the alloca are described as follows:


Advantages-of-Alloca
* Using alloca wastes very little space and is very fast. (It is open-coded by the GNU C compiler.)

* Since alloca does not have separate pools for different sizes of blocks, space used for any size block can be reused for any other size. alloca does not cause memory fragmentation.

* Nonlocal exits done with longjmp (see Non-Local Exits) automatically free the space allocated with alloca when they exit through the function that called alloca. This is the most important reason to use alloca.

ref. https://www.gnu.org/software/libc/manual/html_node/Advantages-of-Alloca.html#Advantages-of-Alloca

And also described of disadvantages


Disadvantages-of-Alloca
* If you try to allocate more memory than the machine can provide, you don’t get a clean error message. Instead you get a fatal signal like the one you would get from an infinite recursion; probably a segmentation violation (see Program Error Signals).

* Some non-GNU systems fail to support alloca, so it is less portable. However, a slower emulation of alloca written in C is available for use on systems with this deficiency.

ref. https://www.gnu.org/software/libc/manual/html_node/Disadvantages-of-Alloca.html#Disadvantages-of-Alloca

Since gcc 4.7, __builtin_alloca_with_align has been added, and since gcc 8.1, __builtin_alloca_with_align_and_max has also been added. Its added to set align and max_size to make alloca safer.

It’s simple, please check the document.

https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html

Built-in Function: void *__builtin_alloca_with_align (size_t size, size_t alignment)
Built-in Function: void *__builtin_alloca_with_align_and_max (size_t size, size_t alignment, size_t max_size)

As discussion continues about stability of alloca, gcc 7.0 has added gcc options to detect whether alloca is used or make it safer to use. We will discuss this in a separate post later.

-Walloca-larger-than, -Walloca …

If you get a situation where you have to use alloca, pay attention to the size and range check. Note that even if you do a range range check, using it in an inline function can lead to unpredictable situations, so it’s better not to use it in an inline function.

jooojub.
  1. variable-length array (VLA), also called variable-sized, runtime-sized, is an array data structure whose length is determined at run time instead of at compile time 

gcc release notes link

link post

  • Check the gcc release note for each versions
    • link: https://gcc.gnu.org/releases.html
  • And also gcc online document for each versions
    • link: https://gcc.gnu.org/onlinedocs/

Requires :

  • compiler: gcc 2.8 later

If I declare char with no compile options, will it be regarded as signed or unsigned?
The results are different for each of architecture. Maybe it depends on the compiler version.

Most of the time, naturally writing code with assuming that char is a signed char, but it is depends on the compiler options, which can sometimes be dangerous. Especially if you want to write architecture independent code.

The gcc ompiler has a options1 that allows you to set whether char is treated as signed or unsigned.

-fsigned-char, -funsigned-char, -fno-signed-char”, -fno-unsigned-char

It looks like there’s a lot of options, in the end it means just two things.

char -> signed char: -fsigned-char == -fno-unsigned-char
char -> unsigned char: -funsigned-char == -fno-signed-char

It is a simple and explicit option, so it is also briefly described in the gcc documentation.


gcc-7.4.0/C-Dialect-Options
-funsigned-char
Let the type char be unsigned, like unsigned char.
Each kind of machine has a default for what char should be. It is either like unsigned char by default or like signed char by default. Ideally, a portable program should always use signed char or unsigned char when it depends on the signedness of an object. But many programs have been written to use plain char and expect it to be signed, or expect it to be unsigned, depending on the machines they were written for. This option, and its inverse, let you make such a program work with the opposite default.
The type char is always a distinct type from each of signed char or unsigned char, even though its behavior is always just like one of those two.

ref. https://gcc.gnu.org/onlinedocs/gcc-7.4.0/gcc/C-Dialect-Options.html#C-Dialect-Options

I haven’t checked in detail which version of the option was added, but it was already included in gcc 2.8.

-> git checkout gcc-2_8_0-release

$ cat ./gcc/toplev.c

char *lang_options[] =
{
	...
  "-fsigned-char",
  "-funsigned-char",
  "-fno-signed-char",
  "-fno-unsigned-char",
  ...

It isn’t noted in the release note, but in cpp, these options were added in gcc 3.1.

-> git checkout gcc-3_1-release

$ cat ./gcc/cppinit.c

#define COMMAND_LINE_OPTIONS                                      \
....
  DEF_OPT("fsigned-char",             0,      OPT_fsigned_char)
...
  DEF_OPT("funsigned-char",           0,      OPT_funsigned_char)

Let’s check the code to see if the options are work correctly.

Check with code

-> sample source code: char.c

#include <stdio.h>

int main(void) {
	char a = (1 << 8) - 1;

	printf("%d\n", a);

	return 0;
}

-> gcc version 7.4.0 –target=x86_64-linux-gnu

$ gcc -o char char.c 
$ ./char
-1

x86_64 gcc treats char as a signed char. Let’s build with -funsigned-char options.

-> gcc version 7.4.0 –target=x86_64-linux-gnu

$ gcc -funsigned-char -o char char.c 
$ ./char
255

-funisgned-char option is added, so char is treated as unsigned char.

I have seen how to treat char by default for each architecture.

-> x86_64: default signed char

...
 652:	c6 45 ff ff          	movb   $0xff,-0x1(%rbp)
 /* 
  - movsbl (Move a Sign-extended Byte): signed char
  - movzbl (Move a Zero-extended Byte): unsigned char
 */
 656:	0f be 45 ff          	movsbl -0x1(%rbp),%eax
...

-> aarch64: default unsigned char

...
 72c:	12800000 	mov	w0, #0xffffffff            	// #-1
 730:	39007fa0 	strb	w0, [x29, #31]
 /*
  - ldrsb (Load Register Signed Byte): signed char
  - ldrb (Load Register Byte): unsigned char
 */
 734:	39407fa1 	ldrb	w1, [x29, #31]
...

-> mips64: default signed char

...
 10000b20:	2402ffff 	li	v0,-1
 10000b24:	a3c20000 	sb	v0,0(s8)
 /*
  - lb (Load Byte): signed char
  - lbu (Load Byte unsigned): unsigned char
 */
 10000b28:	83c20000 	lb	v0,0(s8)
...

-> ppc: default unsigned char

 /*
  - lis (Load Immediate Shifted): signed char
  - li (Load Immediate): unsigned char
 */
 1000046c:	38 00 ff ff 	li      r0,-1
 10000470:	98 1f 00 0a 	stb     r0,10(r31)

I have found that simpler test code whether the defaults are signed or unsigned in stackoverflow.

#include <stdio.h>

int main(void) {
  printf("%d\n", '\x80');

	return 0;
}

-> result

$ gcc -o simple simple.c 
$ ./simple
-128

$ gcc -funsigned-char -o simple simple.c 
$ ./simple
128

If we are working on a large project, we will not be able to set the compile-options for each process because it use compile options globally.

That is, because we can’t rely on the compile options, should always use signed char or unsigned char when it depends on the signedness of type.

jooojub.
  1. The gcc documentation is also referred to as gcc commands. I will use it together. 

Requires :

  • compiler: gcc 3.3 later

If use the nonnull attribute, can check in compile-time for situations where NULL is passed as a function argument.

However, it only detects when NULL is specified implicitly, and its functionality is limited because it can not detect situations that are implicitly used.
I’ll go into more detail below about that.

This attribute is use with -Wnonnull or -Werror=nonnull compile options.
Meaning is lost when use with -Wno-nonnulloptions.

In the gcc-3.3 release note, nonnull was first introduced and used in many places, including the kernel and glibc.


GCC 3.3 Changes
C/ObjC/C++
...
A new function attribute, nonnull, has been added which allows pointer arguments to functions to be specified as requiring a non-null value. The compiler currently uses this information to issue a warning when it detects a null value passed in such an argument slot.

ref. https://gcc.gnu.org/gcc-3.3/changes.html

Details are kindly documented in the gcc documentation.


gcc 7.3/Common-Function-Attributes/nonnull
nonnull (arg-index, …)

The nonnull attribute specifies that some function parameters should be non-null pointers. For instance, 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 option is enabled, a warning is issued. The compiler may also choose to make optimizations based on the knowledge that certain function arguments will never be null.

If no argument index list is given to the nonnull attribute, all pointer arguments are marked as non-null. To illustrate, the following declaration is equivalent to the previous example:
extern void *
    my_memcpy (void *dest, const void *src, size_t len)
    __attribute__((nonnull));
ref. https://gcc.gnu.org/gcc-3.3/changes.html

As this is a simple attribute, it can be easily understood by sample code.
The importants thing is the argument index list is 1-based, rather than 0-based.

Check with code

-> sample source code: nonnull.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void __attribute__((nonnull(1, 2)))
  my_test_function(char *dest, const char *src, int len) {
	strncpy(dest, src, len);
}

int main(void) {
	my_test_function(NULL, NULL, 10);

	return 0;
}

I have specified that dest and src in my_test_function() should not be NULL.

$ gcc -Wnonnull nonnull.c

nonnull.c: In function ‘main’:
nonnull.c:11:2: warning: null argument where non-null required
										(argument 1) [-Wnonnull]
  my_test_function(NULL, NULL, 10);
  ^~~~~~~~~~~~~~~~
nonnull.c:11:2: warning: null argument where non-null required
										(argument 2) [-Wnonnull]

The compiler kindly warned that we could not use NULL for argnument 1, 2.
Because warning can overlook sometimes, we can print it out as an error to cause a compilation failure.

$ gcc -Werror=nonnull nonnull.c

nonnull.c: In function ‘main’:
nonnull.c:11:2: error: null argument where non-null required
										(argument 1) [-Werror=nonnull]
  my_test_function(NULL, NULL, 10);
  ^~~~~~~~~~~~~~~~
nonnull.c:11:2: error: null argument where non-null required
										(argument 2) [-Werror=nonnull]
cc1: some warnings being treated as errors

Using the nonnull attribute does not mean that don’t need to NULL check about the argument inside the my_test_function() function.
This attribute only check the detectable state at compile-time, which also has limited functionality.
-Wnonnull can not detect for the following situations:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void __attribute__((nonnull(1, 2)))
		my_test_function(char *dest, const char *src, int len) {
	strncpy(dest, src, len);
}

int main(void) {
	char *a = NULL;

	my_test_function(a, "test", 5);

	return 0;
}
$ gcc -Werror=nonnull nonnull.c
/* build success! */

If no argument index list is given to the nonnull attribute, NULL is checked for all arguments.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void __attribute__((nonnull))
		my_test_function(char *dest, const char *src, int len) {
	strncpy(dest, src, len);
}

int main(void) {
	my_test_function(NULL, NULL, 5);

	return 0;
}
$ gcc -Werror=nonnull nonnull.c

nonnull.c: In function ‘main’:
nonnull.c:11:2: error: null argument where non-null required
										(argument 1) [-Werror=nonnull]
  my_test_function(NULL, NULL, 10);
  ^~~~~~~~~~~~~~~~
nonnull.c:11:2: error: null argument where non-null required
										(argument 2) [-Werror=nonnull]
cc1: some warnings being treated as errors

Although it is limited in functionality, it may be a good attribute to cover the user’s mistakes to some extent because it can be checked at compile-time for situations where NULL is explicitly used.

jooojub.