5 ms·
No C is very very simple. That’s why it’s not easy :) No but really, there isn’t anything to it. Just think about how a CPU sees memory and registers and it be
by IgorPartola 8y ago
No C is very very simple. That’s why it’s not easy :)
No but really, there isn’t anything to it. Just think about how a CPU sees memory and registers and it becomes intuitive. But that doesn’t make it easy to use because it doesn’t give you any guard rails to show you the one true way to accomplish a task.
- mmastrac 8y agoI agree partially - overall C's promise was that it was a light wrapper over top of registers and memory, absolutely. The issues with C are around some of the weirder bits of the parsing process, namely the context-sensitive parser and the "interesting" corner cases of how the C preprocessor works. Edit: Here's a fun example - try it with and without the typedef line commented out: typedef int T1; int main() { const T1 (*T1); printf("%d\n", _Generic((T1), const int *: 1, const int (*)(int *): 2)); }
- IshKebab 8y agoAlso the rules around undefined behaviour, type aliasing and so on are definitely not simple.
- saagarjha 8y agoSeems like a "most vexing parse", but for C!
- acura 8y agoIs it expected to compile without the typedef?
- foldr 8y agoAmazingly, it does compile. I believe the declaration that looks as though it shouldn't (because T1 isn't defined) compiles as a function pointer declaration, where the first T1 is the name of the variable defined and the second is, in syntactic terms, the name of the function. With the typedef commented out, you can delete the second T1 on that line. Note that the type of a variable defaults to int in C, so you can write 'const a' instead of 'const int a'. The return type of a function also defaults to int.
- saagarjha 8y agoClang accepts it, but GCC does not. I suspect there's a bug here.
- foldr 8y agoIt fails because gcc appears not to support _Generic by default, but that's not the important part of the code. If you comment out the printf, or figure out which compiler options to set, gcc will compile it.
- boomlinde 8y agoI think C has broken that promise since. It now targets an abstract machine, and a lot of behavior that would make sense for a light wrapper compiles to something counter-intuitive in an optimizing compiler. In a different time, that something was left undefined by the standards might have meant that the result might be some artefact of the target machine architecture, but with aggressively optimizing C compilers the undefined behavior is more likely to be an artefact of the optimizer. A thin light wrapper wouldn't compile this: static int doub(i) { return i + i; } int main(void) { int i; int sum; sum = 0; i = 5; while (i--) sum += doub(i); return sum; } into this: movl $20, %eax ret I'm not programming memory or registers, I'm instructing an optimizer.
- janekm 8y agoWhat compiler does that? None of the ones I tried on god bolt seem to (after fixing the missing type on the function parameter)? And what's the undefined behaviour here?
- foldr 8y agoYou're probably not turning on optimization. For example, gcc 8.3 with -O3 gives the result that the OP posted. There's no undefined behavior in that example, as far as I can see. The point is that the compiler optimizes away all of the actual computation, since it's able to determine at compile time that the result will always be 0x20.
- boomlinde 8y agoI genuinely forgot to specify the type of the function argument, but that's not considered an error in C which will default to int when the type is omitted. If the compiler you're using objects to that it's probably for the better, but it's still legal C. There is no undefined behavior in the example. It's meant to further illustrate my point that C is not a light wrapper around memory and registers. In the example, it's just a deceptively imperative looking way of describing the return value to the optimizer. The compiler in this case was gcc with -O3. For an example of behavior that is straight forward in the machine but is undefined in C, try signed integer overflow. Intuitively on x64 it should behave just like ADD, possibly adjust the carry and overflow flags and wrap around. In C, the behavior is entirely undefined. Some operation that results in UB is low hanging fruit for the optimizer. If it can deduce that the operation will result in UB, in this case signed integer overflow, it might omit the operation altogether.
- AnaniasAnanas 8y agoC is far from simple, a simple language would be something like Scheme, Forth, or even SML. It so happens that the C standard (which is nearly 1k pages long nowadays) hides a lot of pitfalls, making the job of optimisers, implementators, and regular programmers miserable.
- fooker 8y agoGenerating performant code from Scheme, Forth or SML is far more difficult compared to the C family of languages.
- AnaniasAnanas 8y agoAre you sure about that? Forth is famous for being extremely fast. As for SML, assuming a variant with manual memory management (which I do not consider to add complexity in C), I can't think of any reason on why it would be more difficult to optimise.
- boomlinde 8y ago> Forth is famous for being extremely fast. Have you seen that claim backed up by any evidence produced within the last 25 years?
- fooker 8y ago> I can't think of any reason on why it would be more difficult to optimise. https://benchmarksgame-team.pages.debian.net/benchmarksgame/faster/ghc-gpp.html https://benchmarksgame-team.pages.debian.net/benchmarksgame/... The main reason a language like SML or Haskell is difficult to optimize is because it is incredibly difficult to canonicalize for the purpose of making peephole optimizations and abstract interpretation effective. https://sunfishcode.github.io/blog/2018/10/22/Canonicalization.html https://sunfishcode.github.io/blog/2018/10/22/Canonicalizati...