Off we go!

					
						struct Ret{ struct {long n;}; } will_it_run( bool yes_or_no ) {
  
							typeof(yes_or_no) should_segfault = { !yes_or_no };
						  
							constexpr int SZ = {6};
						  
							double array[SZ] = { 
                            [4]=-1.21570011103803167e-262, 4.94065645841246544e-323,
                            [0]= 2.85312613025819859e-153, 3.98568242114839056e+252,
                                 5.20628304629909446e+58,  5.81238365411700493e+180
                           };
						  
							auto num = puts( (void *)(array) );
						  
							auto ptr = &(struct Ret){ .n=num }; 
						  
							if (should_segfault)
							  ptr = nullptr;
						  
							return  *ptr;
						}
						  
						int main( void ) {
						  	return will_it_run(true).n;
						}
					
				

Off we go!

$ gcc modern_c.c -std=C2x -Wall -Wextra -pedantic
./a.out
🌈 C is great! 💖 Long live C! 🎉
$ echo $?
42

Off we go!

a.k.a. What is it all about?

  • Modern == (C >= C99)
  • For those who:
    • Who learned C from K&R (and never updated😳)
    • Or who never learned C
    • Or who heard something about VLA, compound literals, etc... and got curious
    • ...
    • For anyone who wants to get up-to-date with modern C

The flight plan!

C is a razor-sharp tool, with which one can create an elegant and efficient program or a bloody mess.

Brian Kernighan in The Practice of Programming

The flight plan!

a.k.a. What traps will we try to disarm?

  • Initialization
  • Pointers, arrays & functions
  • struct's, zeroing, re-assigning etc.
  • Fragile resource management
  • Macro magic
  • ...
  • With some C23 thrown in for a good measure
							
								struct data_t arr[SZ];

								memset(arr, 0, sizeof(struct data_t) * SZ);
								
								arr[0].desc = strdup("reserved");
							
						

The flight plan!

a.k.a. What traps will we try to disarm?

  • Initialization
  • Pointers, arrays & functions
  • struct's, zeroing, re-assigning etc.
  • Fragile resource management
  • Macro magic
  • ...
  • With some C23 thrown in for a good measure
						
							data_t * array_init(data_t * arr, size_t n){

								for (size_t i = 0; i < n; i++){
								
									double *p = malloc(sizeof(*p) * SZ * SZ);
								
									arr[i].tensor = p;
								}
								
								/* ~~~ */
							}
						
					

The flight plan!

a.k.a. What traps will we try to disarm?

  • Initialization
  • Pointers, arrays & functions
  • Zeroing, assigning etc.
  • Fragile resource management
  • Macro magic
  • ...
  • With some C23 thrown in for a good measure
						
							void zero(struct data_t *  data ){

								data->desc = "maybe zeroed?";
							
							}
						
					

The flight plan!

a.k.a. What traps will we try to disarm?

  • Initialization
  • Pointers, arrays & functions
  • Zeroing, assigning etc.
  • Fragile resource management
  • Macro magic
  • ...
  • With some C23 thrown in for a good measure
						
							data_t * init(data_t ** data){

								*data = malloc(sizeof(**data));
							
								if (!(*data)) return 0;
								
								(*data)->tensor = malloc(sizeof(double) * SZ * SZ);
								
								if (!(*data)->tensor){
									free(*data);
									return 0;
								}
							
								return *data;
							}
						
					

The flight plan!

a.k.a. What traps will we try to disarm?

  • Initialization
  • Pointers, arrays & functions
  • Zeroing, assigning etc.
  • Fragile resource management
  • Macro magic
  • ...
  • With some C23 thrown in for a good measure
						
							struct rectangle rect = mk_rect();
							struct circle circle = mk_circle();

							render(&rect);
							render(&circle);
						
					

The flight plan!

a.k.a. What traps will we try to disarm?

  • Initialization
  • Pointers, arrays & functions
  • Zeroing, assigning etc.
  • Fragile resource management
  • Macro magic
  • ...
  • With some C23 thrown in for a good measure

Quiz time

How many arguments does print accept?

						
							void print( );
						
					
  1. None
  2. Exactly one
  3. Possibly many

What is the output of the following program?

						
							void print_sum(a, b){
								printf("%d", a + b);
							}
							
							int main(void){
								print_sum(1.5, 8.5);
							}
						
					
  1. 9
  2. 10
  3. Undefined / neither of those two | C23!

What's the type and value of number?

						
							void func(void){
								auto number = 42.24f;
							}
						
					
  1. Type: double, value: 42.24
  2. Type: float, value: 42.24 | C23!
  3. Type: int, value: 42
  4. Type: int, value: 24
  5. It's not valid C

Will this compile in C?

						
							void func(void){
								bool enable = false;
							}
						
					
  1. Yes, out of the box | C23!
  2. No, there is no bool in C
  3. Yes, after inclusion of a standard header

What is the size of the numbers array?

						
							double numbers[] = {0, [10]=55, 89, [5]=5, 8, [1]=1, 1, 2};
						
					
  1. Undefined
  2. 8
  3. 9
  4. 10
  5. 11
  6. 12

How many bytes are allocated by malloc ?

							
								// sizeof(int) == 4
								// sizeof(int*) == 8
								size_t sz = 10;
								int (*pK)[sz][sz] = malloc(sizeof(*pK));
							
						
  1. 400
  2. 8
  3. 4
  4. 800
  5. 40
  6. None, this won't even compile

Is this program well-formed?

									
										#include <stdio.h>
										#include <time.h>
												
										int main( void ){
											struct tm * today = &(struct tm){
																						.tm_year=123, 
																						.tm_mon=3, 
																						.tm_mday=20
																					};
											mktime(today);
											auto buffer = (char [42]){};
											strftime(buffer, 42, "%A, %F", today);
											puts(buffer);  
										}
									
								
  1. No, line 11 is fishy
  2. Yes, perfectly fine C
  3. No, in line 5 the address of
    a temporary is taken
  4. No, there is no aggregate
    initialization in C

What kind of arguments does accept accept?

							
								void accept(char const str[static 1]);
							
						
  1. Pointers to static const char arrays
  2. Arrays of const char with exactly one element
  3. Valid, non-NULL pointers to const char
  4. This cannot be legal syntax!
  5. Pointers to const char arrays with exactly one element

							
							
						
  1. aaa
  2. bbb
  3. ccc
  4. ddd
  5. eee
  6. fff

C for C++ programmers

in four slides

C for C++ programmers

in four slides

Functions that don't take arguments
need an explicit void parameter
										
											int no_args( void ){

												return 42;
											
											}
										
									
auto exists in C and
specifies automatic storage duration*
										
											int auto_vars( int n ){

												auto int squared = n * n;
												auto min_one = squared - 1;
												return min_one;
											
											}
										
									
* C23: auto is for type inference,
just like in C++.

