8 ms·
C is the ultimate WYSIWYG language (provided you understand the semantics of your target architecture and assuming a non-buggy compiler). The language is relati
by jay-barronville 2y ago
C is the ultimate WYSIWYG language (provided you understand the semantics of your target architecture and assuming a non-buggy compiler). The language is relatively simple. The standard is accessible. I’d like it to remain that way. I don’t need C to adopt any other “modern” language features.
C11 provided a few worthwhile improvements (i.e., a proper memory model, alignment specification, standardized anonymous structures/unions), but so many of the other additions, suggestions, and proposals I’ve seen will just ruin the minimal nature of C. In C++, a simple statement like `a = b++;` can mean multiple constructors being called, hidden allocations, unexpected exceptions, unclear object hierarchies, an overloaded `++`, an overloaded `=`, etc. Every time I wish I had some C++ feature in C, I just think about the cognitive overhead it’d bring with it, slap myself a couple times, and go back to loving simple ole C.
Please don’t ruin C.
- chowells 2y agoAnd presuming you avoid 100% of undefined behavior, which I've never seen a non-trivial C program succeed at. C is way too complicated in the real world. You don't want C, you want a language that actually gives defined semantics to all combinations of language constructs.
- einpoklum 2y agoYou don't necessarily want that. Forcing language-defined semantics on everything costs performance. Sorry, it just does, we can't have it all. So, you can sacrifice performance for well-defined'ness, or you can choose not to - and the choice depends on the language _design goals_. As the design goals differ, so do the combinations of choices made for syntax and semantics.
- bigstrat2003 2y agoI think pretty much any amount of performance is worth sacrificing in order to get rid of the gnarly things UB can cause. Correctness is the first and most important thing in programming, because if you can't be certain it works then it's not very useful.
- einpoklum 2y agoIt may be worth it _for you_. It is not worth it _for others_. Correctness can be established well enough - even if guaranteed automatically - in a language with UB.
- fooker 2y ago>you want a language that actually gives defined semantics to all combinations of language constructs No, this is wrong. It's a common misconception though. You would only want that in a hypothetical world where all computers are exactly the same. Undefined and implementation defined behavior is what allows us to have performance at all. Here are some simple examples. Suppose we want to make division by zero and null pointer dereference defined. Now every time you write a/b or *x, the compiler will be forced to emit an extra branching check before this operation. Something much more common---addition. What about signed overflow? Do you want the compiler to emit an overflow check in advance? Similar reasoning for shift instructions. UB in the language specification allows compilers to optimize based on the assumption that the programs you write won't have undefined behavior. If compilers are not able to do this, it becomes impossible to implement most optimizations we rely on. It's a very core feature of modern language specifications, not an oversight you can fix by thinking about it for 10 minutes.
- grumpyprole 2y agoI'm sorry I still don't buy it. Can you please show me a use case where ignoring null pointer or overflow checks makes your product non-viable or uncompetitive? Some of these checks could be removed by languages with better compilers and likely more restrictions. That is the better approach. As a user, I don't want to run code that is potentially unsafe and/or insecure.
- fooker 2y agoIt's not ignoring about ignoring null pointer or overflow checks, it's having to necessarily insert these checks everywhere.
- grumpyprole 2y agoWe should build compilers that insert these checks for us (if they cannot statically determine them unnecessary). The ability to omit these checks doesn't IMHO justify undefined behaviour.
- kimixa 2y agoI feel the meme of "Undefined Behavior" has been massively exaggerated on the internet - the vast majority of examples appear to be extreme toy examples using the weirdest contrived constructs, or things that are expected to fault and you're already using platform-specific information to know what that would look like (e.g. expecting a segmentation fault). It's a Scary Boogyman That Will Kill You, not something that can be understood, managed, and avoided if necessary. And even then there are tools to help define much of that - if you want well defined wrapped signed integers, great. If you want to trap on overflow, there's an option for that. Lots of compiler warnings and other static analysis tools (that would just be default-rejected by the compiler today if it didn't have historical baggage, but they exist and can be enabled to do that rejection). Yes, there's many issues with the ecosystem (and tooling - those options above should be default IMHO), but massively overstating them won't actually help anyone make better software. And other languages often have similar amounts of "undefined behavior" - but just don't document it as such, relying on a single implementation being "Defined Correct", and hope they're not actually being relied on if anything changes. Just like C, only undocumentated.
- adrianN 2y agoI don't feel like the cause of most (all?) memory safety bugs has been "massively exaggerated".
- kimixa 2y agoIf you removed every case of "Undefined Behavior" from the C spec, you'd still have memory safety bugs. Because they're orthogonal (though may be coupled if they come from the same core logic error). This is what I mean by it becoming "meme" - things like "Undefined Behavior" or "Memory Safety" have become a discussion-ending "Objective Badness", hiding the real intent - being "Languages I Do No Like" (or, most often, are a poor fit for the actual job I'm trying to do. Which is fine, but not rejecting that those jobs actually exist). But they mean real things that we can improve in terms of software quality, and safety - but that's rarely the intended result when those terms are now brought up. And many things we can do right now with existing systems to improve things, to not throw away huge amounts of already well-tested code. To do a staged improvement, and not let "perfect" be the enemy of better.
- throwaway2037 2y ago> You don't want C, you want a language that actually gives defined semantics to all combinations of language constructs. So, Zig?
- sixfiveotwo 2y agoWell, perhaps a subset of it, since it also introduces concepts that do not exist in C (eg. exceptions).
- samatman 2y agoZig does not have exceptions, what it has is error sets. It uses the words try and catch, which does cause confusion, but the semantics and implementation are completely different. If a function has an error type (indicated by a ! in the return type), you have a few options. You can use `result = try foo();`, which will propagate the error out of the function (which now must have ! in its signature). Or you can use `result = foo() catch default;` or `result = foo() catch unreachable;`. The former substitutes a default value, the latter is undefined behavior if there's an error (panic, in debug and ReleaseSafe modes). Or, just `result = foo();` gives `result` an error-union type, of the intended result or the error. To do anything useful with that you have to unwrap it with an if statement. It's a different, simpler mechanism, with much less impact on performance, and (my opinion) more likely to end up with correct code. If you want to propagate errors the way exceptions do, every function call needs a `try` and every return value needs a ! in the return type. Sometimes that's what you need, but normally error propagation is shallow, and ends at the first call which can plausibly do anything about the error.
- sixfiveotwo 2y agoThank you for your input, I stand corrected. So as I understand it, it works somewhat like the result type of rust (or ocaml), or the haskell either type, but instead of being parameterized, it is extensible, isn't it?
- samatman 2y ago
- planede 2y agoHow do you define a buffer overflow?
- theeandthy 2y agoAgreed 100%. C is what it is and that’s a good thing. However, if I were to request a feature to the core language it would be: NAMESPACES. This would clean up the code significantly without introducing confusing code paradigms.
- hgs3 2y agoNamespaces are nice, but to my knowledge require name mangling which isn't a thing in C. I'm curious what you mean by "clean up the code significantly" and "confusing code paradigms" because in C you typically prefix your functions to prevent name collisions which isn't confusing or too noisy in my subjective opinion.
- theeandthy 2y agoYeah you’re right. I guess folks who want C++ stuff should just use C++… I guess I should have reworded. I don’t expect that feature in C, but if I were to reinvent C today I would keep it the same but add namespace and mangling. Adding an explicit prefix to every function call is a lot boilerplate when it’s all added up.
- pjmlp 2y agoName mangling is an implementation detail to fit into UNIX linker design space, not the same approach as other compiled languages with modules, with their own linker.
- gpderetta 2y agoAlso name mangling (which in this case would simply be appending the namespace name to the identifier) would be trivially implementable in C. In fact on some targets the assembler name of identifiers doesn't always match the C name already. Although as someone almost always explicitly qualifies names, typing foo_bar is not very different from foo::bar; the only minor advantages are that you do not have to use foo:: inside the implementation of foo itself and the ability to use aliases.
- gpderetta 2y ago> a simple statement like `a = b++;` can mean multiple constructors being called, hidden allocations, unexpected exceptions [...] Yes, nothing like that is possible in C https://godbolt.org/z/Ge4EqzznT https://godbolt.org/z/Ge4EqzznT
- jay-barronville 2y agoHaha. You can’t be serious—what’s the likelihood of running into C code like this in anything remotely serious (compared to the millions upon millions of lines of innocent-looking C++ code that does like a dozen different things under the hood)?
- gpderetta 2y agoNo true Scotsman. I assume you haven't looked at the expansion of errno lately? edit: also https://github.com/KxSystems/kdb/blob/master/c/c/k.h https://github.com/KxSystems/kdb/blob/master/c/c/k.h
- quietbritishjim 2y agoThe difference is that in C++ it's expected that you'll overload operators, provide implicit conversions and throw exceptions. Of course you can write terrible code in C but it is not common accepted practice to hide a longjmp in a macro disguised as an identifier.
- gpderetta 2y agopthread_cleanup_{push,pop}
- jay-barronville 2y agoThe funny thing is, examples of macro craziness only strengthen my point, because C++ inherits all of that in addition to its hidden behaviors and magical semantics. It’s rare to find serious C code doing a lot of crazy things behind macros. In my experience, the few exceptions I can think of include the GMP library and data structure-related code trying to emulate generics (mostly hash tables).
- tsegratis 2y agowishlist 1) labels as values in standard 2) control over memory position offsets, without linker script other than that a few more compiler implementations offering things like checked array bounds, and a focus on correctness rather than accepting the occasional compiler bug the rough edges like switch fallthrough are rough, but easy to work around. They don't need fixing (-pedantic fixes it already, etc) maybe more control over assembly generation, such as exposing compilation at runtime; but that is into the wishful end of wishlists
- hgs3 2y ago> Please don’t ruin C. Exactly this. C++ folks should not approach C like a "C++ lite". I appreciate the authors candid take on the subject. As for defer, there is some existing precedent like GCC and Clang's __attribute__((cleanup)), but - at least for me - a simple "goto cleanup;" is usually sufficient. If I understand N3199 [1] correctly, which is the authors proposal for introducing defer in C, then "defer" would be entirely a compile-time construct. Essentially just a code transformation to inject the necessary cleanup at the right spots. If you're going to introduce defer to C then that does seem like the "best" approach IMO. [1] https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3199.htm https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3199.htm
- sixfiveotwo 2y ago[dead]
- smallstepforman 2y agoWe C++ devs haved moved away from C decades ago, and frankly dont even think of it any more, and will never go back. Its a relic of its time, like DOS, Amiga etc. RAII is a big feature we can no longer live without. The type system and overloading is fantastic. And std::vector is a magnificant feature. A language without these features is a relic for us C++ devs. And yes, I also agree that C++ has WTF insanity, like 17 or so initialisation quirks, exceptions in general (primarily to address failures in constructers, surely there must be a better way, also OOM / bad_alloc is a relic from the past), and unspecified sizes for default built in types (thats C heritage).
- uecker 2y agoI moved from C++ back to C and found that I am much more productive not worrying about a lot of things. But it takes a while to figure out how to do things in C because almost nothing comes out-of-the-box.
- atn34 2y ago> provided you understand the semantics of your target architecture Unless you're writing inline assembly or intrinsics or something like that, the semantics of your target architecture are quite irrelevant. If you're reasoning about the target architecture semantics that's a pretty good indication that what you're writing is undefined behavior. Reasoning about performance characteristics of your target architecture is definitely ok though.
- riku_iki 2y ago> a simple statement like `a = b++;` can mean multiple constructors being called, hidden allocations, unexpected exceptions, unclear object hierarchies, an overloaded `++`, an overloaded `=`, etc. its just mean if you need that logic, in C you would write lots of verbose less safe code.
- pjmlp 2y agoOnly if you mean C as defined by K&R C, and its original use when porting UNIX.