Home | Manual | Static Analysis | Playground
Cake - C23 and Beyond
- Using cake
- C89
-
C99
- C99 restrict pointers
- C99 Variably-Modified (VM) types
- C99 Flexible array members
- C99 static and type qualifiers in parameter array declarators
- C99 Complex and imaginary support
- C99 Universal character names (\u and \U)
- C99 Hexadecimal floating constants
- C99 Compound literals
- C99 Designated initializers
- C99 Line comments
- Declarations in for loop initializers
- C99 inline functions
- C99 _Pragma preprocessing operator
- C99 __func__ predefined identifier
- C99 Variadic macros
- C99 _Bool
- C11
-
C23
- C23 Variably-Modified (VM) types mandatory
- C23 _Decimal32, _Decimal64, and _Decimal128
- C23 static_assert / single-argument static_assert
- C23 u8 character prefix
- C23 No function declarators without prototypes
- C23 Improved Tag Compatibility
- C23 Unnamed parameters in function definitions
- C23 Digit separators
- C23 Binary literals
- C23 Introduce the nullptr constant
- C23 Make false and true first-class language features
- C23 {} empty initializer
- C23 auto
- C23 typeof / typeof_unqual
- C23 Improved Normal Enumerations
- C23 constexpr
- C23 Enhancements to Enumerations
- C23 [[Attributes]]
- C23 [[fallthrough]] attribute
- C23 [[deprecated]] attribute
- C23 [[maybe_unused]] attribute
- C23 [[nodiscard]] attribute
- C23 [[unsequenced]] and [[reproducible]]
- C23 __has_attribute
- C23 __has_include
- C23 #warning
- C23 #embed
- C23 #elifdef #elifndef
- C23 __VA_OPT__
- C23 BitInt(N)
- C23 Compound Literals with storage specifier
- C2Y
- Cake Extensions
- GCC extensions
- MSVC extensions
Using cake
Cake works as an extension for MSVC on Windows and as an extension for GCC on Linux. This approach makes Cake useful in real and existing programs.
When applicable, Cake uses the same command line options of MSVC and GCC.
Include directories
Include directories are specified in cakeconf.h file.
On Windows, to manually discover which directories are included, you can run at Visual Studio command prompt the command:
echo %INCLUDE%
To find out what are the directories used by GCC type:
echo | gcc -E -Wp,-v -
Sample of cakeconf.h
#ifdef __linux__
/*
To find the include directories used by GCC type:
echo | gcc -E -Wp,-v -
*/
#pragma dir "/usr/lib/gcc/x86_64-linux-gnu/11/include"
#pragma dir "/usr/local/include"
#pragma dir "/usr/include/x86_64-linux-gnu"
#pragma dir "/usr/include"
#endif
#ifdef _WIN32
/*
To find the include directories used by MSVC,
open Visual Studio Developer Command prompt and type:
echo %INCLUDE%.
Running Cake inside MSVC command prompt uses %INCLUDE% automatically.
*/
#pragma dir "C:/Program Files/Microsoft Visual Studio/2022/Professional/VC/Tools/MSVC/14.38.33130/include"
#pragma dir "C:/Program Files/Microsoft Visual Studio/2022/Professional/VC/Tools/MSVC/14.38.33130/ATLMFC/include"
#pragma dir "C:/Program Files/Microsoft Visual Studio/2022/Professional/VC/Auxiliary/VS/include"
#pragma dir "C:/Program Files (x86)/Windows Kits/10/include/10.0.22000.0/ucrt"
#pragma dir "C:/Program Files (x86)/Windows Kits/10/include/10.0.22000.0/um"
#pragma dir "C:/Program Files (x86)/Windows Kits/10/include/10.0.22000.0/shared"
#pragma dir "C:/Program Files (x86)/Windows Kits/10/include/10.0.22000.0/winrt"
#pragma dir "C:/Program Files (x86)/Windows Kits/10/include/10.0.22000.0/cppwinrt"
#pragma dir "C:/Program Files (x86)/Windows Kits/NETFXSDK/4.8/include/um"
#endif
The command line cake -autoconfig generates the cake config file.
We can have a cakeconf.h per project and call a more generic cakeconf.h for system includes.
Sample:
yourproject\cakeconf.h
//system includes...etc
#include "C:\Program Files (x86)\cake\cakeconf.h"
//project extra includes
#pragma dir ".\openssl\include"
Command line
cake [options] source1.c source2.c ...
SAMPLES
cake source.c
Compiles source.c and outputs /[default-target]/source.c
cake -target=X86_msvc source.c
Compiles source.c and outputs C89 code at /X86_msvc/source.c
cake file.c -o file.cc && cl file.cc
Compiles file.c and outputs file.cc then use cl to compile file.cc
Options
-I(same as GCC and MSVC) Adds a directory to the list of directories searched for include files-no-outputCake will not generate output-D(same as GCC and MSVC) Defines a preprocessing symbol for a source file-E(same as GCC and MSVC) Copies preprocessor output to standard output-o name.c(same as GCC and MSVC) Defines the output name, when we compile a single file-dump-tokensOutput tokens before preprocessor-Wnumber -Wno-numberEnables or disable warnings. See warnings-disable-assertDisable cake extension where assert is a statement.-H(same as gcc, /showIncludes in MSVC) Causes the compiler to output a list of the include files.-preprocess-def-macropreprocess def macros after expansion-WallEnables all warnings-sarifGenerates sarif files. Sarif Visual Studio plugin https://marketplace.visualstudio.com/items?itemName=WDGIS.MicrosoftSarifViewer-sarif-pathSpecifies the Sarif output dir. "Visual Studio -> External Tools"-Wstyle -msvc-output -no-output -sarif -sarif-path "$(SolutionDir).sarif" $(ItemPath)-line-directivesEmmits #line directives-targetDefines how the source code is interpreted (integers sizes, align etc) and specifies the C89 output that is compatible with the target compiler. Options: x86x64gcc, x86msvc, x64msvc, catalina, ccu8-msvc-outputOutput is compatible with Visual Studio IDE.-fdiagnostics-color=never(same as GCC) Output will not use colors-fanalyzerruns cake flow analysis-auto-configGenerates cakeconf.h header (see includes)-style=nameSet the style used in (w011) style warnings. Options are-style=cake,-style=gnu,-style=microsoft-comment-to-attrConverts at the preprocessor phase, comment like this/*w12*/to attributes[[cake::w12]]-const-literalMakes the compiler handle string literals as const char[] rather than char[].
Output
The current backend generates C89-compatible code, which can be pipelined with existing compilers to produce executables. You can create your own C compiler backend.
The output is a simplified version with some K & R and C89.
Using C89 as base:
- no preprocessor
- no typedefs
- no enums
- no const
- no constant expressions
- no switch
- no nested structs/unions
- no sizeof
- no local static variables
- arrays[size], size is always given and it is integer
- function prototypes are generated
For instance:
cake c:\project\file1.c
output:
c:\project
├── file1.c
├── target
├── file1.c
More files..
cake c:\project\file1.c c:\project\other\file2.c
output
c:\project
├── file1.c
├── other
│ ├── file2.c
├── target
├── file1.c
├── other
├── file2.c
C89
https://nvlpubs.nist.gov/nistpubs/Legacy/FIPS/fipspub160.pdf
C99
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n325.pdf https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf
#define __STDC_VERSION__ 199901L //C99
C99 restrict pointers
void f(const char* restrict s);
int main(){
f("");
}
Currently restrict is being removed on the generated code.
C99 Variably-Modified (VM) types
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n683.htm
In C99/C23, there are two related but distinct concepts:
- VLA objects (
int a[n]) - a local array whose storage is allocated on the stack at runtime. Optional in C11/C23 (controlled by__STDC_NO_VLA__). - VM types (
int (*p)[n]) - any type derived from a runtime-sized array, including pointers to VLAs. Mandatory in C23 even when__STDC_NO_VLA__is defined.
Cake supports VM types and converts them to C89-compatible code. Cake does not support VLA objects.
VM type pointer declarations
#include <stdlib.h>
#include <stdio.h>
int main() {
int n = 2;
int m = 3;
int (*p)[n][m] = malloc(sizeof *p);
printf("%zu\n", sizeof(*p));
free(p);
}
C99 Flexible array members
#include <stdio.h>
#include <stdlib.h>
struct X {
int count;
double values[]; // flexible array
};
/*
The size of a structure with a flexible array member is
determined as if the flexible array member were omitted,
EXCEPT that it may have more trailing padding than the
omission would imply
*/
int main() {
int n = 3;
printf("sizeof(struct X) = %d\\n", (int) sizeof(struct X));
printf("allocated = %d\\n", (int) sizeof(struct X) + n * sizeof(double));
struct X* p = malloc(sizeof(struct X) + n * sizeof(double));
if (p == NULL) return 0;
p->count = n;
p->values[0] = 10.0;
p->values[1] = 20.0;
p->values[2] = 30.0;
for (int i = 0; i < p->count; ++i)
printf("%f\\n", p->values[i]);
free(p);
return 0;
}
C99 static and type qualifiers in parameter array declarators
#include <stdlib.h>
void F(int a[static 5]) {
}
int main()
{
F(0);
F(NULL);
F(nullptr);
int a[] = {1, 2, 3};
F(a);//error
int b[] = { 1, 2, 3 , 4, 5};
F(b);
int c[] = { 1, 2, 3 , 4, 5, 6};
F(c);
}
Cake will perform the same checks regardless of the static keyword.
C99 Complex and imaginary support
Not implemented
C99 Universal character names (\u and \U)
Not implemented
C99 Hexadecimal floating constants
double d = 0x1p+1;
Cake converts hexadecimal floating-point values to decimal floating-point representation using strtod followed by snprintf. This conversion may introduce precision loss.
0x1.234p1 means:
2 3 4
r1= 1 + --- + --- + --- = 1.1376953125
1 2 3
16 16 16
1
1.1376953125 x 2 = 2.275390625 (final number)
C99 Compound literals
struct s {
int i;
};
int f(void) {
struct s * p = 0, * q;
int j = 0;
again:
q = p, p = & ((struct s) { j++ });
if (j < 2) goto again;
return p == q && q -> i == 1;
}
N716 https://www.open-std.org/jtc1/sc22/wg14/www/docs/n716.htm
C99 Designated initializers
int main()
{
int a[6] = {[4] = 29, [2] = 15 };
struct point { int x, y; };
struct point p = { .y = 2, .x = 3 };
}
N494 https://www.open-std.org/jtc1/sc22/wg14/www/docs/n494.pdf
C99 Line comments
//line comments
Declarations in for loop initializers
int main()
{
const int max = 10;
for (int n = max - 1; n >= 0; n--)
{
// body of loop
}
}
C99 inline functions
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n709.htm (30 May 1997) https://www.open-std.org/jtc1/sc22/wg14/www/docs/n741.htm
inline int sum(int a, int b)
{
return a + b;
}
int main(void)
{
int r = sum(1, 2);
}
C99 _Pragma preprocessing operator
// Use -E option
//6.10.11 Pragma operator
#define LISTING(x) PRAGMA(listing on #x)
#define PRAGMA(x) _Pragma(#x)
LISTING (..listing.dir)
C99 __func__ predefined identifier
N611 13 Sep 96 Mooney, __FUNC__
#include <stdio.h>
int main()
{
printf("%s\n", __func__);
printf("%s\n", __func__);
}
C99 Variadic macros
#include <stdio.h>
#define debug(...) fprintf(stderr, __VA_ARGS__)
int main()
{
int x = 1;
debug("X = %d\n", 1);
}
N707 https://www.open-std.org/jtc1/sc22/wg14/www/docs/n707.htm
C99 _Bool
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n815.htm (1998)
int main(void)
{
_Bool b = 1;
return 0;
}
C11
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2310.pdf
#define __STDC_VERSION__ 201112L //C11
C11 _Static_assert
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1330.pdf
int main()
{
_Static_assert(1 == 1, "error");
}
_Static_assert became static_assert in C23.
C11 Anonymous structures and unions
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1406.pdf
struct v {
union { /* anonymous union*/
struct { int i, j; }; /* anonymous structure*/
struct { long k, l; } w;
};
int m;
} v1;
int main(){
v1.i = 2; /* valid*/
v1.w.k = 5; /* valid*/
}
C11 _Noreturn
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1478.htm
_Noreturn void f () {
abort(); // ok
}
_Noreturn became [[noreturn]] in C23.
C11 Thread_local/Atomic
Thread_local uses __declspec(thread) in MSVC output and __thread with GCC output.
Atomic - not implemented
C11 type-generic expressions (_Generic)
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1441.htm
#include <math.h>
#define cbrt(X) _Generic((X), \
double: cbrtl, \
float: cbrtf , \
default: cbrtl \
)(X)
int main(void)
{
cbrt(1.0);
}
C11 u' ' U' ' character constants
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1326.pdf
int i = U'ç';
int i2 = u'ç';
Important: Cake assume source is utf8 encoded.
C11 u8"literals"
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1488.htm
char * s1 = u8"maçã";
char * s2 = u8"maca";
Important: Cake assume source is utf8 encoded.
C11 _Alignof or C23 alignof
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1397.htm
int main()
{
int align = alignof(int);
}
_Alignof became alignof in C23.
C11 _Alignas or C23 alignas
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1335.pdf
Uses __declspec(align(n)) in MSVC output and __attribute__((aligned(n))) in GCC output.
C23
https://open-std.org/JTC1/SC22/WG14/www/docs/n3096.pdf
#define __STDC_VERSION__ 201710L //C17 (Cake accepts C17 source)
#define __STDC_VERSION__ 202311L //C23
C23 Variably-Modified (VM) types mandatory
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2778.pdf
C23 formally separates two concepts that were bundled together in C99:
- VLA objects (
int a[n]) - arrays with automatic storage duration whose size is determined at runtime. These remain optional in C23. An implementation that defines__STDC_NO_VLA__does not support them. - VM types (
int (*p)[n]) - variably-modified types: any type derived from a runtime-sized array dimension, including pointers to VLAs. These are mandatory in C23. Every conforming implementation must support them regardless of__STDC_NO_VLA__.
The rationale (N2778, Martin Uecker): VM types encode array bounds in the type system, allowing compilers to detect out-of-bounds accesses at compile time or at runtime. They are a form of dependent type and their implementation cost is much lower than stack-allocated VLAs.
/* VM type - mandatory in C23, supported by Cake */
void foo(int n, double (*x)[n])
{
(*x)[0] = 1.0; /* bounds visible in the type */
}
In C23, __STDC_NO_VLA__ only indicates that VLA objects with automatic storage
duration are not supported. It does not mean VM types are unavailable.
Cake reflects this split exactly:
- VLA object declarations produce a compile-time error with a suggestion to use a VM type pointer instead.
- VM type pointers, parameters, and
sizeofexpressions are fully supported and translated to C89-compatible code.
See the C99 VM types section for full details and C89 output examples.
C23 _Decimal32, _Decimal64, and _Decimal128
Not implemented. https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1107.htm
C23 static_assert / single-argument static_assert
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1330.pdf
int main(void)
{
static_assert(1 == 2);
}
C23 u8 character prefix
https://open-std.org/JTC1/SC22/WG14/www/docs/n2418.pdf
int main(){
unsigned char c = u8'~';
}
C23 No function declarators without prototypes
https://www.open-std.org/JTC1/SC22/WG14/www/docs/n2841.htm
int main(){
func(); //this is an error in C23
}
See also Remove support for function definitions with identifier lists
https://open-std.org/JTC1/SC22/WG14/www/docs/n2432.pdf
C23 Improved Tag Compatibility
Not implemented yet.
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3037.pdf
struct foo { int a; } p;
void bar(void)
{
struct foo { int a; } q;
q = p;
}
C23 Unnamed parameters in function definitions
https://open-std.org/JTC1/SC22/WG14/www/docs/n2480.pdf
int f(int );
int f(int ) {
}
Missing a dummy name when generating c89.
C23 Digit separators
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2626.pdf
int main()
{
int a = 1000'00;
}
C23 Binary literals
#define X 0b1010
int main()
{
int a = X;
int b = 0B1010;
}
C23 Introduce the nullptr constant
https://open-std.org/JTC1/SC22/WG14/www/docs/n3042.htm
int main()
{
void * p = nullptr;
auto p2 = nullptr;
typeof(nullptr) p3 = nullptr;
}
C23 Make false and true first-class language features
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2935.pdf
#if true
#warning yes..
#endif
int main()
{
bool b = true;
}
C23 {} empty initializer
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2900.htm https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3011.htm
int main()
{
struct X {
int i;
} x = {};
x = (struct X) {};
struct Y
{
struct X x;
} y = { {} };
}
C23 auto
https://open-std.org/JTC1/SC22/WG14/www/docs/n3007.htm
static auto a = 3.5;
auto p = &a;
double A[3] = { 0 };
auto pA = A;
auto qA = &A;
C23 typeof / typeof_unqual
https://open-std.org/JTC1/SC22/WG14/www/docs/n2927.htm https://open-std.org/JTC1/SC22/WG14/www/docs/n2930.pdf
#define SWAP(a, b) \
do {\
typeof(a) temp = a; a = b; b = temp; \
} while (0)
int main()
{
/*simple case*/
int a = 1;
typeof(a) b = 1;
/*pay attention to the pointer*/
typeof(int*) p1, p2;
/*let's expand this macro and see inside*/
SWAP(a, b);
/*for anonymous structs we insert a tag*/
struct { int i; } x;
typeof(x) x2;
typeof(x) x3;
/*Things get a little more complicated*/
int *array[2];
typeof(array) a1, a2;
typeof(array) a3[3];
typeof(array) *a4[4];
/*abstract declarator*/
int k = sizeof(typeof(array));
/*new way to declare pointer to functions?*/
typeof(void (int)) * pf = nullptr;
}
C23 Improved Normal Enumerations
TODO
https://open-std.org/JTC1/SC22/WG14/www/docs/n3029.htm
enum a {
a0 = 0xFFFFFFFFFFFFFFFFULL
};
static_assert(_Generic(a0,
unsigned long long: 0,
int: 1,
default: 2 == 0));
The type of the enum must be adjusted.
C23 constexpr
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3018.htm
#include <stdio.h>
constexpr int c = 123;
constexpr int c2 = c + 1000;
int a[c];
constexpr double PI = 3.14;
static_assert(PI + 1 == 3.14 + 1.0);
struct Y {
int a;
int ar[3];
int b;
};
void T3()
{
constexpr struct Y y = { .ar[1] = 2, 3, 4 };
static_assert(y.a == 0);
static_assert(y.ar[0] == 0);
static_assert(y.ar[1] == 2);
static_assert(y.ar[2] == 3);
static_assert(y.b == 4);
static_assert(y.ar[1] + y.ar[2] == 5);
}
static_assert("abc"[0] == 'a');
int main()
{
constexpr char ch = 'a';
printf("%f %c", PI, ch);
}
C23 Enhancements to Enumerations
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3030.htm
enum X : short {
A
};
int main() {
enum X x = A;
}
C23 [[Attributes]]
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2335.pdf https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2554.pdf
C23 [[fallthrough]] attribute
TODO
https://open-std.org/JTC1/SC22/WG14/www/docs/n2408.pdf
void g(){}
void h(){}
void i(){}
void f(int n) {
void g(void), h(void), i(void);
switch (n) {
case 1: /* diagnostic on fallthrough discouraged */
case 2:
g();
[[fallthrough]];
case 3: /* diagnostic on fallthrough discouraged */
h();
case 4: /* fallthrough diagnostic encouraged */
i();
//[[fallthrough]]; /* constraint violation */
}
}
C23 [[deprecated]] attribute
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2334.pdf
// Compile with -w03
[[deprecated]] void f2() {}
struct [[deprecated]] S { int a;};
enum [[deprecated]] E1 { one };
int main(void) {
struct S s;
enum E1 e;
f2();
}
C23 [[maybe_unused]] attribute
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2270.pdf
//
// Compile with -w02 -w06
//
void f( [[maybe_unused]] int arg1, int arg2) //warning C0006: 'arg2': unreferenced formal parameter
{
[[maybe_unused]] int local1;
int local2; //warning C0002: 'local2': unreferenced declarator
}
C23 [[nodiscard]] attribute
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2267.pdf
https://open-std.org/JTC1/SC22/WG14/www/docs/n2448.pdf
#include <stdlib.h>
struct [[nodiscard]] error_info { int error; };
struct error_info enable_missile_safety_mode(void);
void launch_missiles(void);
void test_missiles(void) {
enable_missile_safety_mode();
launch_missiles();
}
[[nodiscard("must check armed state")]]
bool arm_detonator(int within);
void detonate();
void call(void) {
arm_detonator(3);
detonate();
}
Cake implementation does not yet support the optional message argument of [[nodiscard("message")]].
C23 [[unsequenced]] and [[reproducible]]
TODO
https://open-std.org/JTC1/SC22/WG14/www/docs/n2956.htm
typedef double f_t [[reproducible]] (double); // invalid, applies to identifier f_t
typedef double g_t(double) [[reproducible]]; // valid, applies to type
extern g_t f [[unsequenced]]; // invalid, applies to identifier f
extern typeof(double(double)) [[unsequenced]] g; // valid, applies to type specifier
extern g_t [[unsequenced]] g; // valid, applies to type specifier
C23 __has_attribute
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2799.pdf
#if __has_c_attribute(fallthrough)
#warning YES
#else
#warning NO
#endif
C23 __has_include
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2799.pdf
#if __has_include(<stdio.h>)
#warning YES
#endif
#if __has_include(<any.h>)
#warning YES
#else
#warning NO
#endif
C23 #warning
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2686.pdf
int main()
{
#warning my warning message
}
C23 #embed
Partially implemented.
#include <stdio.h>
int main()
{
static const char file_txt[] = {
#embed "stdio.h"
,0
};
printf("%s\n", file_txt);
}
C23 #elifdef #elifndef
#define Y
#ifdef X
#define VERSION 1
#elifdef Y
#define VERSION 2
#else
#define VERSION 3
#endif
C23 __VA_OPT__
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3033.htm
#define F(...) f(0 __VA_OPT__(,) __VA_ARGS__)
#define G(X, ...) f(0, X __VA_OPT__(,) __VA_ARGS__)
#define SDEF(sname, ...) S sname __VA_OPT__(= { __VA_ARGS__ })
#define EMP
void f(int i, ...) {}
int main()
{
int a = 1;
int b = 2;
int c = 3;
F(a, b, c);
F();
F(EMP);
G(a, b, c);
G(a, );
G(a);
}
C23 BitInt(N)
TODO https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2763.pdf
int main()
{
_BitInt(2) a2 = 1;
_BitInt(3) a3 = 2;
_BitInt(33) a33 = 1;
char c = 3;
}
C23 Compound Literals with storage specifier
void F(int *p){}
int main()
{
F((static int []){1, 2, 3, 0});
}
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3038.htm
C2Y
C2Y Obsolete implicitly octal literals
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3353.htm
static_assert(0o52 == 052);
static_assert(0O52 == 052);
static_assert(0O52 == 42);
int main()
{
int i = 0o52;
}
C2Y case range expressions
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3370.htm
#include <stdio.h>
void f(int n)
{
switch (n)
{
case 1 ... 10:
printf("n in range 1...10\n");
break;
default:
break;
}
}
int main(){
f(1);
f(11);
}
```
### C2Y #def
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3524.txt
```c
//Use -E
#def foo(x)
do {
bar(x);
baz(x);
}
while (0)
#enddef
foo(1)
foo(2)
It may not be part of C2Y, but it is implemented in Cake while we wait to see whether we will keep it as an extension or remove it.
C2Y _Countof operator
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3369.pdf
void f(int n)
{
int v[123][n];
static_assert(_Countof(v) == 123);
}
int main()
{
int a[7][3];
int n = _Countof(a);
static_assert(_Countof(a) == 7);
int n2 = _Countof(int [7][3]);
static_assert(_Countof(int [2][3]) == 2);
}
Obs: Cake extends countof to enums, returning the number of enumerators. (this is not part of C2Y) It can be a new keyword in cake.
#include <string.h>
enum E { A, B, C, D, E, F };
void f(enum E e)
{
switch (e)
{
case A:
break;
case B:
break;
default:
static_assert(_Countof(e) == 6);
}
}
enum E parse_enum_e(const char* s)
{
if (strcmp(s, "A") == 0) return A;
if (strcmp(s, "B") == 0) return B;
if (strcmp(s, "C") == 0) return C;
if (strcmp(s, "D") == 0) return D;
if (strcmp(s, "E") == 0) return E;
if (strcmp(s, "F") == 0) return F;
static_assert(_Countof(enum E) == 6);
return A;
}
int main() { }
C2Y defer
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3734.pdf
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3733.htm
// 12 EXAMPLE 1 Defer statements cannot be jumped over.
#include <stdio.h>
int main()
{
goto target; // constraint violation
_Defer { fputs(" meow", stdout); }
target:
fputs("cat says", stdout);
return 1;
}
/*g*/
#include <stdio.h>
int main()
{
// print "cat says" to standard output
return fputs("cat says", stdout);
_Defer { fputs(" meow", stdout); } // okay: no constraint violation,
// not executed
}
/*h*/
#include <stdio.h>
int main()
{
goto target;
{
// okay: no constraint violation
_Defer { fputs(" meow", stdout); }
}
target:
fputs("cat says", stdout);
return 1; // prints "cat says" to standard output
}
/*i*/
#include <stdio.h>
int main()
{
{
_Defer { fputs("cat says", stdout); }
// okay: no constraint violation
goto target;
}
target:
fputs(" meow", stdout);
return 1; // prints "cat says meow" to standard output
}
/*j*/
#include <stdio.h>
int main()
{
_Defer {
goto target; // constraint violation
fputs(" meow", stdout);
}
target:
fputs("cat says", stdout);
return 1;
}
/*k*/
#include <stdio.h>
int main()
{
_Defer {
return 5; // constraint violation
fputs(" meow", stdout);
}
fputs("cat says", stdout);
return 1;
}
/*l*/
#include <stdio.h>
int main()
{
_Defer
{
target:
fputs(" meow", stdout);
}
goto target; // constraint violation
fputs("cat says", stdout);
return 1;
}
/*m*/
#include <stdio.h>
int main()
{
goto target; // okay: no constraint violation
{
target:
_Defer { fputs("cat says", stdout); }
}
fputs(" meow", stdout);
return 1; // prints "cat says meow" to standard output
}
/*n*/
#include <stdio.h>
int main()
{
goto target; // constraint violation!!
{
_Defer {fputs(" meow", stdout); }
target:
}
fputs("cat says", stdout);
return 1;
}
/*o*/
#include <stdio.h>
int main()
{
{
_Defer fputs("cat says", stdout);
goto target;
}
target:;
fputs(" meow", stdout);
return 1; // prints "cat says meow"
}
/*p*/
#include <stdio.h>
int main()
{
{
goto target;
_Defer fputs(" meow", stdout);
}
target:;
fputs("cat says", stdout);
return 1; // prints "cat says"
}
/*q*/
#include <stdio.h>
int main()
{
{
_Defer { fputs(" meow", stdout); }
target:
}
goto target; // constraint violation !!
fputs("cat says", stdout);
return 1;
}
/*r*/
#include <stdio.h>
int main()
{
{
target:
_Defer { fputs("cat says", stdout); }
}
goto target; // ok
fputs(" meow\n", stdout);
return 1; // prints "cat says" repeatedly
}
/*s*/
#include <stdio.h>
int main()
{
{
target:
_Defer { fputs("cat says", stdout); }
goto target; // ok
}
// never reached
fputs(" meow", stdout);
return 1; // prints "cat says" repeatedly
}
/*t*/
#include <stdio.h>
int main()
{
int count = 0;
{
target:
_Defer { fputs("cat says ", stdout); }
++count;
if (count <= 2) {
goto target; // ok
}
}
fputs("meow", stdout);
return 1; // prints "cat says cat says cat says meow"
}
/*u*/
#include <stdio.h>
int main()
{
int count = 0;
{
_Defer { fputs("cat says", stdout); }
target:
if (count < 5) {
++count;
goto target; // ok
}
}
fputs(" meow", stdout);
return 1; // prints "cat says meow"
}
/*v*/
#include <stdio.h>
int main()
{
int count = 0;
target:
if (count >= 2) {
fputs("meow", stdout);
return 1; // prints "cat says cat says meow "
}
_Defer { fputs("cat says ", stdout); }
count++;
goto target;
return 0; // never reached
}
#include <stdio.h>
/*
13 EXAMPLE 2 All the expressions and statements of an
enclosing block are evaluated before executing defer
statements, including any conversions. After all defer
statements are executed, the block is then exited.
*/
int main()
{
int r = 4;
int* p = &r;
_Defer { *p = 5; }
return *p; // return 4;
}
#include <stddef.h>
#include <stdlib.h>
int use_buffer(size_t n, void* buf)
{
/* ... */
return 0;
}
int main()
{
const int size = 20;
void* buf = malloc(size);
_Defer { free(buf); }
// buffer is not freed until AFTER use_buffer returns
return use_buffer(size, buf);
}
/*
14 EXAMPLE 3 It is not defined if defer statements execute
their deferred blocks if the exiting / non- returning
functions detailed previously are called.
*/
#include <stdlib.h>
int f()
{
void* p = malloc(1);
if (p == NULL) {
return 0;
}
_Defer free(p);
exit(1); // "p" may be leaked
return 1;
}
/*
15 EXAMPLE 4 Defer statements, when execution reaches them,
are tied to the scope of the defer statement within their
enclosing block, even if it is a secondary block without
braces.
*/
#include <stdio.h>
#include <stdlib.h>
int main()
{
{
_Defer { fputs(" meow", stdout); }
if (true) _Defer fputs("cat", stdout);
fputs(" says", stdout);
}
// "cat says meow" is printed to standard output
exit(0);
}
/*
16 This applies to any enclosing block, even for loops
without braces around its body.
*/
#include <stdio.h>
#include <stdlib.h>
int main() {
const char* arr[] = {"cat", "kitty", "ferocious little baby"};
_Defer { fputs(" meow", stdout); }
for (unsigned int i = 0; i < 3; ++i) _Defer printf("my %s,\n", arr[i]);
fputs("says", stdout);
// "my cat,
// my kitty,
// my ferocious little baby,
// says meow"
// is printed to standard output
return 0;
}
/*
17 EXAMPLE 5 Defer statements execute their deferred blocks
in reverse order of the appearance of the defer statements,
and nested defer statements execute their deferred blocks
in reverse order but at the end of the deferred block they
were invoked within. The following program:
*/
int main() {
int r = 0;
{
_Defer {
_Defer r *= 4;
r *= 2;
_Defer { r += 3; }
}
_Defer r += 1;
}
return r; // return 20;
}
/*
18 EXAMPLE 6 Defer statements can be executed within a
switch, but a switch cannot be used to jump into the scope
of a defer statement.
*/
#include <stdlib.h>
int main()
{
void* p = malloc(1);
switch (1) {
_Defer free(p); // constraint violation
default:
_Defer free(p);
break;
}
return 0;
}
/*
19 EXAMPLE 7 Defer statements can not be exited by means
of break or continue
*/
int main() {
switch (1) {
default:
_Defer {
break; // constraint violation
}
}
for (;;) {
_Defer {
break; // constraint violation
}
}
for (;;) {
_Defer {
continue; // constraint violation
}
}
return 0;
}
/*
20 EXAMPLE 8 Defer statements that are not reached are
not executed.
*/
#include <stdlib.h>
int main() {
void* p = malloc(1);
return 0;
_Defer free(p); // not executed, p is leaked
}
/*
21 EXAMPLE 9 Defer statements can contain other
compound statements.
*/
typedef struct meow* handle;
extern int purr(handle* h);
extern void un_purr(handle h);
int main()
{
handle h;
int err = purr(&h);
_Defer if (!err) un_purr(h);
return 0;
}
C2Y if declarations, v4
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3388.htm
#include <stdio.h>
int main()
{
int size = 10;
if (FILE* f = fopen("file.txt", "r"); f)
{
/*...*/
fclose(f);
}
if (FILE* f = fopen("file.txt", "r"))
{
/*...*/
fclose(f);
}
}
C2Y typename on _Generic
This feature was created in Cake and now it is part of C2Y!
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3260.pdf
int main()
{
const int * const p;
static_assert(_Generic(p, const int *: 1));
/*extension*/
static_assert(_Generic(int, int : 1));
static_assert(_Generic(typeof(p), const int * const: 1));
}
C2Y __COUNTER__
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3457.htm
/*
Compile with -E
*/
#define X(Z) Z Z
X(__COUNTER__) // 0 0
X(__COUNTER__) // 1 1
C2Y Local functions
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3678.pdf
int main()
{
static int dup(int a) { return a * 2; }
return dup(1);
}
C2Y Function Literals
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3679.pdf
#include <stdio.h>
int main()
{
printf("%d", (int (void)){
return 1;
}());
}
C2Y Statement expressions
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3643.htm
#include <stdio.h>
#define maxint(a,b) \
({int _a = (a), _b = (b); _a > _b ? _a : _b; })
int main()
{
printf("%d", maxint(1, 2));
}
C2Y Elvis operator ?:
https://www.open-std.org/JTC1/SC22/WG14/www/docs/n3804.txt
The elvis operator is a shorthand for the ternary operator where the middle operand is omitted. When the condition is true, the condition's own value is returned - evaluated only once.
a ?: b
is equivalent to a ? a : b, except a is evaluated exactly once.
Basic usage
#include <stdio.h>
int main()
{
int x = 0;
int y = 5;
/* returns y because x is 0 (falsy) */
int r1 = x ?: y;
printf("%d\n", r1); /* 5 */
/* returns x because x is non-zero */
x = 3;
int r2 = x ?: y;
printf("%d\n", r2); /* 3 */
}
Pointer fallback
The most common use case - return a pointer if non-null, otherwise a default:
#include <stdio.h>
const char *get_name(void);
int main()
{
const char *name = get_name();
const char *display = name ?: "unknown";
printf("%s\n", display);
}
Constant expressions
The elvis operator works in constant expressions:
static_assert(1 ?: 0 == 1);
static_assert(0 ?: 1 == 1);
enum { DEFAULT = 0 ?: 42 };
Side effects - condition evaluated once
When the condition has side effects, it is guaranteed to be evaluated exactly once. Cake introduces a temporary variable in the C89 output to ensure this:
int i = 0;
int b = 10;
int r = i++ ?: b;
/* i is now 1, r is 10 (i++ yielded 0, which is falsy) */
C89 output:
int __v0;
__v0 = i++;
int r = __v0 ? __v0 : b;
Nested elvis
Elvis associates right-to-left like the ternary operator:
int a = 0, b = 0, c = 7;
int r = a ?: b ?: c; /* 7 */
Cake Extensions
Pre-defined macros in Cake
#define __CAKE__ 202311L
#define __STDC_VERSION__ 202311L
assert built-in
In cake assert is an built-in function. The reason is because it works as tips for flow analysis.
For instance, in a linked list when head is null tail is also null,
and tail->next always points to null.
Assertion will check these properties in runtime and also make the static analysis assume that assert evaluates to true.
void list_push_back(struct list* list,
struct item* _Owner p_item)
{
if (list->head == NULL) {
list->head = p_item;
}
else {
assert(list->tail != nullptr);
assert(list->tail->next == nullptr);
list->tail->next = p_item;
}
list->tail = p_item;
}
However, assert is not a "blind override command." In situations like:
int i = 0;
assert(i != 0);
In situations where static analysis can identify two or more possible states, assert works as a state selector, similar to what happens in if statements but without the scope.
void f(int * _Opt p)
{
if (p != NULL) {
//p is not null here...
}
}
void f2(int * _Opt p)
{
assert(p != NULL);
//we assume p is not null here...
}
try { throw; } catch {}
try-statement:
try secondary-block
try secondary-block catch secondary-block
jump-statement:
throw;
try catch is a external block that we can jump off.
try catch is a LOCAL jump this is on purpose not a limitation.
catch block is optional.
extern int error;
int main()
{
try
{
for (int i = 0 ; i < 10; i++)
{
for (int j = 0 ; j < 10; j++)
{
/*...*/
if (error) throw;
/*...*/
}
}
}
catch
{
}
}
checked expressions
Syntax
checked-expression:
assignment-expression
assignment-expression !
The checked expression evaluates its operand expression. If the result compares equal to zero, we jump to a catch block. Otherwise, the value of the operand is returned unchanged.
This operator can be applied to any scalar expression, including integers and pointers.
int f();
int* get_ptr();
int main()
{
try {
int i = f()!;
int *p = get_ptr()!;
int a = 1, b = 0;
int x = (a + b)!;
}
catch {
}
}
#pragma safety enable
void* _Owner _Opt malloc(unsigned long size);
void free(void* _Owner _Opt ptr);
int main() {
try
{
void * _Owner p = malloc(1)!;
free(p);
}
catch{
}
}
#pragma dir
#pragma dir "C:/Program Files (x86)/Windows Kits/10//include/10.0.22000.0/cppwinrt"
Add the path to the list of directory paths used to seach include files.
offsetof
In cake offset (https://en.cppreference.com/w/cpp/types/offsetof.html) is an operator
#include <stdio.h>
struct S
{
char m0;
double m1;
short m2;
char m3;
};
int main()
{
printf("offset of char m0 = %zu", offsetof(struct S, m0));
printf("offset of char m0 = %zu", offsetof(struct S, m1));
printf("offset of char m0 = %zu", offsetof(struct S, m2));
printf("offset of char m0 = %zu", offsetof(struct S, m3));
}
Type traits
We have some compile time functions to infer properties of types.
_is_char()
The three types char, signed char, and unsigned char are collectively called the character types.
_is_pointer
Pointer to object or function
_is_array
Array type
_is_function
A function type describes a function with specified return type.
_is_floating_point
float, double, and long double return true
_is_integral
The standard signed integer types and standard unsigned integer types are collectively called the
standard integer types;
_is_arithmetic
Integer and floating types are collectively called arithmetic types.
_is_scalar
Arithmetic types, pointer types, and the nullptr_t type are collectively called scalar types
Note: Type traits that can be easily created with _Generic will be removed. _
Extension - Object lifetime checks
See ownership
_Owner
_Opt
_View
GCC extensions
- __builtin_va_list
- __builtin_c23_va_start
- __builtin_va_start
- __builtin_va_end
- __builtin_va_arg
- __builtin_va_copy
- __builtin_offsetof (same as cake offsetof)
- __attribute__
- __typeof__ alias same as typeof
Other builtins are declared at \src\include\x86_x64_gcc_builtins.h
Pre-defined macros for GCC compatibility https://gcc.gnu.org/onlinedocs/cpp/Predefined-Macros.html
See \src\include\x86_x64_gcc_builtins.h
MSVC extensions
- __ptr32, __ptr64
- __int8 ... __int64
- __declspec
- __cdecl
- __fastcall, __stdcall
- __forceinline alias for inline in cake
- __pragma
- __unaligned
Pre-defined macros for MSVC compatibility https://learn.microsoft.com/en-us/cpp/preprocessor/predefined-macros?view=msvc-170#standard-predefined-macros
See \src\include\x86_msvc_macros.h and \src\include\x64_msvc_macros.h