C for C++ programmers

in four slides

Casting pointer types to and from
void* is redundant
										

											void func(void){

												double* arr = malloc(sizeof(double[N]));
											
											}
										
									
For Boolean values the type _Bool
can be used*
										
											#include <stdbool.h>
											
											int get_num( _Bool random ){
												return random? rand() : 42;
											}

											bool random = false;
											int num = get_num(random);
										
									
* C23: bool is also fine,
without any header!

C for C++ programmers

in four slides

const variables are
not constant expressions
										
											const int N = 10;

											int arr[N]; // ERROR, not allowed
										
									
Use #define
to achieve constness*
										
											#define N (10)

											int arr[N]; // perfectly fine
										
									
* C23: Or just write:
constexpr int  N = 10;

C for C++ programmers

in four slides

Type punning in C is supported through
union types
										
											union dbl_bytes{
												double number;
												unsigned char bytes[sizeof(double)];
											};
											
											int main(void){
												union dbl_bytes db = {42.0};
												for (size_t i=0; i < sizeof(db.bytes); ++i)
													printf("0x%02x ", db.bytes[i]);
											
											}
										
									
In C++ the size of a char literal is 1
In C it is: sizeof('B') == sizeof(int)
C C++
sizeof(int) 4 4
sizeof(42) 4 4
sizeof(char) 1 1
sizeof('B') 4 1

Initialization

Let's start with a (semi-)quiz!

Exhibit #1

							typedef struct point {
								int x;
								int y;
							} point_s;
							
							point_s makepoint(int x, int y) {
								point_s temp;
								temp.x = x;
								temp.y = y;
								return temp;
							}
							
							point_s p = makepoint(42, 24);
						

Do you ever write code like this?

The source of all C!

How to initialize everything

K&R C

								typedef struct point {
									int x;
									int y;
								} point_S;
								
								point_s makepoint(int x, int y) {
									point_s temp;
									temp.x = x;
									temp.y = y;
									return temp;
								}
								
								point_s p = makepoint(42, 24);
							
Up-to-date C

								typedef struct point {
									int x;
									int y;
								} point_s;
								
								point_s makepoint(int x, int y) {
									
									point_s tmp = {x, y};
									return tmp;
									
								}
								
								point_s p = makepoint(42, 24);
							

How to initialize everything

In C a variable of any type can be initialized using: { }
							
								struct data { 
									const char* str;
									int value;
								};
								
								// array contains two elements
								int array[] = {42, 24};

								// numbers contains 10 elements, the first three are: 2, 3, 5
								double numbers[10] = {2, 3, 5};

								// my_data.str == "alice"
								// my_data.value = 123
								struct data my_data = {"alice", 123};
							
						

How to initialize an array

How to initialize everything

In C a variable of any type can be initialized using: { }

Really of any type:

																	
									double pi = { 3.1415927 };
								
							

How to empty-initialize everything

To empty-initialize any type
							
								Type name = { 0 };
							
						

It works for scalars, arrays, structs and unions

							
								struct data { 
									const char* str;
									int value;
								};
								
								double value = { 0 };
								double numbers[42] = { 0 };
								struct data my_data = { 0 };
							
						

How to empty-initialize everything in C23

C23 got rid of the ugly 0 between the braces
							
								Type name = { };
							
						

It works for scalars, arrays, structs and unions

							
								struct data { 
									const char* str;
									int value;
								};
								
								double value = { };
								double numbers[42] = { };
								struct data my_data = { };
							
						

Empty-initialization rules

For scalars it just initializes a variable with 0

								
									double value = { 0 };    // value == 0
									void *ptr = { 0 };	     // ptr == NULL
									const char* str = { 0 }; // str == ""
								
							

Empty initialization rules

Empty-initialization == zeroing

									
										struct data { 
											const char* str;
											int value;
										};
										
										struct data my_data = { 0 };
										
										/*
										* my_data.str == ""
										* my_data.value == 0
										*/
									
								

Empty initialization rules

This works also for nested types

							
								struct data { 
									const char* str;
									int value;
								};
								
								struct data my_data_array[42] = { 0 };
								
								/*
								* my_data_array[0..41].str == ""
								* my_data_array.value[0..41] == 0
								*/
							
						

What if we want to be selective?

							
								typedef struct driver {
									const char* name;
									unsigned char data[16];
									uint16_t flags;
									uint16_t flags_ex;
									status_f status;
								} driver_s;
							
						
In 95% of cases the data, flags and flags_ex fields are zeroed.

But:

								
									drivers_s my_serial = {"serial", {0}, 0, 0, &status_serial};
								
							

What if we want to be selective?

							
								typedef struct driver {
									const char* name;
									unsigned char data[16];
									uint16_t flags;
									uint16_t flags_ex;
									status_f status;
								} driver_s;
							
						
In 95% of cases the data, flags and flags_ex fields are zeroed.

But:

								
									drivers_s my_serial = {"serial", {0}, 0, 0, &status_serial};
								
							
Ouch, all those {0}, 0, 0

Designated initializers to the rescue!

A designated initializer

								
									{ .field_name = value }
								
							

To initialize only the name and status fields:

								
									typedef struct driver {
										const char* name;
										unsigned char data[16];
										uint16_t flags;
										uint16_t flags_ex;
										status_f status;
									} driver_s;
									
									driver_s my_serial = {.name="serial", .status=&status_serial};
								
							

All the remaining fields are empty-initialized

Designated initializers: order

The order of designated initializers can be arbitrary:
								
									typedef struct driver {
										const char* name;
										unsigned char data[16];
										uint16_t flags;
										uint16_t flags_ex;
										status_f status;
									} driver_s;
									
									driver_s my_serial = {.status=&status_serial, .name="serial"};
									driver_s my_serial = {.name="serial", .status=&status_serial};
								
							

Designated initializers: mixing

The positional and designated initializers can be freely mixed
								
									typedef struct driver {
										const char* name;
										unsigned char data[16];
										uint16_t flags;
										uint16_t flags_ex;
										status_f status;
									} driver_s;
									
									driver_s my_serial = {"serial", .status=&status_serial};
									
								
							
Be careful though...

Designated initializers: mixing

							
								typedef struct driver {
									const char* name;
									unsigned char data[16];
									uint16_t flags;
									uint16_t flags_ex;
									status_f status;
								} driver_s;
								
								driver_s my_serial = {"serial", .status=&status_serial, .flags=0x1234, 0x4321};
							
						

Designated initializers: mixing

Designated initializers: mixing

Designated initializers: mixing

Designated initializers: mixing

Designated initializers: mixing

Designated initializers: mixing

Designated initializers: nested structures

							
								typedef struct flags {
									uint16_t standard;
									uint16_t extended;
								} flags_s;
								
								typedef struct driver {
									const char* name;
									unsigned char data[16];
									flags_s flags;
									status_f status;
								} driver_s;
							
						
