It’s still an abstraction, but at least being aware of memory management, copying vs referencing, etc are hugely important concepts that ML languages can hide.
It’s still an abstraction, but at least being aware of memory management, copying vs referencing, etc are hugely important concepts that ML languages can hide.
C is in an odd position right now to argue it is how the machine is really working. Computers are more complicated since bigger caches entered the picture. Hell I do not think even ASM is a good approximation on how machine really work given the data dependencies will make stuff being processed in parallel instead of sequentially.
What you could argue is that C is the archetype for an imperative language procedural language with a clean mapping to ASM. That is different than how the machine works. Simpler architecture have less distance between their ASM and what is really hapenning.
C really isn't as great for this as is often suggested either, not for decades at least
K&R's original compiler on an actual Digital machine from that era makes the case best, but remember this is the era when if you hot loop over modifying a variable your compiler is going to emit memory stores for each iteration - because that's what you wrote, isn't it? No modern C compiler would do this because it's awfully slow.
Likewise that iteration of C doesn't have what you'd recognise as function prototypes, it doesn't care whether your function takes six arguments, here are six arguments the first two are integers, good luck with that. In assembler that makes sense, but you don't do that in modern C either.
C is still a close-to-the-metal language, but it is programming an abstract machine and it is important that the programmer knows that's not really how the machine works, if you want to learn about the machine you will need to write at least assembler and possibly just go learn electronics. Good luck.
It's good enough for undergraduate teaching. In C, you can easily explain the relation between a struct definition and its layout in memory. It's much more difficult in Caml (what's the relation between an algebraic datatype and its layout in memory?) or Java (which introduces pointers that you never asked for).
We (University of Paris-Cité) are teaching Java in first year, then C and Caml in second year, with seemingly good results.
Likewise packing isn't allowed by the standard, so you'd again only need to talk about packing if you want to.
This seems like a reasonable place to start. Like the way driving school teaches you a U-turn but not a J-turn. Is a J turn actually a thing you might need? Maybe, but it's definitely not where we should start.
This is exactly what makes it great for teaching. Students don’t need to know actual arch or hardware details. They just need to grasp the core concepts of what is happening on the hardware.
For that purpose the ideal teaching language is lower level than python, javascript, Ocaml, etc without diving into nitty gritty arch specifics.
C is the undisputed champion in that domain.
C is still heavily abstracted. Modern OS evolved in conjunction with C s.t. it behaves like a C runtime simulating a PDP11 (I remember reading a nice article on that). To learn system, what you need is an OS course, not C. I'd say the current C-based stack is in a quite embarrassing state.
Also somehow the implied argument that computing hardware and operating systems simulate a PDP-11 for the sake of C is completely backwards. Historically, there were other approaches, e.g. processors designed for object-oriented programming or actors etc.. All those were not very successful.
UNIX for PDP-7 in Assm -> UNIX for PDP-11 in Assm -> UNIX for PDP-11 in C
https://news.ycombinator.com/item?id=42644851
and
A. you reduce impedance mismatch, but making the new language using similar concepts and mechanisms
--
You're right, this table is just a coincidence, probably derived from the standard math notation, and used in many PLs like FORTRAN and Python ;)
PDP-11 C
INC R ++i
DEC R --i
ADD src, dst dst += src
SUB src, dst dst -= src
(R)+ *p++
-(R) *--p
X(R) p[x]
@(R)+ **pp++
@X(R) *p[x]
BR label goto label ; near jump
JMP label goto label ; far jumpIt would also completely contradict the whole idea that everything today simulates the PDP-11 design because of C, as few architectures have this auto-in/decrement addressing modes despite C having native syntax.
PDP-11 was hugely influential in the later designs of various Hardware, Software, OS, Languages etc. See for example; Dave Cheney's What Have We Learned from the PDP-11? - https://dave.cheney.net/2017/12/04/what-have-we-learned-from... The conclusion from the article;
While its development was sometimes chaotic, and not without its flaws, the PDP-11 is at the intersection of many threads of history.
Hardware, software, programming languages, operating systems, have all been influenced by the PDP-11. I wager there is not a single person in this room who cannot trace the lineage of the language they work with, the computer they use, or the operating system it runs, back to the PDP-11.
And that is worth celebrating.
While the PDP-11 instruction set was certainly influential in the design of the "C Abstract Machine" the latter was generalized to accommodate other architectures extent at that time (eg. Honeywell 6000, IBM System/370) with enough flexibility that you can implement a C compiler for almost any architecture you can think of. That is its strength.
David Chisnall's criticisms in his C Is Not a Low-level Language: Your computer is not a fast PDP-11 (https://queue.acm.org/doi/10.1145/3212477.3212479) has to do mainly with the fact that the abstract machine was serial execution with no concept of memory protection/models. But this very flexibility is what makes C easily portable to dinky little MCUs which do not have those features while allowing the programmer to explicitly program those using libraries on more complex processors with lots of parallel cores, mmus etc.
Thus a single thread runs on a "C abstract machine" on a core (i.e. the bare minimum) and it is up to the programmer to manage interactions between the threads on various cores. We have lost nothing but perhaps burdening the programmer with more knowledge of hardware complexity which was an acceptable tradeoff then. Note also that there already exists various extensions to C to handle parallel programming directly eg. "Concurrent C" by Narain Gehani et al. The industry however chose to settle on external libraries and optional thread support in C11 again keeping with its minimality and flexibility mantras.
However, it is a good article to read and understand low-level multiprocessing issues.
Those that don’t need low level details can spend their professional career in languages like python and JavaScript while still have a sense of what lies beneath the abstraction.
For those that do want or need to go deeper, C is an excellent jumping off point into ASM and arch specifics.
Deliberate efforts to surface this deeper layer back to the programmer-facing ISA (VLIW, notably Itanium) failed spectacularly outside of niche applications. C and its contenders can't simply hop over the CPU abstraction presented to them without having to map backward from some lower-level model to the surface ISA it's forced to target then having the rug pulled every few years due to microarchitectural changes. It has managed to evolve surprisingly gracefully over decades from tiny 16 and 18-bit machines, sometimes segmented, to 32 and 64 bit machines, and even accommodating SIMD reasonably gracefully via intrinsics (which is about the best we can do at present, as CPUs continue to diverge wildly in this area, though I'm begrudgingly impressed by what modern compilers can do via autovectorization to optimize simple loops).
Well some procedural language would do the trick.
The main issue is see is people jumping straight into OO or functional programming and developing a habit of over-abstracting everything.
I think something like Odin which is purely procedural but has less historical cruft and offers modern affordances would be a great choice.
C is absolutely worth learning but students will spent a lot of time learning to cope with its sharp corners and subpar standard library. Some might give up before they discover the joys for programming.
Lisp can teach you everything about what logically a program is, how computation itself can be expressed - code as data, recursion, and abstraction built from almost nothing, and C can teach you what a program physically is - memory, pointers, and how the machine actually runs it.
I have gone through over a dozen of different PLs, each time thinking that my skillset was improving but I still sucked at it, and just needed to learn yet another language to truly get better at programming. Turns out, I just needed to grok Lisp.
Don't be like me, don't waste your lives wandering around, following a map everyone's holding. Majority not only can be wrong, but as history teaches us - it is wrong most of the time. Instead, follow those few that are looking for shortcuts. They may not have most popular opinions, but they might be on the right path after all.
Most modern languages have ways to automate parts of memory management, and rightfully so. But you should at least be somewhat aware of what is going on under the hood.
What low level thing do you think C can do, but Modula-2, Ada or Object Pascal are unable to provide the comparable feature?
Also taking into account possible C extensions not covered by ISO.
It’s also popular enough that there is an abundance of high quality learning resources for beginners.
No other language threads that needle as well as C.
Lifetimes are a compiler safety abstraction and mostly unrelated with how the computer runs your program.
Learning a language like C helps to understand why lifetime annotations are needed and how the compiler uses them.
The hardware machine model used by the C programming language is completely obsolete and very different from how modern CPUs work. Even for abstracting PDP-11 it had defects and omissions.
The fact that C is indeed more transparent than many other more abstract programming languages does not make it good enough for understanding how the CPU runs your program. Believing that the CPU works within the straitjacket of the C language is dangerous, because much more than half of the instructions of a modern CPU cannot be directly expressed in C (though a good optimizing compiler can sometimes infer when such instructions can be used), and the C language does not even have the data types that are used by many hardware instructions.
The relationship between the C language and how a computer runs a program is exactly the opposite of what the previous comment says.
For someone who knows how a CPU runs a program, it will be easier to understand how a C program is run than how a program in another language is run, because for the latter there may be very different runtime library implementations, whose behavior cannot be guessed by looking at a source program, without having supplementary documentation about the compiler or interpreter that is used.
On the other hand if someone knows only the C programming language, their mental image about how the program is executed is likely to be very wrong for any non-trivial program.
For learning an assembly language, the prior knowledge of C is more a handicap than something helpful, by creating bad habits, like using incorrect implicit conversions, inappropriate integer data types, not caring about interactions with the cache memory or memory access ordering, choosing between alternative expressions those that are more inefficient in hardware (e.g. in many modern CPUs accessing data through indices is more efficient than through pointers, but a lot of legacy C programs use pointers to access arrays, under the wrong assumption that this is more efficient), etc.
I agree with the top poster that languages from the ML family are a very good choice for a first-learned programming language.
For a programming language to be known before any assembly language, I believe that even Fortran is much more appropriate than C, because it is much less misleading about how a modern CPU works, and I say this despite the fact that for decades I have been writing programs in the C language, for embedded computers (but before learning the first assembly languages I had experience mostly in Fortran, and to a lesser extent in LISP and COBOL).
For a few years starting with 1990, I liked C very much, because with the Microsoft and Borland C compilers for IBM PC it was a great improvement over the Pascal, Fortran or Basic to which I had access previously, but today, 36 years later, I do not think that there exists any application for which learning or using C makes sense, despite the fact that we will remain stuck with it in many places for many more decades in the future.
Even the ancient Fortran remains more useful than C today, because for many computational applications most modern programming languages are crippled in comparison with it, by poor support for array operations, while C does not have any intrinsic advantage over alternative programming languages. A "C" done in the right way is "D", so it would be better for learning, though it still inherits from C some questionable features, which would not exist in a clean design.