The main thing that C does is force you to use "pointers".
And "pointers" force you to learn "indirection".
And, "indirection" is like "recursion" in being one of the absolute core concepts you MUST learn in order to program well.
Rust is actually somewhat obscuring "indirection" a bit because instead of a single thing to keep track of you now have two things--(base, index)--which you can think of as a single unit but newbies are going to get confused. Because of the immutability, people are throwing around data structures of indices like in very old FORTRAN code common-block programming.
I like this article a lot. There are two ways you can think of looking at the field of programming: * As a continuum from "low level" to "high level". * As a giant bag of topics: strings, heap, hash tables, machine learning, garbage collection, function, instruction set, etc. If your goal is to have a broad understanding of CS, you want to explore the whole continuum and many topics. C is great for that because it ex…
I still don't understand why you should learn C if you really want to know "how the computer works". The author says "By learning C, you can learn more about how computers work", but why taking the long path if the short path exists? Just learn assembly language. It doesn't take that much time, a few weeks are enough to get a good insight into how simple CPU work (registers, memory, no difference between numbers and pointers, a stack that grows, etc.). Why spending time on learning C if you already know that this is not what you actually wanted? That's like wanting to learn German and then learning Dutch because you heard it's easier than German.
To all the people who are going to reply "But machine language is not how the CPU works!!1!!111": Yes, I know that real modern CPUs translate the machine code to something else internally and that they do things like register renaming and microinstruction reordering and they have ports and TLBs and instruction caches and data caches etc. But CPUs still have registers and an instruction pointer and most of them have a stack pointer and interrupts and exceptions. C will definitely NOT help you to understand those things.
I suggest maybe you learn C :) or Rust. There are significant performance and strategy differences between the stack and the heap. On the stack, allocation is cheap, deallocation is free and automatic, fragmentation is impossible, the resource is limited, and the lifetime is lexically scoped. On the heap, allocation might be cheap or it might be expensive, deallocation might be cheap or it might be expensive, fragmen…
Those performance and strategy differences only exist inside C or other high-level languages, as a result of abstractions created by those languages. They are not in any way reflective of "the computer." By all means it's certainly a valuable abstraction, one that most high-level languages support. But that's like how functions are a valuable abstraction, or objects, or key-value stores, or Berkeley sockets. Learning…
You are more than welcome to use a stack in an assembly language too. What you say certainly can be true, but many architectures include dedicated stack pointer registers and operations to manipulate them, either special-purpose (AVR, __SP_H__ and __SP_L__, push, pop) or more generic (x86 %sp, push, pop). I'd argue that functions exist at the hardware level to some extent too in architectures that support, for instance, link registers (PPC $LR) and special instructions (call, ret instead of a generic jump family). Calling conventions, sure, are an attraction over functions.
I suggest maybe you learn C :) or Rust. There are significant performance and strategy differences between the stack and the heap. On the stack, allocation is cheap, deallocation is free and automatic, fragmentation is impossible, the resource is limited, and the lifetime is lexically scoped. On the heap, allocation might be cheap or it might be expensive, deallocation might be cheap or it might be expensive, fragmen…
> On the heap, allocation might be cheap or it might be expensive, deallocation might be cheap or it might be expensive, In other words it might behave just like "the stack"; the differences between different kinds of "heaps" are as large or larger than the difference between "the stack" and "the heap". > fragmentation is a risk, the resource is 'unlimited', and the lifetime is unscoped. None of these is true in all…
I'd argue that given special purpose registers exist on most platforms to support a stack, and instructions dedicated to manipulating them (x86 %sp, push, pop) that the stack is in fact a hardware concept. The heap, however, is left as an exercise to the reader.
I suggest maybe you learn C :) or Rust. There are significant performance and strategy differences between the stack and the heap. On the stack, allocation is cheap, deallocation is free and automatic, fragmentation is impossible, the resource is limited, and the lifetime is lexically scoped. On the heap, allocation might be cheap or it might be expensive, deallocation might be cheap or it might be expensive, fragmen…
Well, you can just write a naive allocator that just bumps pointer and never deallocates things :). To appreciate stack vs heap, you have to learn a bit about memory management and some basic algorithms like dlmalloc, imho.
Definitely, as I've replied elsewhere, the stack is in fact a hardware concept whereas the heap is left as an exercise to the reader.
Except it isn't, not really. Even just the distinction between the stack & heap is wrong. They aren't different things, just different functions called on the otherwise identical memory. It's why things like Go work fine, because the stack isn't special. It's just memory. malloc & free are also totally divorced from how your program interacts with the OS memory allocator, even. GC'd languages don't necessarily sit on…
Stack accesses _are_ different in hardware these days, which is why AArch64 brings the stack pointer into the ISA level vs AArch32, and why on modern x86 using RSP like a normal register devolves into slow microcoded instructions. There's a huge complex stack engine backing them that does in fact give you better access times averaged vs regular fetches to cache as long as you use it like a stack, with stack-like data…
The stack pointer is just that, a pointer. It points to a region of the heap. It can point anywhere. It's a data structure the assembly knows how to navigate, but it's not some special thing. You can point it anywhere, and change that whenever you want. Just like you can with any other heap-allocated data structure.
It occupies the same L1/L2 cache as any other memory. There's no decreased access times or fetches other than the fact that it just happens to be more consistently in L1 due to access patterns. And this is a very critical aspect of the system, as it also means it page faults like regular memory, allowing the OS to do all sorts of things (grow on demand, various stack protections, etc...)
> Your statement is equivalent to saying "there's no difference between pointers and integers" - technically, they are both just numbers that live in registers or somewhere in memory. In reality, that approach will not get you far in computer science. No, because in reality those are handled by different computational units. Integers are handled by the ALU, and pointers are handled by the loader. They are distinct th…
> No, because in reality those are handled by different computational units. Integers are handled by the ALU, and pointers are handled by the loader. They are distinct things to the CPU. I strongly disagree. Yes some execution units are more let's say "dedicated" to pointers than other, and obviously ultimately you will dereference your pointers, so you will load/store, but compilers happily emit lea to do e.g. Ax9+B…
Exactly, this will vary wildly between architectures. I would accept the statement on, for instance, a Harvard architecture but the world is a lot more nuanced in the much more common von Neumann architecture.
I suggest maybe you learn C :) or Rust. There are significant performance and strategy differences between the stack and the heap. On the stack, allocation is cheap, deallocation is free and automatic, fragmentation is impossible, the resource is limited, and the lifetime is lexically scoped. On the heap, allocation might be cheap or it might be expensive, deallocation might be cheap or it might be expensive, fragmen…
> Your statement is equivalent to saying "there's no difference between pointers and integers" - technically, they are both just numbers that live in registers or somewhere in memory. In reality, that approach will not get you far in computer science. No, because in reality those are handled by different computational units. Integers are handled by the ALU, and pointers are handled by the loader. They are distinct th…
No no, haha, that's also not quite right. You can save the stack frame to memory then load it back which is what green threads do [1]. This includes the register state also, which is not what we're talking about here when we say "stack" -- we're referring to stack variables, not full frames. Full frames are even further from the heap, by virtue of including register states which must be restored also.
Although, it sounds like you're agreeing with me that there is in fact a difference between the stack and the heap because to your point one exists supported at the hardware level with instructions and registers, and one doesn't exist or can exist many times over. Hence, different.
But in addition to the mismatch between the abstractions provided and the real hardware, C qua C is missing a huge number of abstractions that are how real hardware works, especially if we pick C99 as Steve did, which doesn't have threading since that came later. I don't think it has any support for any sort of vector instruction (MMX and its followons), it doesn't know anything about your graphics card which by raw…
But that's fighting against a straw man. When people advice others to "learn C," they don't mean the C99 specification they mean C as it is used in the real world. That includes pthreads, simd intrinsics, POSIX and a whole host of other features that aren't really C but what every decent C programmer uses daily. As you point out, modern architectures are actually designed to run C efficiently, so I'd say that's a goo…
> . Pascal is at the same level as C, but no one says "learn Pascal to learn how computers work" because architectures weren't adapted according to that language.
They used to say it though, before UNIX derived OSes took over the computing world.
> What that means is that most C operations can be easily translated into the machine code equivalents and that makes debugging of compiled code so much easier. A disassembly of many binaries will reveal their C roots by function calls being equivalent to jump statements, as an example. If you have the C source then you can usually figure out what's going on. This is very much not true in my experience. Optimization…
Why would you turn on optimization while debugging?
Because with them turned off, the executable might not fit on the target platform, for example.