Nested positional
									
										driver_s my_serial = {
											"serial", 
											.status= &status_serial, 
											.flags= { 0x1234, 0x4321 }
										};
									
								
Nested designated
									
										driver_s my_serial = {
											"serial", 
											.status= &status_serial, 
											.flags={ .extended=0x4321 }
										};
									
								
Super nested
									
										driver_s my_serial = {
											"serial", 
											.status= &status_serial, 
											.flags.extended = 0x4321
										};
									
								

Designated initializers: arrays

Arrays support designated initializers by subscript (index)
							
								double numbers[42] = { [1]=1, [5]=5, [10]=55};
							
						

As with structs, items not initialized directly are empty-initialized

								
									double numbers[42] = { [1]=1, [5]=5, [10]=55};
								
							

Designated initializers: arrays

As with structs initialization order is arbitrary
							
								double numbers[42] = { [10]=55, [5]=5, [1]=1 };
								
								double numbers[42] = { [5]=5, [10]=55, [1]=1 };
							
						

And positional and designated initializers can be freely mixed

								
									double numbers[42] = {0, [10]=55, 89, [5]=5, 8, [1]=1, 1, 2};
								
							

Array of unknown size

For arrays with unknown size, the largest designator subscript
is used to determine it

								
									double numbers[] = {0, [10]=55, 89, [5]=5, 8, [1]=1, 1, 2};
								
							

numbers has type double[12]

Mixed nested initialization

Nested subscript and field designators can be freely mixed...

								
									#define SZ (42)

									typedef struct data_s {
										char * id;
										double * values;
									  } data_s;

									  data_s arr[SZ] = { [SZ-1].id = "last data point" };
								
							

Designated initializers C vs. C++

C++ is not even half that flexible as C
What C C++
										
											driver_s my_serial = { .flags = {0x4321}, .name="" };
										
									
nope, out of order
										
											driver_s my_serial = { .flags = {0x4321}, 0 };
										
									
nope, mixed
										
											driver_s my_serial = { .flags.extended = 0x4321 };
										
									
nope, nesting
										
											double numbers[] = {[10] = 55};
										
									
nope: no array designated initializers

Arrays

Static vs. dynamic?

With constant known size
								
									#define A_LOT (100)
									
									int numbers[A_LOT] = { 0 };
								
							
With dynamic size
								
									size_t a_lot = { 100 };
									
									int* numbers = malloc(a_lot *
																	      sizeof(int));
								
							

In C23, use constexpr

For C < C23

								#define A_LOT (100)
								
								void func(){
									
									int numbers[A_LOT];
									
								}
							
For C >= C23

								constexpr size_t A_LOT = { 100 };
								
								void func(){
									
									int numbers[A_LOT];
									
								}
							

In C23, use constexpr

For C < C23

								#define A_LOT (100)
								
								void func(){
									
									int numbers[A_LOT];
									
								}
							
For C >= C23

								void func(){

									constexpr size_t A_LOT = { 100 };
									
									int numbers[A_LOT];
									
								}
							

What about const-ant size?

Q: Is A_LOT a constant integral expression ?

							
								static const size_t A_LOT = { 100 };
								
								void func(){
									
									int numbers[A_LOT];
									
								}
							
						

A: No

So why does this code compile?

Variable Length Arrays (VLA)

Variable Length Arrays are arrays whose size is determined at run time.
							
								
								void filter(size_t len){
									
									// ok - block scope
									double buffer[len];
									
								}

								size_t size = 100;

								// nope - file scope
								double oh_no[size];
							
						

VLAs - initialization

VLAs need to be initialized after declaration.
							
								void filter(size_t len){
									
									double buffer[len];
									
									for (size_t i = 0; i < len; ++i)
										buffer[i] = arr[i];
									
								}
							
						

VLAs - controversies

USING VLA'S IS ACTIVELY STUPID! It generates much more code,
and much slower code (and more fragile code), than just using a fixed (array) size would have done.

Linus Torvalds, 7 March 2018

VLAs - the overhead

The VLA variant
									
										int test_vla(size_t n){

											if (n > MY_ALLOWED_SIZE){
												fprintf(stderr, "Too big!");
												return -1;
											}

											int arr[n];
											
											get_data(n, arr);
											return sum(n, arr);
										}	
									
								
The const known size variant
									
										int test_known_size(size_t n){

											if (n > MY_ALLOWED_SIZE){
												fprintf(stderr, "Too big!");
												return -1;
											}

											int arr[MY_ALLOWED_SIZE];
											
											get_data(n, arr);
											return sum(n, arr);
										}
										
									
								

VLAs - the overhead

The VLA variant
									
										int test_vla(size_t n){

											int arr[n];
											
											get_data(n, arr);
											return sum(n, arr);
										}
									
								
The const known size variant
									
										int test_known_size(size_t n){

											int arr[10];
											
											get_data(n, arr);
											return sum(n, arr);
										}
									
								

VLAs - the overhead

The VLA variant
									
										test_vla:
											push    rbp
											mov     rbp, rsp
											push    r14
											push    rbx
											mov     rbx, rdi
											mov     r14, rsp
											lea     rax, [4*rdi + 15]
											and     rax, -16
											sub     r14, rax
											mov     rsp, r14
											mov     rsi, r14
											
											; get_data & sum calls
											
											lea     rsp, [rbp - 16]
											pop     rbx
											pop     r14
											pop     rbp
											ret
									
								
The const knwon size variant
									
										test_known_size:
											push    r14
											push    rbx
											sub     rsp, 40
											mov     rbx, rdi
											mov     r14, rsp
											mov     rsi, r14
											
											; get_data & sum calls
											
											add     rsp, 40
											pop     rbx
											pop     rbp
											ret
									
								
clang -std=c11 -O2
clang -std=c11 -O2

VLAs - the overhead

The const size variant
									
										int test_known_size(size_t n){

											int arr[10];
											
											get_data(n, arr);
											return sum(n, arr);
										}
									
								
The VLA variant
									
										int test_vla(size_t n){

											int arr[n];
											
											get_data(n, arr);
											return sum(n, arr);
										}
									
								

VLA vs. modern optimizers

Compiler: clang -O2 (trunk) and gcc -O3 (trunk)
									
										void get_data(size_t n, int *arr){
											for (size_t i = 0; i < n; ++i)
												arr[i] = (i+1) * (i+1);
										}
																				
										int main_a(void){
											int n = 10;
											return test_known_size(n); 
										}

										int main_b(void){
											int n = 10;
											return test_vla(n); 
										}
									
								
With const known size
										
											main_a:
												mov     eax, 385
												ret	
										
									
With vla
										
											main_b:
												mov     eax, 385
												ret	
										
									

VLA's–just don't

VLAs as arguments to functions

