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.
For static analyzer concepts of ownership and nullable pointers visit ownership
On Windows, Cake can be used on the command line similarly to MSVC.
Cake reads the INCLUDE
variable, the same variable used by MSVC to locate the include directories.
Additionally, you can run Cake outside the Visual Studio command prompt by placing the file cakeconfig.h
in
the same directory or above the source files, and specifying the directories using #pragma dir.
If Cake doesn't find cakeconfig.h
in the local directories, it will try to locate it in the
same path as the Cake executable.
The -autoconfig option generates the cakeconfig.h
automatically on both Windows and Linux.
To manually discover which directories are included, you can run the command:
echo %INCLUDE%
at Visual Studio command prompt.
To find out what are the directories used by GCC type:
echo | gcc -E -Wp,-v -
Sample of cakeconfig.h
#ifdef __linux__
/*
To find the include directories used my 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 my MSVC,
open Visual Studio Developer Commmand prompt and type:
echo %INCLUDE%.
Running Cake inside mscv 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
Sample, project cakeconfig.h
//system includes...etc
#include "C:\Program Files (x86)\cake\cakeconfig.h"
//project extra includes
#pragma dir ".\openssl\include"
cake [options] source1.c source2.c ...
SAMPLES
cake source.c
Compiles source.c and outputs /out/source.c
cake -target=C11 source.c
Compiles source.c and outputs C11 code at /out/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
cake file.c -direct-compilation -o file.cc && cl file.cc
Compiles file.c and outputs file.cc for direct compilation then use cl to compile file.cc
Adds a directory to the list of directories searched for include files
Cake will not generate output
Defines a preprocessing symbol for a source file
Copies preprocessor output to standard output
Defines the output name. used when we compile one file
Output tokens before preprocessor
Enables or disable warnings. See warnings
disable cake extension where assert is an statement. See extensions
Causes the compiler to output a list of the include files. The option also displays nested include files, that is, the files included by the files that you include.
Enables all warnings
Generates sarif files. Sarif Visual Studio plugin https://marketplace.visualstudio.com/items?itemName=WDGIS.MicrosoftSarifViewer
Specifies the Sarif output dir.
Inside "Visual Studio -> External Tools" this command can be used for static analysis.
`-Wstyle -msvc-output -no-output -sarif -sarif-path "$(SolutionDir).sarif" $(ItemPath)´
Output is compatible with visual studio IDE. We can click on the error message and IDE selects the line.
This option enables an static analysis of program flow. This is required for some ownership checks
Generates cakeconfig.h header.
On Windows, it must be generated inside the Visual Studio Command Prompt to read the INCLUDE variable. On Linux, it calls GCC with echo | gcc -v -E - 2>&1 and reads the output.
One directory called out is created keeping the same directory structure of the input files.
For instance:
cake c:\project\file1.c
output:
c:\project
├── file1.c
├── out
├── file1.c
More files..
cake c:\project\file1.c c:\project\other\file2.c
output
c:\project
├── file1.c
├── other
│ ├── file2.c
├── out
├── file1.c
├── other
├── file2.c
#define __CAKE__ 202311L
#define __STDC_VERSION__ 202311L
#define __STDC_OWNERSHIP__ 1
The define STDC_OWNERSHIP indicates that the compiler suports owneship checks
https://gcc.gnu.org/onlinedocs/cpp/Predefined-Macros.html
not implemented yet
C89 https://port70.net/~nsz/c/c89/c89-draft.html
Implemented. C89 backend. enum specifiers become the corresponding type and the enumerators becomes constants. See C23 enum
Implemented. C89 backend removes const.
In C23 the old K&R was removed.
C99 https://open-std.org/JTC1/SC22/WG14/www/docs/n1124.pdf
#define __STDC_VERSION__ 199901L //C99
void f(const char* restrict s);
Backend is removing restrict this. (TODO It will be added with a compiler flag)
N448
#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);
}
Becomes C89 (not implemented)
#include <stdlib.h>
#include <stdio.h>
int main() {
int n = 2;
int m = 3;
/*these variables are created to store the dynamic size*/
const int vla_1_n = n;
const int vla_1_m = m;
int (*p)[n][m] = malloc((vla_1_n*vla_1_m)*sizeof(int));
printf("%zu\n", (vla_1_n*vla_1_m)*sizeof(int));
free(p);
}
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n683.htm
struct s {
int n;
double d[];
};
Becomes (not implemented)
struct s {
int n;
double d[1];
};
//TODO sizeof
#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 verifies that the argument is an array with at least the specified number of elements. It extends this check to arrays without a static size as well.
Not implemented
Not implemented
double d = 0x1p+1;
Becomes in C89
double d = 2.000000;
Cake converts hexadecimal floating-point values to decimal floating-point representation using strtod followed by snprintf. This conversion may introduce precision loss.
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;
}
Becomes in C89
struct s {
int i;
};
int f(void) {
struct s * p = 0, * q;
int j = 0;
again:
struct s compound_literal_1 = { j++ };
q = p, p = & compound_literal_1;
if (j < 2) goto again;
return p == q && q -> i == 1;
}
N716 https://www.open-std.org/jtc1/sc22/wg14/www/docs/n716.htm
int main()
{
int a[6] = {[4] = 29, [2] = 15 };
struct point { int x, y; };
struct point p = { .y = 2, .x = 3 }
}
Becomes C89
int main()
{
int a[6] = { 0, 0, 15, 0, 29, 0 };
struct point { int x, y; };
struct point p = { 3, 2 }
}
N494 https://www.open-std.org/jtc1/sc22/wg14/www/docs/n494.pdf
Implemented.
C89 backend n/a.
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n741.htm
Not implemented. TODO!
Parsed. C89 not implemented yet. TODO!
#include <stdio.h>
#define debug(...) fprintf(stderr, __VA_ARGS__)
int main()
{
int x = 1;
debug("X = %d\n", 1);
}
C89 backend n/a
N707 https://www.open-std.org/jtc1/sc22/wg14/www/docs/n707.htm
When compiling to C89 _Bool is replaced by unsigned char.
//line comments
int main(void)
{
_Bool b = 1;
return 0;
}
Becomes in C89
/*line comments*/
int main(void)
{
unsigned char b = 123;
return 0;
}
TODO. Conversion to 1 or 0 at backend.
#define __STDC_VERSION__ 201112L //C11
https://open-std.org/JTC1/SC22/WG14/www/docs/n1570.pdf
https://files.lhmouse.com/standards/ISO%20C%20N2176.pdf
Implemented. C89 backend n/a
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*/
}
C89 backend, names are generated for the anonymous parts.
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1406.pdf
_Noreturn void f () {
abort(); // ok
}
C89 backend it is removed.
Parsed but not transformed. C89 backend TODO.
Implemented
N1441 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);
}
C89 backend will have the selected expression.
int i = U'ç';
int i2 = u'ç';
C89 backend
int i = 231u;
int i2 = ((unsigned short)231);
Important: Cake assume source is utf8 encoded.
u8 literals are converted to escape sequences.
char * s1 = u8"maçã";
char * s2 = u8"maca";
C89 backend
char * s1 = "ma\xc3\xa7\xc3\xa3";
char * s2 = "maca";
N1488 https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1488.htm
Important: Cake assume source is utf8 encoded.
int main()
{
int align = alignof(int);
}
The C89 backend replaces by its constant value.
int main()
{
int align = 4;
}
Not implemented.
https://open-std.org/JTC1/SC22/WG14/www/docs/n3096.pdf
#define __STDC_VERSION__ 201710L //C17
#define __STDC_VERSION__ 202311L //C23
Not implemented. https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1107.htm
Implemented. C89 backend n/a
N1330 https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1330.pdf
Implemented. https://open-std.org/JTC1/SC22/WG14/www/docs/n2418.pdf
int main(){
unsigned char c = u8'~';
}
C89 backend
int main(){
unsigned char c = ((unsigned char)'~');
}
Implemented. 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
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;
}
Becomes < C23
struct foo { int a; } p;
void bar(void)
{
struct foo q;
q = p;
}
int f(int );
int f(int ) {
}
https://open-std.org/JTC1/SC22/WG14/www/docs/n2480.pdf
C89 backened. We should add a dummy name when generating - Not implemented yet.
int main()
{
int a = 1000'00;
}
C89 backend
int main()
{
int a = 100000;
}
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2626.pdf
#define X 0b1010
int main()
{
int a = X;
int b = 0B1010;
}
C89 backend it will be decimal
int main()
{
int a = 10;
int b = 10;
}
int main()
{
void * p = nullptr;
auto p2 = nullptr;
typeof(nullptr) p3 = nullptr;
}
C89 backend converts to ((void*)0)
int main()
{
void * p = ((void*)0);
void * p2 = ((void*)0);
void * p3 = ((void*)0);
}
https://open-std.org/JTC1/SC22/WG14/www/docs/n3042.htm
Implemented
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2935.pdf
C89 backend. bool specifier is replaced by unsigned char; true by the constant 1 ; false by the constant 0.
int main()
{
struct X {
int i;
} x = {};
x = (struct X) {};
struct Y
{
struct X x;
} y = { {} };
}
C89 backend
int main()
{
struct X {
int i;
} x = {0};
x = (struct X) {0};
struct Y
{
struct X x;
} y = { {0} };
}
Note: Cake code is 100% equivalent because it does not make padding bit zero.
static auto a = 3.5;
auto p = &a;
double A[3] = { 0 };
auto pA = A;
auto qA = &A;
C89 backend the final type is used.
static double a = 3.5;
double * p = &a;
double A[3] = { 0 };
double * pA = A;
double (* qA)[3] = &A;
https://open-std.org/JTC1/SC22/WG14/www/docs/n3007.htm
#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 = NULL;
}
C89 backend
struct _struct_tag_0 {
int i;
};
int (* g1)(int a);
int (* g2)(int a);
int *(* f3)(int a);
int f()
{
return 1;
}
void f4(int a[2])
{
int * p;
}
int main()
{
int a = 1;
int b = 1;
int * p1;
int * p2;
int * p3;
int * p4;
int p5;
int * p6;
do
{
int temp = a;
a = b;
b = temp;
}
while (0);
struct _struct_tag_0 x;
struct _struct_tag_0 x2;
struct _struct_tag_0 x3;
int *array[2];
int *a1[2];
int *a2[2];
int *a3[3][2];
int *(*a4[4])[2];
int k = sizeof (int *[2]);
void (* pf)(int) = ((void *)0);
}
int f5()
{
int (* p1)[2] = 0;
int (* p2)[2] = (int (*)[2])p1;
}
https://open-std.org/JTC1/SC22/WG14/www/docs/n2927.htm https://open-std.org/JTC1/SC22/WG14/www/docs/n2930.pdf
//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.
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3018.htm
Cake convert constexpr declarator with a cast and the value. addressof constexpr declarator is not implemented.
#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);
}
C89 backend will replace by constants in places where a constant expression is required.
struct Y {
int a;
int ar[3];
int b;
};
int c = 123;
int c2 = c + 1000;
int a[123];
double PI = 3.140000;
void T3()
{
struct Y y = {0, 0, 2, 3, 4};
}
int printf(char * format, ...);
int main()
{
char ch = 97;
printf("%f %c", PI, ch);
}
TODO: Maybe suffix like ULL etc makes the code easier to read.
enum X : short {
A
};
int main() {
enum X x = A;
}
C89 backend.
enum X {
A
};
int main() {
short x = ((short)A);
}
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3030.htm
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2335.pdf https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2554.pdf
Related: Standard Attributes in C and C++ - Timur Doumler - ACCU 2023 https://youtu.be/EpAEFjbTh3I
Not implemented C89 backend - n/a
https://open-std.org/JTC1/SC22/WG14/www/docs/n2408.pdf
Partially implemented https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2334.pdf C89 backend - n/a
Implemented https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2270.pdf
C89 backend - n/a
Partially implemented
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2267.pdf
https://open-std.org/JTC1/SC22/WG14/www/docs/n2448.pdf
C89 backend - n/a
//TODO
https://open-std.org/JTC1/SC22/WG14/www/docs/n2956.htm
Implemented C89 backend - n/a
#if __has_include(<stdio.h>)
#warning YES
#endif
#if __has_include(<any.h>)
#warning YES
#else
#warning NO
#endif
Implemented C89 backend - n/a
Implemented
int main()
{
#warning my warning message
}
Implemented
C89 backend - n/a
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2686.pdf
Partially implemented.
#include <stdio.h>
int main()
{
static const char file_txt[] = {
#embed "stdio.h"
,0
};
printf("%s\n", file_txt);
}
C89 backend. TODO fix bug.
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3017.htm
#include <stdio.h>
int main()
{
static const char file_txt[] = {
35,112,114,/*lot more here ...*/ 13,10
,0
};
printf("%s\n", file_txt);
}
Implemented
#define Y
#ifdef X
#define VERSION 1
#elifdef Y
#define VERSION 2
#else
#define VERSION 3
#endif
C89 backend - n/a
Implemented.
#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);
}
C89 backend - n/a
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3033.htm
Not implemented
Not implemented yet.
void F(int *p){}
int main()
{
F((static int []){1, 2, 3, 0})
}
Becomes (not implemented yet)
void F(int *p){}
int main()
{
static int _compound_1[] = {1, 2, 3, 0};
F(_compound_1);
x }
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3038.htm
Not implemented https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2778.pdf
static_assert(0o52 == 052);
static_assert(0O52 == 052);
static_assert(0O52 == 42);
int main()
{
int i = 0o52;
}
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3353.htm
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3199.htm
defer will call the defer statement before the block exit at inverse order of declaration.
defer-statement:
defer secondary-block
For instance:
#include <stdio.h>
int main() {
do {
FILE* f = fopen("in.txt", "r");
if (f == NULL) break;
defer fclose(f);
FILE* f2 = fopen("out.txt", "w");
if (f2 == NULL) break;
defer fclose(f2);
//...
}
while(0);
}
C89 backend
int *fopen(char * filename, char * mode);
int fclose(int * stream);
int main()
{
do
{
int * f = fopen("in.txt", "r");
if (f == ((void *)0))
{
break;
}
int * f2 = fopen("out.txt", "w");
if (f2 == ((void *)0))
{
fclose(f);
break;
}
fclose(f2);
fclose(f);
}
while (0);
}
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3196.htm
#include <stdio.h>
int main()
{
int size = 10;
if (FILE* f = fopen("file.txt", "r"); f)
{
/*...*/
fclose(f);
}
}
C89 backend
int *fopen(char * filename, char * mode);
int fclose(int * stream);
int main()
{
int size = 10;
{
int * f = fopen("file.txt", "r");
if (f)
{
fclose(f);
}
}
}
C++ proposal https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0305r0.html
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));
}
C89 backend see _Generic.
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.
try
{
for (int i = 0 ; i < 10; i++) {
for (int j = 0 ; j < 10; j++) {
...
if (error) throw;
...
}
}
}
catch
{
}
Lambdas without capture where implemented using a compound literal syntax. Since, we cannot have compound literal of function types (only pointer to function) the syntax can be reused.
For instance:
extern char* strdup(const char* s);
void create_app(const char* appname)
{
struct capture {
char * name;
} capture = { .name = strdup(appname) };
(void (void* p)) {
struct capture* capture = p;
}(&capture);
}
C89 backend
struct capture {
char * name;
};
extern char *strdup(char * s);
static void _lambda_0(void * p)
{
struct capture * capture = p;
}
void create_app(char * appname)
{
struct capture capture = {0};
capture.name = strdup(appname);
_lambda_0(&capture);
}
Code generation is already working, but static analysis is not implemented.
#pragma dir "C:/Program Files (x86)/Windows Kits/10//include/10.0.22000.0/cppwinrt"
pragma dir makes the preprocessor include the directory when searching for includes.
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. _
See ownership
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...
}