logoalt Hacker News

Giving C a superpower: custom header file (safe_c.h)

222 pointsby mithcstoday at 10:40 AM180 commentsview on HN

Comments

woodruffwtoday at 1:30 PM

Intentionally or not, this post demonstrates one of the things that makes safer abstractions in C less desirable: the shared pointer implementation uses a POSIX mutex, which means it’s (1) not cross platform, and (2) pays the mutex overhead even in provably single-threaded contexts. In other words, it’s not a zero-cost abstraction.

C++’s shared pointer has the same problem; Rust avoids it by having two types (Rc and Arc) that the developer can select from (and which the compiler will prevent you from using unsafely).

show 8 replies
jurschreudertoday at 9:55 PM

Anybody know his github?

cachiustoday at 12:56 PM

A recent superpower was added by Fil aka the pizlonator who made C more Fil-C with FUGC, a garbage collector with minimal adjustments to existing code, turning it into a memory safe implementation of the C and C++ programming languages you already know and love.

https://news.ycombinator.com/item?id=45133938

https://fil-c.org/

show 3 replies
purplesyringatoday at 3:55 PM

This feels like a misrepresentation of features that actually matter for memory safety. Automatically freeing locals and bounds checking is unquestionably good, but it's only the very beginning.

The real problems start when you need to manage memory lifetimes across the whole program, not locally. Can you return `UniquePtr` from a function? Can you store a copy of `SharedPtr` somewhere without accidentally forgetting to increment the refcount? Who is responsible for managing the lifetimes of elements in intrusive linked lists? How do you know whether a method consumes a pointer argument or stores a copy to it somewhere?

I appreciate trying to write safer software, but we've always told people `#define xfree(p) do { free(p); p = NULL; } while (0)` is a bad pattern, and this post really feels like more of the same thing.

kraphttoday at 12:41 PM

C++: "look at what others must do to mimic a fraction of my power"

This is cute, but also I'm baffled as to why you would want to use macros to emulate c++. Nothing is stopping you from writing c-like c++ if that's what you like style wise.

show 6 replies
fuhsnntoday at 1:13 PM

> C23 gave us [[cleanup]] attributes

C23 didn't introduce it, it's still a GCC extension that needs to be spelled as [[gnu::cleanup()]] https://godbolt.org/z/Gsz9hs7TE

show 1 reply
archargelodtoday at 2:28 PM

You can get all of that and more with Nim[0].

Nim is a language that compiles to C. So it is similar in principle to the "safe_c.h". We get power and speed of C, but in a safe and convenient language.

> It's finally, but for C

Nim has `finally` and `defer` statement that runs code at the end of scope, even if you raise.

> memory that automatically cleans itself up

Nim has ARC[1]:

"ARC is fully deterministic - the compiler automatically injects destructors when it deems that some variable is no longer needed. In this sense, it’s similar to C++ with its destructors (RAII)"

> automated reference counting

See above

> a type-safe, auto-growing vector.

Nim has sequences that are dynamically sized, type and bounds safe

> zero-cost, non-owning views

Nim has openarray, that is also "just a pointer and a length", unfortunately it's usage is limited to parameters. But there is also an experimental view types feature[2]

> explicit, type-safe result

Nim has `Option[T]`[3] in standard library

> self-documenting contracts (requires and ensures)

Nim's assert returns message on raise: `assert(foo > 0, "Foo must be positive")`

> safe, bounds-checked operations

Nim has bounds-checking enabled by default (can be disabled)

> The UNLIKELY() macro tells the compiler which branches are cold, adding zero overhead in hot paths.

Nim has likely / unlikely template[4]

------------------------------------------------------------

[0] https://nim-lang.org

[1] https://nim-lang.org/blog/2020/10/15/introduction-to-arc-orc...

[2] https://nim-lang.org/docs/manual_experimental.html#view-type...

[3] https://nim-lang.org/docs/options.htm

[4] https://nim-lang.org/docs/system.html#likely.t%2Cbool

edwcrosstoday at 2:59 PM

The post mentions cgrep several times, but I don't see a link to the code. Is it available somewhere?

Github has several repositories named cgrep, but the first results are written in other languages than C (Haskell, Python, Typescript, Java, etc).