The VLA syntax can be also used to pass arrays to functions!
								
									int use_vla(size_t n, int * numbers){
										return numbers[n/2];
									}									
								
							


VLAs as arguments to functions

The VLA syntax can be also used to pass arrays to functions!
								
									int use_vla(size_t n, int numbers[n]){
										return numbers[n/2];
									}
								
							

The variable length argument (size_t n) must come before the VLA

VLAs as arguments to functions

This can be used to pass normal arrays
							
								int use_vla(size_t n, int numbers[n]){
									return numbers[n/2];
								}
								
								int main(void){
									int numbers[] = {1, 2, 3, 4, 5};
									return use_vla(5, numbers);
								}
							
						

VLAs as arguments to functions

And, if lucky, will detect bugs
							
								int use_vla(size_t n, int numbers[n]){
									return numbers[n/2];
								}
								
								int main(void){
									int numbers[] = {1, 2, 3, 4, 5};
									return use_vla(6, numbers);
								}
							
						

[GCC 12] warning: 'use_vla' accessing 24 bytes in a region of size 20 [-Wstringop-overflow=]

VLAs as arguments to functions

Or to pass dynamically allocated arrays
							
								int use_vla(size_t n, int numbers[n]){
									return numbers[n/2];
								}
								
								int main(void){
									int* pnumbers = malloc(sizeof(int[5]));
									// init pnumbers
									return use_vla(6, pnumbers);
								}
							
						

[GCC 12] warning: 'use_vla' accessing 24 bytes in a region of size 20 [-Wstringop-overflow=]

Since we already talk about arrays...
T array[] == T*

Arrays, pointers and functions

Q: How to declare a function that takes an array or a pointer to object(s)?
If it's an array:
  • with enough storage for n objects
  • that not necessarily contains n valid objects
								
									void make_great( int n, char buffer[n] ){

										const char * str = "C is great, long live C!";
										while (--n > 0 && *str){
											*buffer++ = *str++;
										}
										*buffer = '\0';
									
									}
								
							
VLA array syntax

Arrays, pointers and functions

Q: How to declare a function that takes an array or a pointer to object(s)?
If it's an array:
  • with enough storage for n objects
  • that's guaranteed to contain n valid objects
								
									void sum( size_t n, double numbers[static n] ){

										double sum = 0;
										for(size_t i = 0; i < n; ++i ){
											sum += numbers[i];
										}
										return sum;
									
									}
								
							
VLA array syntax with static qualifier

Pointers to single objects

Q: How to declare a function that takes an array or a pointer to object(s)?
A: If it's conceptually a single object and it the pointer can be NULL:
									
										time_t time( time_t * arg ){
											time_t result = /* ??? */ ;
											
											if (arg)
												*arg = result;
											
											return result;
										}
									
								
Just use a pointer

Pointers to single objects

Q: How to declare a function that takes an array or a pointer to object(s)?
A: If it's conceptually a single object and it must be valid:
									
										size_t strlen(const char str[static 1]){
											size_t len = 0;
											while( *str++ != '\0')
												++len;
											return len;
										}
									
								
Use [static 1] array notation

Array parameters with static sizes

								
									constexpr size_t N = {42};
									void func( Type array[static N] );

									void func( size_t n, Type array[static n] );
								
							
  • This syntax is only allowed in function prototypes
  • Works for sizes that are constant integer expressions or run-time values
  • Communicates the intent: func expects at least N (or n) valid objects
  • Compilers will often be able to check and emit a warning

Array parameters with static sizes

Compilers will sometimes be able to check and emit a warning
								
									pair_s solve_quadratic(double coeffs[static 3]);

									double c0[] = {1, 2};
									double* c1 = malloc(sizeof(double[2]));
								
									pair_s r = solve_quadratic(c0);
									pair_s r = solve_quadratic(c1);
								
							
These both trigger -Wstringop-overflow under GCC
The first one triggers -Warray-bounds under Clang

Array parameters with static sizes

Compilers will sometimes be able to check and emit a warning
								
									size_t strlen(const char str[static 1]);

									const char* str = nullptr;
									size_t len = strlen(str);
								
							
[GCC 11]: argument 1 to 'char[static 1]' is null where non-null expected [-Wnonnull]

Arrays, pointers and functions

What How Result
A single object,
can be null
											
												void func(T * obj);
											
										
func is responsible for checking obj
A single object,
must exist/ shouldn't be null
											
												void func(T obj[static 1]);
											
										
Compilers might emit a warning
Multiple objects,
possibly invalid/ non-existing
											
												void func(T arr[N]);

												void func(size_t n, T arr[n]);
											
										
Compilers might emit a warning
Multiple objects,
must exist/ shouldn't be NULL
											
												void func(T arr[static N]);
											
												void func(size_t n, T arr[static n]);
											
										
Compilers might emit a warning

Dynamic multidimensional arrays

Let's create a dynamic multidimensional array...
									
										double* kernel_gauss_create(size_t sz){
											double *pK = malloc(sizeof(double[sz][sz]));
											// ...
											return pK;
										}
									
								

Dynamic multidimensional arrays

Subscripting into a dynamic multidimensional array is an ugly business
								
									double* kernel_gauss_create(size_t sz){
	
										double *pK = malloc(sizeof(double[sz*sz]));
										if (!pK) return pK;
										
										for (size_t i=0; i < sz; ++i)
											for (size_t j=0; j < sz; ++j)
												*(pK + i*sizeof(double[sz]) + j) = _gcoeff(sz, i, j);
									
										return pK;
									}
								
							

Dynamic multidimensional arrays

We write:
								
									*(pK + i*sizeof(double[sz]) + j) = _gcoeff(sz, i, j);
								
							

We mean:
								
									pK[i][j] = _gcoeff(sz, i, j);
								
							

Dynamic multidimensional arrays, VLA way

The pointer-to-array declaration can contain variable size!
								
									double* kernel_gauss_create(size_t sz){
	
										double (*pK)[sz][sz] = malloc(sizeof(*pK));
										if (!pK) return NULL;
										
										for (size_t i=0; i < sz; ++i)
											for (size_t j=0; j < sz; ++j)
												(*pK)[i][j] = _gcoeff(sz, i, j);
									
										return (double *)pK;
									}
								
							

Dynamic multidimensional arrays, VLA way

If you don't like *pK...
								
									double* kernel_gauss_create(size_t sz){
	
										double (*pK)[sz] = malloc(sizeof(*pK) * sz);
										if (!pK) return NULL;
										
										for (size_t i=0; i < sz; ++i)
											for (size_t j=0; j < sz; ++j)
												pK[i][j] = _gcoeff(sz, i, j);
									
										return (double *)pK;
									}
								
							

Dynamic multidimensional arrays, VLA way

