Generics in C without void* or macros – enabled by psychec
github.com
github.com
Like what?
If your constructors fail without throwing an exception, much of the usefulness of RAII goes away.
How would that make the usefulness of RAII go away?
std::vector<int> v; // v's lifetime begins
// currently owns nothing, exactly as in two-step initialization
v = other_vector; // resource #1 becomes live
v = std::move(another_vector); // resource #1 dies, resource #2 becomes live
return std::move(v); // resource #2 now owned by returned object, will outlive v
// v's lifetime ends
I'd say RAII is more about making sure every resource always has an owner and that when an object dies, all the resources it owned are cleaned-up. And that works fine with two-step initialization.Looking at their example:
_Generic void prepend(_Forall(node_t)** head,
_Forall(value_t) value)
{
node_t* n = malloc(sizeof(node_t));
n->value = value;
n->next = *head;
*head = n;
}
For each use of `prepend()`, their frontend defines an appropriate struct with fields `value` then `next` (in that order) whose types are inferred from the assignments in the body of the function.Though TBH i wouldn't use OP's preprocessor either as i think that void* and macros are perfectly fine.
Regarding execution times, I don't know from first-hand experience, I did some research and found this[1]. The third sentence in that reply ("The error code is not sensitive to the percentage of occurrence") is AFAIK wrong (because of speculative execution, I believe?), but the rest seems quite solid.
There's cost to transfer it through memory and cache hierarchies and there's cost when it causes the CPU to drop some other L1C cache line.
I think I'll test whether use of exceptions can affect optimizations concerning register allocation and spilling, like function prologues and epilogues. Unwinding will also need to call destructors, and for that you'll need to have the object pointer somewhere in (stack frame) memory. But it might be in just a register. Unless, of course, the unwinding engine can look into registers as well.
I guess it's not a huge deal either way, especially in 32-bit x86 code. For large register file systems it might be a different story, forcing otherwise unnecessary spills. Regardless, I'll find out when I got some time.
Anyways, my point was more generic. So-called zero-cost abstractions are my pet peeve, because they're often not zero cost precisely because of larger generated code size. Typical microbenchmarks don't capture this, because their hot path can usually fit comfortably in L1 code and data caches.
Regarding spilling and register allocation, IIRC[1] the unwind tables contain a simple bytecode that is interpreted by the undwinder to fixup stack and restore registers (indexed by the current ip address), so no actual registration of object to destroy need to be done, nor does the frame pointer need to exist at all: the unwinder interprets the bytecode to recursively adjust each stack frame and registers to the expected values.
[1] At some point I had actually learned to write unwind tables by hand, of course I completely forgot everything about that: look at trampoline_thunk at https://github.com/gpderetta/delimited/blob/master/delimited... even with extensive comments is still write-only code. It doesn't help that there aren't a lot of DWARF CFI tutorials around.
You only use up "cache" if you actually hit the cache. If you're on a different L1 cache line (and never execute), you'll never be in L1 instruction cache, maybe never even in L2 or even L3 cache lines.
Not that I'm an expert on compilers... but whenever I look at assembly, there are lots of "NOPs" across the code. I've always assumed it was for some kind of boundary alignment, probably cache.
I'd sure hope they're on an entirely different page, not just cache line!
Concerning compiler code alignment: they seem to place my code on 16-byte boundaries - for a good reason too, the smallest code size while keeping optimal 16-byte aligned branch targets.
In past I actually had to drop to assembler to get 64-byte (cache line) alignment for functions (MSVC).
GCC does have optimize pragma, like:
#pragma GCC optimize ("align-functions=64")
Although I want to point out one shouldn't usually worry about low level details like function alignment. Use profile guided optimization, etc. instead. Also remember due to L1 set associativity limitations excessive alignment can even cause pathological L1 churn in the worst case.> We're so used to slow software that when a compile-and-link sequence that took two minutes on a PC takes just ten seconds on a 486 computer, we're ecstatic—when in truth we should be settling for nothing less than instantaneous response.
(from https://github.com/jagregory/abrash-black-book/blob/master/s...)
Personally i'm annoyed when my compile times take more than a few seconds :-P
Heck, you could define a few macros and make it impossible to use any baggage features you don't like.
I've used C++ in a pretty tight embedded environment and it was great.
I'd love to hear some details. I'm always looking for C++ stories that didn't end in disaster and what the people did that worked.
There are some very interesting constraints in GPU-programming. Fitting C++ classes and even some data-structures in the "shared memory" region is pretty nifty.
It is quite do-able. Lots of static declarations, basically write C with classes. V-tables ended up being non-trivial in size, but what can you do, GUIs and all that. Possibly the best (only?) good use of inheritance hierarchies.
256KB memory, the UI only had 60KB or so available for it IIRC. We also had a scratch pad available over a super slow bus of a couple megs, but it couldn't be used too much or we'd drop frames and we loved our 30fps v-sync'd performance.
In this mode, no GC is provided, so a developer will be using manual allocation.
With upcoming D's dip1000 feature, it will be able to do rust-like borrows checker at compile time (or at least this is my understanding). And that capability will be available in betterC mode as well (not just for full blown D).
The code in betterC can use generics and other advanced features
https://dlang.org/spec/betterc.html
---
Nearly the full language remains available. Highlights include:
- Unrestricted use of compile-time features
- Full metaprogramming facilities
- Nested functions, nested structs, delegates and lambdas
- Member functions, constructors, destructors, operating overloading, etc.
- The full module system
- Array slicing, and array bounds checking
- RAII (yes, it can work without exceptions) scope(exit)
- Memory safety protections
- Interfacing with C++
- COM classes and C++ classes
- assert failures are directed to the C runtime library
- switch with strings
- final switch
- unittest
dip1000:
https://github.com/dlang/DIPs/blob/master/DIPs/other/DIP1000...
One of the reasons I'm actually going for pure C in this modern day and age, is that I can just write a small program, without having any other dependency.
I'm sure the same could be said for C++, too, but for some reason, with my setup (GCC 5.1.0 with MinGW on Windows 7), I'm averaging around 800KB on C++ vs 80KB on C, for the (mostly) same code. EDIT: It appears that exception-handling is one of the reasons the binary is bigger in C++ than C.
I mainly write code for embedded devices, so whatever programs I write on x86 is to interface with those devices, hence I'm quite comfortable staying with C.
...I guarantee that it is nothing short of humongous xD
Joking aside, maybe, I don't know, it's been a while since I used some C++ code, but I'll have a look into it the next time around.
...though it's probably unlikely, I use Code::Blocks and exclusively use the "Release" candidate, and uncheck the "Debug" version, and I'm quite confident the CB authors know what they are doing.
I'm in the same situation and I use C# for windows based tools.
Is this considered harmful because it hides the fact that you're dealing with a pointer in the first place?
If you apply qualifiers like const to your type, results might me unexpected.
It might have semantic benefits, but C developers should be trained to ignore all these strange *-symbols anyway.
It's all semantic sugar anyway.
While I've great respect for the authors of that fantastic library, I only have this to say about the practice: Jeeesh!
I typedef function pointers when they get too verbose. Otherwise not. But it's not the mole hill I'd choose to die on either.
[1]: http://www.robertgamble.net/2012/01/c11-generic-selections.h...
[1] https://ziglang.org/download/0.1.1/release-notes.html [2] https://andrewkelley.me/post/intro-to-zig.html
* [0.2.0 release notes](https://ziglang.org/download/0.2.0/release-notes.html)
* [0.3.0 release notes](https://ziglang.org/download/0.3.0/release-notes.html)
* [0.4.0 release notes](https://ziglang.org/download/0.4.0/release-notes.html)
0.5.0 is scheduled to be released Sept 30.