6 ms·
Is it main problem with C? I never had any issues with arrays. For me problems with C are as follows: 1. Inadequate standard with lots of unspecified behavior
by vbezhenar 2y ago
Is it main problem with C? I never had any issues with arrays.
For me problems with C are as follows:
1. Inadequate standard with lots of unspecified behavior. I want behavior matching my architecture. Wrap my integers, always. I want my bit fields to be laid out in a predictable way, so I can actually use that feature to work with protocols rather than using shifts and bitwise operations. I must not research whether compiler will throw away my `while (true)` loop. Compiler must be predictable.
2. Bad standard library backed by the language standard (so I can't just throw it away, compiler will replace my loops with memcpy or will replace any standard library call with particular semantics).
3. Unexpected runtime cost. For example global variables are initialized to zero and that requires special code inserted before main.
4. Bad syntax. Switch must not allow fall-through, not by default anyway. `()` must be treated as function with zero arguments, not as a function with unspecified arguments.
5. Lack of namespaces and modules.
I definitely don't want compiler to track array lengths. C is not Rust.
- JonChesterfield 2y ago1. Is what gets in my way the most when writing C. The whole undefined behaviour story is a wreck. Not hugely relevant but zero initialised globals don't imply code inserted before main. They get allocated at link time into a continuous blob which is zeroed by the loader. Non-zero values take up space in the binary but also involve no code generation. Global constructors, C++ style, usually do involve walking an array of function pointers before main though.
- pif 2y ago> I want behavior matching my architecture. Then use assembly. C is meant to abstract CPU architecture away.
- vbezhenar 2y agoUsing just assembly is not realistic. I'd love C-like language which compiles to assembly in an understandable way. I mean that optimizations are certainly useful and needed. I want to write `24 * 60 * 60` and expect for compiler to multiply those values at compile time. I want to write debug logging which could be disabled for release build and all relevant variables and functions be excluded. Basically there are reasonable optimizations which I would expect for compiler to do. Anyway it's not like there's a choice in reality. Manufacturer provides SDK and examples with C, using another language for most projects is not realistic.
- pif 2y ago> Using just assembly is not realistic. And using C as fully deterministic is idiotic, considering the effort that went into specifying in great details everything that is NOT deterministic. > I'd love C-like language which compiles to assembly in an understandable way. Which is orthogonal to its purpose. A compiler's job is to provide the fastest code that implements what your (correct) code specified. Readability is pointless for generated code, as it not meant to be read nor modified.
- rstuart4133 2y ago> Then use assembly. C is meant to abstract CPU architecture away. Maybe. Looking back, I'd describe C as a portable assembler. K&R only optimised CPU architecture away when it was efficient to do so. For example, it wasn't efficient to specify the signed-ness of a char - so they didn't. Likewise the result of modulo ('%') on a negative number was undefined. Even the size of an int was undefined. In all these cases you get what the hardware gives you - just like you do in assembler. I think that was the right decision. It was the best that could be done at the time. The price of the abstractions languages put in place in the name of safety and convenience meant they were a poor fit for embedded and system work. Consequently C has had decades of dominance in it's niche, a success that speaks for itself. Now a plausible challenger has arisen, but it's taken forever in internet time. What I will criticise is what came after K&R. In K&C Undefined Behaviour meant "you get what the hardware gives you". Since what the hardware did was always well defined with a given compiler and arch everything the behaviour of your code was 100% deterministic. Then the compiler writers seeking to produce faster code twisted the definition to mean "if the behaviour is undefined we can do whatever we damned well please in order to generate faster code". And with that, at different optimisation levels code started to do different things. The C++ plumbed the depths of this idiocy by definite infinite loops as UB. I have no idea why - embedded computers, services on modern OS's and even OS's themselves have infinite loops at their core - loops running until the electrons are removed is exactly the behaviour the programmer is depending on. It's idiocy because the halting problem (and ergo whether a particular loop is infinite) is famously not decidable. So they've undefined behaviour in a way that itself isn't defined. In practical terms, this means you write an infinite loop you are making a bet some you've made so convoluted a future version of the optimiser won't spot it. It's a bet some the occasional person has lost when they moved to a new version of the compiler. It is possible to signal to the compiler the loop must stay regardless, but you have to be a language lawyer to know how, and most don't. So yes for today's C you are right, it is not assembler. In some ways it's objectively worse, as assembly is always well defined.
- pif 2y ago> In K&C Undefined Behaviour meant "you get what the hardware gives you". No, what you are talking about is "implementation-defined". Out of all trades, programmers should know better than everybody else to code against the specification rather than the implementation. If your code worked with K&R compiler by chance, and not by contact, then your code is wrong. Period.
- lieks 2y agoThe Rust/memory safety crowd does exaggerate its importance somewhat. Arrays aren't actually that common in some types of applications, especially kernels, which is what C was designed to implement. Null-terminated strings are bad, but there's nothing about the language (other than the standard library and literals) that forces you to use them. There are several alternative implementations[a][b]. 1 is true, but the modern interpretation of UB is against the spririt of the original standard, even though it's according to the letter of it. It was meant to mean exactly what you're talking about, but got optimized into oblivion later. 2 is what I'd say is the main problem with C. The library is truly awful, to the point where I treat all of it except the mem* functions and maybe exit deprecated. 3 is not true; look up how the BSS segment works (it might be true on Windows, I don't know, but that's not really a very good C implementation, since it's for C++ primarily). 4 is true for a reason; pre-standard C didn't have function prototypes. This was invented by the standard, to much rejoicing. But they needed compatibility, so that's where it comes from. Switch cases are really labels, and that's why they fall through. If C was more actively developed (read: less stable) these would have already been fixed, by deprecating them and creating replacements. 5 is not something I usually care about much. Write one header file per module, don't include headers from other headers (declare anything you need in the header itself, the compiler allows it) and just prefix each exported function with foo_ or something like that. It's not that much trouble, and you get multiple .c-files pre module, which some languages don't allow. The thing is that, in order to fix most of those issues, you'd either break backward-compatibility (and fork the laguage) or break most existing implementations (and lose all support). Making a new language is much easier. As for tracking lengths, I mostly agree. If the length is statically known, that's perfectly fine. But dynamic lengths shouldn't have hidden tracking. Still, the C99 solution isn't that great, and I can't see how else to do it. [a] https://nullprogram.com/blog/2023/10/08/ https://nullprogram.com/blog/2023/10/08/ [b] there was a fat-pointer string library, but I can't find it now
- nesarkvechnep 2y agoIs [b] https://github.com/antirez/sds https://github.com/antirez/sds?
- 2y ago
- juliangmp 2y agoI 100% agree with the first part, "undefined behavior" should have never become an excuse for the compiler authors to remove error handling from our code as an "optimization". But its still beyond me how in decades of programming the C people somehow haven't had the idea of a pointer+size type. Whether you call it a slice, span or a view, doesn't matter. Why do I have to manually pass a size everywhere, it's such a common case, why is there not a common solution?? Are the people writing the standard just afraid of change? Seems to me, especially when it comes to things that could resemble new syntax. Sidenote why does _Atomic look like that?