If you just fell in love with VLA syntax ...and want to show off
								
									double* kernel_gauss_create(size_t sz){
	
										double (*pK)[sz] = malloc(sizeof( typeof(*pK)[sz] ));
										if (!pK) return nullptr;
										
										for (size_t i=0; i < sz; ++i)
											for (size_t j=0; j < sz; ++j)
												pK[i][j] = _gcoeff(sz, i, j);
									
										return (double *)pK;
									}
								
							

Zeroing, re-assigning
& ephemeral lvalues

Back to brace-enclosed initializers

K&R C

								typedef struct point {
									int x;
									int y;
								} point_S;
								
								point_s makepoint(int x, int y) {
									point_s temp;
									temp.x = x;
									temp.y = y;
									return temp;
								}
								
								point_s p = makepoint(42, 24);
							
Up-to-date C Modestly up-to-date C

								typedef struct point {
									int x;
									int y;
								} point_s;
								
								point_s makepoint(int x, int y) {
									
									point_s tmp = {.x=x, .y=y};
									return tmp;
									
								}
								
								point_s p = makepoint(42, 24);
							

Back to brace-enclosed initializers

K&R C

								typedef struct point {
									int x;
									int y;
								} point_S;
								
								point_s makepoint(int x, int y) {
									point_s temp;
									temp.x = x;
									temp.y = y;
									return temp;
								}
								
								point_s p = makepoint(42, 24);
							
Modern C

								typedef struct point {
									int x;
									int y;
								} point_s;
								
								point_s makepoint(int x, int y) {
							
									return (point_s) {.x=x, .y=y};
									
								}
								
								point_s p = makepoint(42, 24);
							

This is known as compound literals

Compound literals 101

Compound literals are created with
								
									(Type){ /* initializer list */ }
								
							

Creates an unnamed object of type Type

Compound literals 101

  • static storage (for file-scope compound literals)
    								
    									static const double* default_coeffs = (double[]){0.12, 0.32, 0.32, 0.12};
    
    									void filter(...){}
    								
    							
  • automatic storage (for block-scope compound literals)
    								
    									typedef struct fir4{
    										const double* coeffs;
    										double buffer[4];
    									} fir4_s;
    									
    									
    									void filter(size_t n, double arr[n]){
    										fir4_s fir = { ... };
    										// ...
    										fir = (fir4_s){.coeffs=default_coeffs, .buffer={0}};
    										// ...
    									}
    								
    							

Compound literals 101 (storage)

									
										static const double* default_coeffs = 

										    (double[]){0.12, 0.32, 0.32, 0.12};

										void filter(size_t n, double arr[n]){
											size_t len = n * 2;
											fir4_s fir = { ... };
											// ...
											fir = (fir4_s){.coeffs=default_coeffs, 
												             .buffer={0}};
											// ...
										}
									
								

Compound literals 101

The unnamed object is an lvalue (its address can be taken)
								
									Type* ptr = &(Type){ /* initializer list */}
								
							
This enables surprising applications
									
										time_t time = mktime( &(struct tm){ .tm_year=2021, .tm_mon=6, .tm_mday=1, .tm_isdst=-1 } );
									
								

Compound literals vs. C++

Compound literals are not supported in ISO C++

They are not C++ temporaries

									
										time_t time = mktime( &(struct tm){ 
											.tm_year=2021, 
											.tm_mon=6, 
											.tm_mday=1, 
											.tm_isdst=-1 } 
										);
									
								
Valid C - taking an address
of a compound literal
									
										std::time_t time = std::mktime( &std::tm{ 
											.tm_mday=1, 
											.tm_mon=6, 
											.tm_year=2021, 
											.tm_isdst=-1 } 
										);
									
								
Invalid C++ - taking an address
of an rvalue (temporary)

Compound literals for initialization

Given a dynamic array structure
							
								typedef struct array {
									double* data;
									size_t capacity;
									size_t count;
								} array_s;
							
						
Write an initialization function
								
									array_s* array_init(array_s * pa, size_t capacity);
								
							

Compound literals for initialization

Good ol' C
							
							array_s* array_init(array_s * pa, size_t capacity){
								
								memset(pa, 0, sizeof(*pa));

								pa->data = calloc(capacity, sizeof(*pa->data));
								
								if (pa->data)
									pa->capacity = capacity;
								
								return pa;
							}		
							
						

Compound literal + designated initializers

(pa->count and pa->capacity are zeroed automatically)

Compound literals for initialization

Modern C
								
								array_s* array_init(array_s * pa, size_t capacity){
								
									*pa = (array_s){
										.data = calloc(capacity, sizeof(*pa->data))
									};
								
									if (pa->data)
										pa->capacity = capacity;
									
									return pa;
								}		
								
							

Compound literal + designated initializers

(pa->count and pa->capacity are zeroed automatically)

Compound literals for initialization

Modern C
								
								array_s* array_init(array_s pa[static 1], size_t capacity){
								
									*pa = (array_s){
										.data = calloc(capacity, sizeof(*pa->data))
									};
								
									if (pa->data)
										pa->capacity = capacity;
									
									return pa;
								}		
								
							

Compound literal + designated initializers

(pa->count and pa->capacity are zeroed automatically)

Compound literals for initialization

Good ol' C
							
							void array_free(array_s * pa){

								if (pa && pa->data){
									free(pa->data);
								}

								memset(pa, 0, sizeof(*pa));
								
								return pa;
							}	
							
						

Compound literal + designated initializers

All the *pa's data is zeroed.

Compilers will call memset for you.

Compound literals for initialization

Modern C
							
							void array_free(array_s pa[static 1]){

								if (pa && pa->data){
									free(pa->data);
								}

								*pa = (array_s){ 0 };
								
								return pa;
							}	
							
						

Compound literal + empty initialization

All the *pa's data is zeroed.

Compilers will call memset for you.

Compound literals as function arguments

							
								image_s* blur(image_s * img, size_t width, blur_type type, int cwh, _Bool in_place);
							
						
A bit ugly, isn't it?😓

Function arguments

							
								typedef struct blur_params {
									size_t width;
									blur_type type;
									int compute_hw;
									_Bool in_place;
								} blur_params_s;

								image_s* blur(image_s img[static 1], blur_params_s params[static 1]);
							
						
Much better!
								
									blur_params_s params = { .width=64, .type=box, .compute_hw=0, .in_place=true };
									blur(&img, &params);
								
							

But there's more!

The struct argument can be created in-place
								
									image_s* blur(image_s img[static 1], blur_params_s params[static 1]);

									blur(&img, &(blur_params_s){ .width=64, .type=box, .compute_hw=0, .in_place=true });
								
							

Structs == default arguments

Some arguments can be even skipped - because compound struct literals
								
									image_s* blur(image_s img[static 1], blur_params_s params[static 1]);
									
									blur(&img, &(blur_params_s){ .width=64, .type=box, .compute_hw=0, .in_place=true });
									
									blur(&img, &(blur_params_s){ .width=64, .in_place=true });
								
							
