Isn't this undefined behavior?
Unfortunely many keep needing education on such matters.
> You can store a null pointer in any lvalue whose data type is a pointer type. [0]
Though, I would expect a complaint from clang, and clang-tidy.
[0] https://www.gnu.org/software/c-intro-and-ref/manual/html_nod...
Relying on sanitizers to catch UB can only work in a best effort basis, because the compiler can perform optimizations that rely on the fact that the program doesn't have UB (and produces broken code if there is UB - beyond what a sanitizer could catch)
It would be much better to also provide another macro to abort the program if the maybe is nothing.
Overall, it is still far more portable than C++ or any other new language.
Heck I learned to program in C++, back when DR-DOS 5 was the latest version, and all I had available was 640 KB to play around, leaving aside MEMMAX.
Nowadays the only reason many embedded developers keep using C is religous.
I think you're making some extraordinary claims. I'd love to see some receipts. :-)
I have had trouble compiling older C++ code bases with newer compilers, even when specifying C++98 as source standard. I gave up trying to get Scott McPeak's Elkhound C++ parser to compile, last I had to attempt it.
C is a bit more forgiving on that topic (it hasn't changed as much, for better or worse).
Still using tcc, or stuck in GCC 5 ?
There are quite a few besides various PICs AFAIK, how modern they are is subjective I guess, and it IS mostly the weaker chips. Keil, Renesas, NXP, STMicro (STM8 MCUs used to be C only, not sure today) all sell parts where C++ is unsupported.
> Nowadays the only reason many embedded developers keep using C is religous.
I don’t completely agree, but I see where you are coming from.
The simplest tool that gets the job done is often the best IMO.
In my experience, it is much more difficult to learn C++ well enough to code safely, compared to C.
Many embedded developers are more EE than CS, so simpler software is often preferred.
I know you don’t have to use all the C++ features, all at once, but still :)
Horses for courses. I prefer C for anything embedded.
The speed at which you can write great C code often far outstrips other languages which are applicable to the problem domain.
All languages are a compromise, there are no silver bullets.
#define maybe(T) struct maybe_##T { bool ok; T value; }
#define maybe_just(T, x) (maybe(T)){ .value = (x), .ok = true }
#define maybe_nothing(T) (maybe(T)){ .value = (T){ }, .ok = false }
#define maybe_value(T, x) (({ maybe(T) _p = &(x); _p->ok ? &_p->value : (void*)0; }))
Please compare this to your other languages.
Especially the the safety is something I can't step over. I don't feel it offers anything substantial over just having the struct without these macros.
1. Ergonomics: Forcing the null sanitizer is quite a sledgehammer you often cannot, or don't want, to pay, You are forcing global behavior for something you really only want locally for this construct. A misuse of maybe_value is a crash where in other languages you have case/match and compile time errors. There is no type inference so you have to be explicit at every, single, line, that you really mean a maybe(int) or whatever.
2. Integration: No libraries uses this, meaning any usage is limited to your own code only. Requiring manual and error prone translation at every interface.
3. Safety: No check the null sanitizer is actually on. This is a huge footgun where someone thinks "I know I use this neat macro I saw here". And then of course not enabling the null sanitizer and everything breaks. So now for this to be safe it's not enough to understand the code. You need to check if the compile options are just right. This is especially insidious since this cannot be hidden in some object file where you make damn sure the sanitizer is on. the CONSUMER of this API must enable the sanitizer
For these reason I would ban this macro in any code I have control over.
It could work if "maybe(T)" is completely opaque to the user; both checking and accessing its payload must happen through helper macros; the checking macro ticks an invisible flag if ok; the accessing macro returns the payload only if the invisible flag is ticked, otherwise it triggers a runtime error/exception.
Not impossible. However, you would need to replace all "p.ok" with "maybe_check(p)", which is not unreasonable, and all "p.value" with "maybe_value(p)", which might be too much for the final user...
nly•6mo ago
https://gcc.godbolt.org/z/vfzK9Toz4
uecker•6mo ago
JonChesterfield•6mo ago
uecker•6mo ago
pjmlp•6mo ago
uecker•6mo ago
pjmlp•5mo ago
uecker•5mo ago
pjmlp•5mo ago
At least that is something we can agree on, C and C++ are both languages where developers could write robust code, and the large majority seldom does it.
uecker•5mo ago
wahern•6mo ago
pwdisswordfishz•6mo ago
pjmlp•6mo ago
https://gcc.godbolt.org/z/31o75W5xx
https://gcc.godbolt.org/z/av6a43WeY
uecker•5mo ago
But what matters is run-time behavior for any input. And sorry, C++'s complexity still has the effect that the code is terrible: https://godbolt.org/z/vWfjjf8rP
In C, the compiler can remove all error paths: https://godbolt.org/z/GGMcc6bxv
nly•5mo ago
uecker•5mo ago
aw1621107•5mo ago
> In C, the compiler can remove all error paths: https://godbolt.org/z/GGMcc6bxv
For what it's worth, if you actually use the same flags for the C++ example (in particular, -fsanitize-trap) the C++ compilers improve quite a bit, with Clang getting fairly close to the GCC C output (https://godbolt.org/z/M3z5EaGKj ; note that GCC doesn't seem to eliminate the std::optional::value() check unless you use -O3). But perhaps more interestingly, if you make a simplified std::optional that still uses C++-specific features both Clang and GCC produce output that is very close to that from C/GCC: https://godbolt.org/z/PhEW7eT8x
Removing I/O-related stuff makes this clearer:
C: https://godbolt.org/z/e71s9E63f
C++: https://godbolt.org/z/YnhdxYxY4 (Clang seems unable to eliminate the UBSan overflow check for some reason?)
Does make me wonder what exactly it is about std::optional that confuses the optimizers, and whether a similarly complex C maybe implementation can suffer from the same issues that std::optional appears to.
account42•6mo ago
It's still a damned shame. Same for not being able to pass optional/unique_ptr/etc in a register.
We really need a trivially_relocatable attribute.