You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The C Development Kit (CDK) is a collection of lightweight, MIT-licensed libraries to make C development on Linux simpler and more consistent.
3
+
Wouldn't it be awesome if we would have errors with messages, try, catche and even backtraces in C?
4
+
Wouldn't it be even more awesome if such library wouldn't be bloated with savejumps mallocs and similiar monsrotities?
4
5
5
-
C Development Kit: Error is an error component. It works much like `errno`, but adds richer context: not just an error code, but also a message and a lightweight backtrace showing where the error has been passed along. All of this comes without any heap allocations, making it safe and fast even for embedded or low-overhead systems.
6
+
It fact it would, so i created this simple lib. It allow using errno alike mechanism but with additionall string messages, backtraces and try-catch mechanism. All in form of one header file. Feel free to just drop the lib into your project to add simple error stack to your lib.
6
7
7
-
What is nice about `cdk_errno` is that on fast path it is as fast as normal `errno`, so if no error occured in the function there is no penalty hit. The difference show only on slow path so when error occurs:
8
-
```
9
-
❯ ./build/example/bench
10
-
5-lvl errno-trace avg: 32.8 ns
11
-
5-lvl fmt errno-trace avg: (disabled by CDK_ERROR_OPTIMIZE)
12
-
5-lvl int avg: 5.7 ns
8
+
Usage example:
9
+
```c
10
+
#include<cdk_error.h>
11
+
12
+
cdk_error_tbar(void) {
13
+
cdk_errno = cdk_errnos(EINVAL, "Something went wrong in bar");
14
+
return cdk_errno;
15
+
}
16
+
17
+
void bar_cleanup(void) {}
18
+
19
+
cdk_error_t foo(void) {
20
+
cdk_errno = bar();
21
+
CDK_TRY(cdk_errno);
22
+
23
+
return 0;
24
+
25
+
error_out:
26
+
return cdk_errno;
27
+
}
28
+
29
+
int main(void) {
30
+
char buf[2048];
31
+
32
+
cdk_errno = bar();
33
+
CDK_TRY(cdk_errno);
34
+
35
+
cdk_errno = foo();
36
+
CDK_TRY_CATCH(cdk_errno, error_bar_cleanup);
37
+
38
+
return 0;
39
+
40
+
error_bar_cleanup:
41
+
bar_cleanup();
42
+
error_out:
43
+
cdk_error_dumps(cdk_errno, sizeof(buf), buf);
44
+
return cdk_errno->code;
45
+
}
13
46
```
14
47
15
-
With `CDK_ERROR_OPTIMIZE` enabled `cdk_error` is like 6-8 times slower than normal errno. This performence hit is perfectly accaptable in most cases, because there will be plenty of other app critical processes wich will slow you more than this 32ns overhead.
48
+
## Architecture overview
49
+
50
+
We use few simple ideas in the library.
51
+
52
+
First: to do not require lock we use _Thread_local storage, which allow use to drop locking because each thread has it's own error stack in the moment it is created.
16
53
17
-
---
54
+
Second: there is `struct Error` which describe generic error and it can be used in three different modes. We do it in such a way because if you want the mosy speed on error you do not want to use additional string hence int error type, we also have str type and fstr type, int is the fastest formatted string is the slowest.
18
55
19
-
## 📦 Getting started
56
+
Third: We gather backtrace manually, the gather is hidden behind CDK_TRY and CDK_TRY_CATCH so user do not need to remember about it.
20
57
21
-
The library is header-only. To use it, copy the single header file into **your own project or library**. Each project should keep its own copy to avoid sharing one global error state across unrelated code.
58
+
Fourth: all code is in one header file which make it adjusted to be dropped in the app or in the lib. This way ecery lib or app has it's own error stack separeated from the rest of the system. Because of that we try to make `struct Error` as tiny as possible, on my amd64 pc it has 664 bytes. If you need to make it smaller compile with `-DCDK_ERROR_OPTIMIZE=1`, on my amd64 machine this bring single error object to 48 bytes.
22
59
23
-
Create a small wrapper header and add one `.c` file that defines the thread-local variables:
60
+
## Getting started
24
61
62
+
The library is header only. To use it, copy the single header file into your own project or library. By default library has enabled errno mechanism so you will need two definitions for errno, `cdk_errno` and `cdk_hidden_errno`:
Every other file in your project just includes `myerror.h`.
46
-
The `.c` file is required, because it provides the actual definitions of the thread-local globals; without it you’d only have declarations, and the linker would complain.
83
+
Every file in your project includes `myerror.h`.
47
84
48
85
🔧 If you’d like to change the prefix (for example, from `cdk_` to `my_`), there’s a helper script:
This produces a copy of the header with your own prefix, ready to drop into a project.
58
-
59
-
---
60
-
61
-
## ❓ Why copy instead of link?
94
+
## Why copy instead of link?
62
95
63
96
Unlike traditional libraries, `cdk_error` is designed to be embedded into each project separately. The reason is simple: every library or program should have its **own private error state**.
64
97
65
-
If multiple components linked against the same global `cdk_errno`, their errors could overwrite each other, leading to confusing or incorrect traces. By copying the header into your project (and optionally renaming the prefix), you guarantee isolation: errors raised inside your library stay inside your library, and don’t clash with others.
66
-
67
-
---
68
-
69
-
## 💡 Usage
70
-
71
-
Here’s the simplest way to use the errno-style API:
72
-
73
-
```c
74
-
#include<stdio.h>
75
-
#include"myerror.h"
76
-
77
-
constchar *nested_failing_func(void) {
78
-
if (1) {
79
-
cdk_errno = cdk_errnos(ENOBUFS, "My error");
80
-
return NULL;
81
-
}
82
-
return "All good";
83
-
}
84
-
85
-
int failing_func(void) {
86
-
const char *s = nested_failing_func();
87
-
if (!s) {
88
-
return cdk_ereturn(-1);
89
-
}
90
-
return 13;
91
-
}
92
-
93
-
void api_entry(void) {
94
-
int res = failing_func();
95
-
if (res < 0) {
96
-
cdk_ewrap();
97
-
return;
98
-
}
99
-
}
100
-
101
-
int main(void) {
102
-
char buf[1024];
103
-
cdk_errno = 0;
104
-
105
-
api_entry();
106
-
if (cdk_errno) {
107
-
cdk_edumps(sizeof(buf), buf);
108
-
printf("%s", buf);
109
-
return -1;
110
-
}
111
-
return 0;
112
-
}
113
-
```
114
-
115
-
The pattern is straightforward: when something fails, set `cdk_errno` with a code or message, return an error value, and wrap it if you need to add another frame. At the top level, dump the error to a buffer or file to see the full trace.
116
-
117
-
---
118
-
119
-
### ⚡ Performance
120
-
121
-
One of the nice things about `cdk_errno` is that on the **fast path** it behaves just like plain `errno`: if no error occurs in a function, there’s no extra overhead at all.
122
-
The difference only shows up on the **slow path**, when an error is actually created and propagated:
123
-
124
-
```
125
-
❯ ./build/example/bench
126
-
5-lvl errno-trace avg: 32.8 ns
127
-
5-lvl fmt errno-trace avg: (disabled by CDK_ERROR_OPTIMIZE)
128
-
5-lvl int avg: 5.7 ns
129
-
```
130
-
131
-
With `CDK_ERROR_OPTIMIZE` enabled (which removes formatted errors), the library is about **6–8× slower than a plain `errno` write** when an error occurs.
132
-
That sounds big, but remember: it’s \~32 nanoseconds to build a full 5-level trace. In practice this overhead is negligible compared to real application work (I/O, syscalls, allocations, etc.).
133
-
134
-
In other words: you get structured errors and backtraces “for free” in the common case, and only pay a small price when something actually goes wrong.
135
-
136
-
---
137
-
138
-
139
-
## 🛠️ Building examples and tests
140
98
141
-
This project uses [Meson](https://mesonbuild.com/) for its build system. To build with examples and tests enabled, run:
142
-
143
-
```sh
144
-
meson setup build -Dtests=true -Dexamples=true
145
-
meson compile -C build
146
-
```
147
-
148
-
Examples will be placed in `build/example/` and test binaries in `build/test/`. You can run them directly:
149
-
150
-
```sh
151
-
./build/example/example_1
152
-
./build/example/example_2
153
-
```
154
-
155
-
and execute the full test suite with:
156
-
157
-
```sh
158
-
meson test -C build
159
-
```
160
-
161
-
🧪 Tests are written using the Unity framework, pulled in automatically as a Meson subproject.
162
-
163
-
---
164
-
165
-
## ⚙️ Development workflow
99
+
## Development workflow
166
100
167
101
For convenience, there’s an [Invoke](https://www.pyinvoke.org/) setup that automates common tasks:
168
102
@@ -175,11 +109,6 @@ inv lint # run clang-tidy checks
175
109
inv clean # remove build artifacts
176
110
```
177
111
178
-
This makes it easy to keep the environment consistent and catch issues early.
0 commit comments