The omitted arguments will be empty-initialized

Structs == named arguments

Add a little macro magic to the mix
								
									blur( &img, .width=64, .in_place=true );
								
							
Not that much magic actually...
								
									image_s* blur_(image_s img[static 1], blur_params_s params[static 1]);

									#define blur(img, ...) blur_((img), &(blur_params_s){__VA_ARGS__})
								
							

Structs == default arguments

							
								image_s* blur_(image_s img[static 1], blur_params_s params[static 1]);

								#define blur(img, ...) blur_((img), &(blur_params_s){__VA_ARGS__})

								blur( &img, .width=64, .in_place=true );

							
						
  • blur macro takes an img
  • and a variable number of arguments hidden behind (...)
  • they are expanded in _blur function call with __VA_ARGS__
  • during expansion a blur_params_s object is created in-place using those arguments

Arbitrary default values

With little effort arbitrary default values are possible
								
									enum blur_type{ box, gauss };

									image_s* blur_(image_s img[static 1], blur_params_s params[static 1]);

									#define blur(img, ...) \
									        blur_((img), &(blur_params_s){.width=32, .type=gauss, __VA_ARGS__})
	
									blur( &img, .width=64, .in_place=true );
	
								
							
  • value of width is overridden from default 32 to 64
  • type defaults to gauss
  • this will trigger an initializer override warning

Arbitrary default values

The warning can be disabled temporarily
								
									image_s* _blur(image_s img[static 1], blur_params_s params[static 1]);
									#define DEF_ARGS_ON \
										_Pragma("GCC diagnostic push") \
										_Pragma("GCC diagnostic ignored \"-Woverride-init\"")

									#define DEF_ARGS_OFF \
										_Pragma("GCC diagnostic pop")

									#define blur(img, ...) \
										DEF_ARGS_ON \
										_blur((img), (blur_params_s){.width=64, .type=gauss, __VA_ARGS__}) \
										DEF_ARGS_OFF	
									
									blur( &img, .width=64, .in_place=true );
								
							
The _Pragma operator was introduced in C99 to allow #pragmas in macro expansion

Compound literals

  • Create unnamed objects with static or automatic storage duration
  • Are lvalues - you can safely take an address
  • Can be used to initialize structs and arrays in-place
    (compilers optimize them away, if needed)
  • Can be used as function arguments (either by-value or by-pointer)
  • Can enable features like default function arguments

Since we are talking about structs...
...and there is more to them...

less fragile dynamic memory management

Let's make a string

The string
										
											typedef struct string{
												size_t sz_arr;
												size_t length;
												char* arr;
											} string_s;

											// sizeof(string_s) == 24
										
									
It's memory layout

Let's make a string

							
								string_s* mk_string(const char str[static 1]){
									
									string_s* p = malloc(sizeof(*p));
									
									if (p){
										size_t len = strlen(str);
										*p = (string_s){
											.sz_arr = len + 1,
											.length = len,
											.arr = malloc(len + 1)
										};

										if (p->arr){
											memcpy(p->arr, str, len +1);
										}
										else{
											free(p);
											p = NULL;
										}
									}
									return p;
								}
							
						

Flexible array members

The string
							
								typedef struct string{
									size_t sz_arr;
									size_t length;
									char * arr;
								} string_s;

								// sizeof(string_s) == 24
							
						

Flexible array members

The string
							
								typedef struct string{
									size_t sz_arr;
									size_t length;
									char arr[];
								} string_s;

								// sizeof(string_s) == 16
							
						
  • struct string has now a flexible array member arr
  • a flexible array member must be the last member of a struct
  • struct string has an incomplete type
  • it cannot be a member of another struct

Flexible array members

The string
							
								typedef struct string{
									size_t sz_arr;
									size_t length;
									char arr[];
								} string_s;
							
						
								
									#define ARR_SZ (100)

									string_s names[ARR_SZ];
								
							
✘ arrays are fobidden
									
										struct person{
											string_s name;
											int id;
										};
									
								
✘ cannot be a member of another struct
									
										string_s str = {
											.sz_arr = 0, 
											.length = 0, 
											.arr = "alice"};
									
								
✘ direct initialization of the flexible array member

Back to the string

The string
									
										string_s* mk_string(const char str[static 1]){
											size_t len = strlen(str);

											string_s* p = malloc(sizeof(*p) +
											                     sizeof(char[len + 1]));
											
											if (p){
												*p = (string_s){.sz_arr = len  + 1, 
												                .length = len};

												memcpy(p->arr, str, len + 1);
											}
											return p;
										}
									
								
It's memory layout
  • Only 1 x malloc
  • No wasted space (and only 1 malloc space overhead)

Macro magic incoming...

Function overloading in C

aka generic selection

How to overload a function in C

Two simple data structures
							
								typedef struct Point2D {
									double x, y;
								} Point2D_s;
								
								typedef struct Circle {
									Point2D_s center;
									double radius;
								} Circle_s;
								
								typedef struct Rectangle {
									Point2D_s center;
									double w, h;
								} Rectangle_s;
							
						

How to overload a function in C

That need scaling
							
								void scale(Circle_s c[static 1], double scale){

									c->radius *= scale;
								
								}
								
								void scale(Rectangle_s r[static 1], double scale){
								
									r->w *= scale;
									r->h *= scale;
								
								}
							
						
A no-go in C

How to overload a function in C

That need scaling
							
								void scale_circ(Circle_s c[static 1], double scale){

									c->radius *= scale;
								
								}
								
								void scale_rect(Rectangle_s r[static 1], double scale){
								
									r->w *= scale;
									r->h *= scale;
								
								}
							
						
Different function names - not so handy...🤔

Say `Hello` to generic selection

Say `Hello` to generic selection

Say `Hello` to generic selection

Say `Hello` to generic selection

Say `Hello` to generic selection

									
										void scale_circ(Circle_s c[static 1], double scale);
										void scale_rect(Rectangle_s r[static 1], double scale);

										#define scale(obj, scale)      \
											_Generic( (obj),             \
												Rectangle_s* : scale_rect, \
												Circle_s* : scale_circ     \
											)                            \
											((obj), (scale))

										void func(){
											Rectangle rect;
											Circle circ;

											scale(&rect, 5.3);
											scale(&circ, 3.5);
										}
									
								

Say `Hello` to generic selection

So far so good, but what about...
...overloading on number of parameter
										
											void scale_circ_1p(Circle_s c[static 1], double scale);

											void scale_rect_1p(Rectangle_s r[static 1], double scale);
											void scale_rect_2p(Rectangle_s r[static 1], double w_scale, double h_scale);

											void func(){
												Rectangle rect1;
												Rectangle rect2;
	
												scale(&rect1, 5.3);
												scale(&rect2, 5.3, 3.5)
											}

										
									

Overloading on number of parameters

