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.
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));
}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.
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.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.
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.
Have you seen that claim backed up by any evidence produced within the last 25 years?
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/Canonicalizati...
C macros on the other hand, should die. I use C a lot but never ever use c macros, they are evil. Extremely hard to debug, confusing and very weak.
When we want macros, we use Lisp, that have proper macros.