I think it's important to keep in mind that with C you might end up using a bunch of extenions, complex pre-processor macros or pragma magic to achieve essential things.
C in itself is relatively simple, but using it in practice can end up becoming quite complex.
Zig ends up being simpler in some ways by having less magic and special ways of achieving things. "printf" is very magical in most C compilers, but the equivalent in Zig is nothing particularly special from either the language or the compilers side.
I think the only thing that truly adds complexity without being strictly necessary, is the async stuff. But you can argue that async IO is becoming a really essential thing for systems programming, and using it without explicit language suppport is a nightmare.
1. https://github.com/bminor/glibc/blob/glibc-2.32/stdio-common...
Also the compiler is likely to optimise most calls to printf, transforming them into calls to other functions like puts [0], or apparently [1] even to fwrite. Probably doesn't count as magic, but almost.
[0] https://blog.stephencleary.com/2012/07/dont-block-on-async-c...
[1] https://docs.microsoft.com/en-us/archive/msdn-magazine/2013/...
It really is just a control flow structure in zig (which is why it's a keyword)
Of course, even if you understand the rules you'll still accidentally write code that invokes undefined behaviour, which is much of the reason languages like Zig exist.
See whole section here
https://ziglang.org/documentation/master/#Undefined-Behavior
the difference is, they try to check for them at compile time, and if you compile with safety checks, also at runtime; however, if you compile with ReleaseFast (which I assume most people will in production), those runtime checks are turned off and the undefined behavior still exists.
No, people are supposed to use ReleaseSafe, especially "in production". You can still disable runtime checks in specific execution paths.
ReleaseFast is for applications where there are no disastrous consequences to a bug in the code, like videogames.
Zig's approach is essentially that of Ada. You can ask the Ada compiler for runtime checks, or promise the compiler that your code is free of undefined behaviour and have your code run at the speed of light (C), or go haywire if you got it wrong. Sadly C is less suited to runtime checks, arrays are dealt with through raw pointers so range checks aren't easy for the compiler to add automatically. You can though ask GCC to generate checks (trap-on-failure) for things like dereferencing NULL, or signed arithmetic overflow.
You can’t really check if an integer variable is greater than 2^32, since that itself would cause an integer overflow.
Of course, in practice this is too much effort for most people most of the time, so actual deployed C and C++ code is full of undefined behavior due to integer overflow. This paper has a great overview:
Also, the maximum value that can be represented in a variable of type uint32_t is (2^32) - 1, not 2^32.
Most DDJ or The C/C++ Users Journal issues will have them.
edit: As lmm says, it's likely you have UB issues in your code you aren't aware of. That's not quite the same thing as having issues in your code due to not being a good enough language-lawyer. I've resolved some very subtle issues that found their ways into a 'serious' C++ codebase, and I didn't spend that long in the C++ world. In most languages those issues simply couldn't have happened in the first place.
GCC compiles to alot of architectures. I have a hard time imagining any modern language compiling to all those platforms without quirks in practice.
Right. A battle-tested codebase only has subtle errors, as the obvious ones will all have been fixed. An immature codebase has subtle errors and more obvious ones.
> GCC compiles to alot of architectures. I have a hard time imagining any modern language compiling to all those platforms without quirks in practice.
Compiler bugs are a separate issue from undefined behaviour and surprising language subtleties. With mature compilers they're pretty rare, but they do happen.
JavaScript is a good example. There's no undefined behaviour in JavaScript. That's vitally important given that JavaScript engines have to be able to run untrusted code. If JavaScript code is able to cause undefined behaviour, that's a serious security issue in the engine. Such bugs do happen, of course, but they aren't all that common. Generally, JavaScript runs fine regardless of whether you're running on x86, AMD64, or AArch64. Same goes for Java.
(I admit I'm ignoring the possibility of a constrained/contained kind of undefined behaviour where the JavaScript context might see things go haywire but the process containing the JavaScript environment is unaffected.)
How recently? Both gcc 4.1.2 (2007) and clang 3.0.0 (2011) optimizes `x+1 > x` to true for a signed int `x` on -O1. And it probably goes way back, these are just the oldest compilers I found on godbolt.
The thing is it's very hard to draw the line once you go that route. Different people expect different things from undefined behavior. The best thing is to not expect anything sane. And if you are unhappy about certain undefined behaviors in the standard then it's better to push the standard to define more behavior. Certain unnecessary undefined behaviors get resolved with newer standards, although I would expect significant pushback on defining the behavior signed integer overflow.
Do you have a source for this?
As I mentioned elsewhere in the thread, you can ask gcc to trap if your code is about to dereference NULL, but the compiler can't easily detect all instances of out-of-bounds array access, due to the way arrays and pointers work in C. I believe Valgrind can help detect out-of-bounds errors at runtime, but in most languages you don't need a sledgehammer like Valgrind to find these common errors.
Well if you include preprocessor, make, and cmake (which I think is basically standard now), the level of complexity feels "close to parity".
True, but on the other hand... C _does_ have preprocessor macros, which from a pure language standpoint adds a lot of complexity.