The bad news is: _Generic doesn't work with macros for expressions
The good news is:
										
											#define scale2p(obj, ...)         \
												_Generic( (obj),                \
													Rectangle_s* : scale_rect_2p  \
												)((obj), __VA_ARGS__)

											#define scale1p(obj, ...)         \
												_Generic( (obj),                \
													Rectangle_s* : scale_rect_1p, \
													Circle_s* : scale_circ_1p     \
												)((obj), __VA_ARGS__)
										
									
`...` stands for variable number of arguments

they are passed on with __VA_ARGS__

Overloading on number of parameters

This allows for a classic trick
										
											#define scale2p(obj, ...)         \
												_Generic( (obj),                \
													Rectangle_s* : scale_rect_2p  \
												)((obj), __VA_ARGS__)

											#define scale1p(obj, ...)         \
												_Generic( (obj),                \
													Rectangle_s* : scale_rect_1p, \
													Circle_s* : scale_circ_1p     \
												)((obj), __VA_ARGS__)

											#define INVOKE(_1, _2, _3, NAME, ...) NAME
											#define scale(...) INVOKE(__VA_ARGS__, scale2p, scale1p,)(__VA_ARGS__)
										
									

Generic selection

  • Makes writing type-generic macros easy
  • Can be nested for multiple selectors
  • Suffers from all the macro-shortcomings
    (e.g. an expression cannot be another macro)
  • Allows overloading both on type and number of arguments

Thank you, that's all!

  • Don't be afraid of modern C

  • Some features really boost productivity & readability
    (compound literals)

  • Some improve safety and help communicating intent
    (VLA pointers, pointer parameter syntax)

  • Some are scary
    (default function arguments through compound literals and macros)

  • Compilers understand modern C and will help you

Going modern C!

Going modern C!

						
							typedef struct vector {
								size_t capacity;
								size_t size;
								Type * data;
							} vector;
						
					
						
							int vec_create(vector ** vec, size_t cap){
								if (*vec != NULL)
									return -1;
								
								*vec = (vector *)malloc(sizeof(**vec));
								if (*vec == NULL)
									return -1;
								
								cap = cap == 0? DEF_CAP : cap;
								
								(*vec)->data = (Type *)malloc(cap * sizeof(Type));
								
								if ((*vec)->data == NULL){
									free(*vec);
									*vec = NULL;
									return -1;
								}
								
								(*vec)->capacity = cap;
								(*vec)->size = 0;
								return 1;
							}
						
					

Going modern C!

"Old" C

								vector * vec = NULL;
								if (vec_create(&vec, 8) >= 0){

									/* do something */
								
								}
							
Very up-to-date C

								vector * vec = {};
								if (vec_create(&vec, 8)){
								
									/* do something */
								
								}
							

Going modern C!

						
							typedef struct vector {
								size_t capacity;
								size_t size;
								Type * data;
							} vector;
						
					
						
							int vec_create(vector ** vec, size_t cap){
								if (*vec != NULL)
									return -1;
								
								*vec = (vector *)malloc(sizeof(**vec));
								if (*vec == NULL)
									return -1;
								
								cap = cap == 0? DEF_CAP : cap;
							
								(*vec)->data = (Type *)malloc(cap * sizeof(Type));
								
								if ((*vec)->data == NULL){
									free(*vec);
									*vec = NULL;
									return -1;
								}
								
								(*vec)->capacity = cap;
								(*vec)->size = 0;
								return 1;
							}
						
					

Going modern C!

						
							typedef struct vector {
								size_t capacity;
								size_t size;
								Type * data;
							} vector;
						
					
						
							bool vec_create(vector ** vec, size_t cap){
								if (*vec != NULL)
									return false;
								
								*vec = (vector *)malloc(sizeof(**vec));
								if (*vec == NULL)
									return false;
								
								cap = cap == 0? DEF_CAP : cap;
							
								(*vec)->data = (Type *)malloc(cap * sizeof(Type));
								
								if ((*vec)->data == NULL){
									free(*vec);
									*vec = NULL;
									return false;
								}
								
								(*vec)->capacity = cap;
								(*vec)->size = 0;
								return true;
							}
						
					

Going modern C!

						
							typedef struct vector {
								size_t capacity;
								size_t size;
								Type * data;
							} vector;
						
					
						
							bool vec_create(vector * vec[static 1], size_t cap){
								if (*vec != NULL)
									return false;
								
								*vec = (vector *)malloc(sizeof(**vec));
								if (*vec == NULL)
									return false;
								
								cap = cap == 0? DEF_CAP : cap;
							
								(*vec)->data = (Type *)malloc(cap * sizeof(Type));
								
								if ((*vec)->data == NULL){
									free(*vec);
									*vec = NULL;
									return false;
								}
								
								(*vec)->capacity = cap;
								(*vec)->size = 0;
								return true;
							}
						
					

Going modern C!

						
							typedef struct vector {
								size_t capacity;
								size_t size;
								Type * data;
							} vector;
						
					
						
							bool vec_create(vector * vec[static 1], size_t cap){
								if (*vec != nullptr)
									return false;
								
								*vec = (vector *)malloc(sizeof(**vec));
								if (*vec == nullptr)
									return false;
								
								cap = cap == 0? DEF_CAP : cap;
							
								(*vec)->data = (Type *)malloc(cap * sizeof(Type));
								
								if ((*vec)->data == nullptr){
									free(*vec);
									*vec = nullptr;
									return false;
								}
								
								(*vec)->capacity = cap;
								(*vec)->size = 0;
								return true;
							}
						
					

Going modern C!

						
							typedef struct vector {
								size_t capacity;
								size_t size;
								Type * data;
							} vector;
						
					
						
							bool vec_create(vector * vec[static 1], size_t cap){
								if (*vec != nullptr)
									return false;
								
								*vec = (vector *)malloc(sizeof(**vec));
								if (*vec == nullptr)
									return false;
								
								cap = cap == 0? DEF_CAP : cap;
							
								**vec = (vector){ .data = malloc(cap * sizeof(Type)),
																	.capacity = cap};
								
								if ((*vec)->data == nullptr){
									free(*vec);
									*vec = nullptr;
									return false;
								}
								
								
								return true;
							}
						
					

Going modern C!

						
							typedef struct vector {
								size_t capacity;
								size_t size;
								Type * data;
							} vector;
						
					
						
							bool vec_create(vector * vec[static 1], size_t cap){
								if (*vec != nullptr)
									return false;
								
								*vec = (vector *)malloc(sizeof(**vec));
								if (*vec == nullptr)
									return false;
								
								cap = cap == 0? DEF_CAP : cap;
							
								**vec = (vector){ .data = malloc(sizeof(Type[cap])),
																	.capacity = cap};
								
								if ((*vec)->data == nullptr){
									free(*vec);
									*vec = nullptr;
									return false;
								}
								
								return true;
							}
						
					