show 1 reply
rurbantoday at 12:46 PM

Just don't mix that up with the real safec.h header from safeclib:

https://github.com/rurban/safeclib/tree/master/include

show 1 reply
gebdevtoday at 5:35 PM

This is a great example of how ADTs can be implemented in C by emulating classes, despite the loss in brevity.

For the first item on reference counting, batched memory management is a possible alternative that still fits the C style. The use of something like an arena allocator approximates a memory lifetime, which can be a powerful safety tool. When you free the allocator, all pages are freed at once. Not only is this less error prone, but it can decrease performance. There’s no need to allocate and free each reference counted pointer, nor store reference counts, when one can free the entire allocator after argument parsing is done.

This also decreases fallible error handling: The callee doesn’t need to free anything because the allocator is owned by the caller.

Of course, the use of allocators does not make sense in every setting, but for common lifetimes such as: once per frame, the length of a specific algorithm, or even application scope, it’s an awesome tool!

show 1 reply
dboontoday at 2:48 PM

Nice, but if the intention is portability my experience has unfortunately been that you pretty much have to stick to C99. MSVC’s C compiler is rough, but pretty much necessary for actual cross platform. I have my own such header which has many, many things like the OP’s. As much as I would find it constantly useful, I don’t have a cleanup utility because of this.

But if you can stay out of MSVC world, awesome! You can do so much with a few preprocessor blocks in a header

show 3 replies
HexDecOctBintoday at 12:55 PM

Any hopes that MSVC will add C23 support before 2040?

show 4 replies
bad_usernametoday at 4:02 PM

This reminds me that Scott Meyers (IMO rightfully) considered the destructor as the single most important fearure in C++.

show 1 reply
k1rdtoday at 6:15 PM

Seems like someone should invent C+, which would be C but with the reasonable safety guardrails that C++ implemented in the last 10/20 years.

throwaway889900today at 4:36 PM

Does the StringView/Span implementation here seem lacking? If the underlying data goes away, wouldn't the pointer now point to an invalid freed location, because it doesn't track the underlying data in any way?

carlos256today at 8:48 PM

Please don't do It.

hanstospacetoday at 1:16 PM

I checked Fil-C out and it looks great. How is this different from Fil-C? Apart from the obvious LLVM stuff

show 1 reply
khaledhtoday at 1:25 PM

The problem with macro-laden C is that your code becomes foreign and opaque to others. You're building a new mini-language layer on top of the base language that only your codebase uses. This has been my experience with many large C projects: I see tons of macros used all over the place and I have no idea what they do unless I hunt down and understand each one of them.

show 3 replies
varispeedtoday at 6:55 PM

I am not convinced. I much prefer using plain C without superpowers and write extensive test suite and analyse the code multiple times. There is always a chance to miss something, but code without "magic" is much easier to reason about.

immibistoday at 2:10 PM

This feels AI-generated.

show 1 reply
kerkeslagertoday at 3:44 PM

I feel like there might be some value in this header file for some projects, but this is exactly the wrong use case.

> In cgrep, parsing command-line options the old way is a breeding ground for CVEs and its bestiary. You have to remember to free the memory on every single exit path, difficult for the undisciplined.

No, no, no. Command line options that will exist the entire lifetime of the program are the quintessential case for not ever calling free() on them because it's a waste of time. There is absolutely no reason to spend processor cycles to carefully call free() on a bunch of individual resources when the program is about to exit and the OS will reclaim the entire process memory in one go much faster than your program can. You're creating complexity and making your program slower and there is literally no upside: this isn't a tradeoff, it's a bad practice.

show 3 replies
miroljubtoday at 12:41 PM

Nice toy. It works until it stops working. An experienced C developer would quickly find a bunch of corner cases where this just doesn't work.

Given how simple examples in this blog post are, I ask myself, why don't we already have something like that as a part of the standard instead of a bunch of one-off personal, bug-ridden implementations?

show 2 replies
keyletoday at 12:50 PM

I don't understand this passion for turning C into what it's not...

Just don't use C for sending astronauts in space. Simple.

C wasn't designed to be safe, it was designed so you don't have to write in assembly.

Just a quick look through this and it just shows one thing: someone else's walled garden of hell.

show 5 replies