Going modern C!

						
							typedef struct vector {
								size_t capacity;
								size_t size;
								Type * data;
							} vector;
						
					
						
							bool vec_create(vector * vec[static 1], size_t cap){
								if (*vec != nullptr)
									return false;
								
								*vec = (vector *)malloc(sizeof(**vec));
								if (*vec == nullptr)
									return false;
								
								constexpr size_t DEF_CAP = 16;
								cap = cap == 0? DEF_CAP : cap;
							
								**vec = (vector){ .data = malloc(sizeof(Type[cap])),
																	.capacity = cap};
								
								if ((*vec)->data == nullptr){
									free(*vec);
									*vec = nullptr;
									return false;
								}
								
								return true;
							}
						
					

Going modern C!

						
							typedef struct vector {
								size_t capacity;
								size_t size;
								Type * data;
							} vector;
						
					
						
							bool vec_create(vector * vec[static 1], size_t cap){
								if (*vec != nullptr)
									return false;
								
								*vec = (vector *)malloc(sizeof(**vec));
								if (*vec == nullptr)
									return false;
								
								constexpr size_t DEF_CAP = 16;
								cap = cap == 0? DEF_CAP : cap;
							
								**vec = (vector){ .data = malloc(sizeof(Type[cap])),
																	.capacity = cap};
								
								if ((*vec)->data == nullptr){
									free(*vec);
									*vec = nullptr;
									return false;
								}
								
								return true;
							}
						
					

Going modern C!

						
							typedef struct vector {
								size_t capacity;
								size_t size;
								Type data[];
							} vector;
						
					
						
							bool vec_create(vector * vec[static 1], size_t cap){
								if (*vec != nullptr)
									return false;
								
								*vec = (vector *)malloc(sizeof(**vec));
								if (*vec == nullptr)
									return false;
								
								constexpr size_t DEF_CAP = 16;
								cap = cap == 0? DEF_CAP : cap;
							
								**vec = (vector){ .data = malloc(sizeof(Type[cap])),
																	.capacity = cap};
								
								if ((*vec)->data == nullptr){
									free(*vec);
									*vec = nullptr;
									return false;
								}
								
								return true;
							}
						
					

Going modern C!

						
							typedef struct vector {
								size_t capacity;
								size_t size;
								Type data[];
							} vector;
						
					
						
							bool vec_create(vector * vec[static 1], size_t cap){
								if (*vec != nullptr)
									return false;
								
								constexpr size_t DEF_CAP = 16;
								cap = cap == 0? DEF_CAP : cap;

								*vec = malloc(sizeof(**vec) + sizeof(Type[cap]));

								if (*vec != nullptr){
									**vec = (vector){ .capacity = cap };
								}
								
								return true;
							}
						
					

Going modern C!

						
							typedef struct vector {
								size_t capacity;
								size_t size;
								Type data[];
							} vector;
						
					
						
							bool vec_create(vector * vec[static 1], size_t cap){
								if (*vec != nullptr)
									return false;
								
								constexpr size_t DEF_CAP = 16;
								cap = cap == 0? DEF_CAP : cap;
	
								*vec = malloc(sizeof(**vec) + sizeof(Type[cap]));

								if (*vec != nullptr){
									**vec = (vector){ .capacity = cap };
								}
								
								return *vec != nullptr;
							}
						
					

Going modern C!

						
							typedef struct vector {
								size_t capacity;
								size_t size;
								Type data[];
							} vector;
						
					
						
							bool vec_create(vector * vec[static 1], size_t cap){
								if (*vec != nullptr)
									return false;
								
								constexpr size_t DEF_CAP = 16;
								cap = cap == 0? DEF_CAP : cap;
	
								*vec = malloc(sizeof(**vec) + 
												sizeof(Type[cap]));

								if (*vec != nullptr){
									**vec = (vector){ .capacity = cap };
								}
								
								return *vec != nullptr;
							}
						
					

Going modern C!

						
							typedef struct vector {
								size_t capacity;
								size_t size;
								Type data[];
							} vector;
						
					
						
							bool vec_create(vector * vec[static 1], size_t cap){
								if (*vec != nullptr)
									return false;
								
								constexpr size_t DEF_CAP = 16;
								cap = cap == 0? DEF_CAP : cap;

								*vec = malloc(sizeof(**vec) + 
											sizeof(typeof(*(*vec)->data)[cap]))

								if (*vec != nullptr){
									**vec = (vector){ .capacity = cap };
								}
								
								return *vec != nullptr;
							}
						
					

Going modern C!

						
							typedef struct vector {
								size_t capacity;
								size_t size;
								Type data[];
							} vector;
						
					
						
							typedef struct vc_args { size_t cap; } vc_args;

							bool vec_create(vector * vec[static 1], vc_args args){
								if (*vec != nullptr)
									return false;
								
								constexpr size_t DEF_CAP = 16;
								cap = cap == 0? DEF_CAP : cap;

								*vec = malloc(sizeof(**vec) + 
											sizeof(typeof(*(*vec)->data)[args.cap]))

								if (*vec != nullptr){
									**vec = (vector){ .capacity = args.cap };
								}
								
								return *vec != nullptr;
							}
						
					

Going modern C!

						
							typedef struct vector {
								size_t capacity;
								size_t size;
								Type data[];
							} vector;
						
					
						
							typedef struct vc_args { size_t cap; } vc_args;

							bool vec_create(vector * vec[static 1], vc_args args){
								if (*vec != nullptr)
									return false;
								
								constexpr size_t DEF_CAP = 16;
								cap = cap == 0? DEF_CAP : cap;

								*vec = malloc(sizeof(**vec) + 
											sizeof(typeof(*(*vec)->data)[args.cap]))

								if (*vec != nullptr){
									**vec = (vector){ .capacity = args.cap };
								}
								
								return *vec != nullptr;
							}

							#define vec_create(vec, ...) \
									vec_create(vec, (vc_args){.cap=16 __VA_OPT__(,) __VA_ARGS__ } )

						
					

Going modern C!

					
						typedef struct vector {
							size_t capacity;
							size_t size;
							Type data[];
						} vector;
					
				
					
						typedef struct vc_args { size_t cap; } vc_args;

						bool vec_create(vector * vec[static 1], vc_args args){
							if (*vec != nullptr)
								return false;
							
							assert(args.cap > 0 && "Capacity must be greater than 0");

							*vec = malloc(sizeof(**vec) + 
										sizeof(typeof(*(*vec)->data)[args.cap]))

							if (*vec != nullptr){
								**vec = (vector){ .capacity = args.cap };
							}
							
							return *vec != nullptr;
						}

						#define vec_create(vec, ...) \
								vec_create(vec, (vc_args){.cap=16 __VA_OPT__(,) __VA_ARGS__ } )

					
				

Thank you, that's all!