Meh. Learn any language and “the rest will come” if you then learn other languages. Some of these points don’t lead to C’s being a better first language or pedagogical language — like, why should a learner care that C is so fast? I teach HS computer science and we do use C, in our Data Structures course, after students have used Racket, Python, and Go. Using those other languages lets us cover interesting and important topics like recursion, data abstraction, concurrency, and more, all while doing projects that hold kids’ attention (sometimes using outside libraries for image or sound processing, e.g.). C is well-suited to the data structures course where students have to think carefully about memory and efficiency.
I can certainly imagine a different sequence, where students learn logic gates, hardware, assembly, then C, working their way up to higher levels of abstraction. Even in this case though, the goal isn’t to jump in and start writing big programs in C right away.
I think people should understand pointers, heap vs. stack and other things. even in higher level languages a lot of performance problems can easily be explained if you understand how memory works. I often see people allocating and copying huge arrays back and forth and then thinking they need bigger machines.
I agree that people should understand pointers, heap and stack. However, there's going to be drop outs from the class if these things are focused on too early. Of course, this is open to debate, which is why we have this type of healthy discussion. You might be right in your assumption that it's better to introduce these concepts earlier. In know that when I was in high school, these concepts were taught and served as a foundation for further learning. It's out of vogue now to teach these concepts to kids. But, you might be right. I need to think about this more. Thank you for your posts.
Dropouts are ok and honestly a good thing. Not everyone is cut out to be an engineer.
I think the commenter is correct. C makes you learn how a computer works. As your code gets more sophisticated you'll eventually have to know this or you can't take your work to the next level. I don't know C all that well and I'm currently facing gaps in my knowledge that would be filled in had I spent some time with a C compiler.
"Dropouts are ok and honestly a good thing. Not everyone is cut out to be an engineer."
Agreed. Unfortunately the job market pushes people into professions they are not suited for. I bet 90% of lawyers do it for the career prospects and not because they like law.
That's like saying everyone should be introduced to Math via integration rather than counting because not everyone is cut out to be a Mathematician.
I accept your premise that we don't want to push people into professions they are not capable of doing well in. But basic knowledge of programming is becoming important enough that I think providing a gentle intro is wise. Like Math and Physics, you can ramp up the difficulty in later classes.
I wouldn't recommend using C# for learning these concepts. Unsafe mode exists but it's not very straightforward in my view. I don't think I could understand unsafe mode without prior knowledge of C.
Agreed! It's a question of how you introduce them to these ideas. One option is to start at the bottom (hardware) and go up, so that pointers feel like a welcome abstraction when students encounter them in C. (They will already be used to thinking about memory addresses in assembly.)
The other option, and the one I choose, is to go from high-level languages down. What I'm not so sure about is just throwing people in in the middle, so that they are trying to grasp abstractions like functions and variables at the same time as they are trying to understand pointer arithmetic.
In the subset of Racket we use, all values are immutable, so an understanding of memory (stack vs. heap) is less necessary. We do still talk about "frames" to understand lexical scope, though, and students get used to thinking of each function call using up a new "frame" of memory to store its local variables. This allows them to understand the memory efficiency of recursive functions.
We then move onto Python, and _do_ talk about an "environment" (stack) and a "heap" (I call the heap "object-land" because objects live there). This enables us to talk about garbage collection. More importantly, students get used to thinking about variables as being bound to either primitives or pointers to shared memory.
In Go, there _are_ pointers, and students get used to thinking about memory addresses as values. They learn that pointers are useful both for sharing mutable references, and for avoiding expensive copies. (In Go, arrays are values, and passing a large array to a function copies it.) But none of the sharp edges of C's pointers are present. Go does "escape analysis" so that if you return a pointer to a local variable, that local variable is allocated on the heap; it is safe to point to anything and if you do, it won't be garbage collected. There's no pointer arithmetic.
By the time they see C, in the third-year course, a lot of the pieces are in place to have a good understanding of how the stack and heap (and manual memory allocation) really work. And along the way, because they were using higher-level languages, the students have been creating cross-platform, complex programs (web servers, games, etc.) that they're proud of. :-)
Re: sibling's comment that "some people aren't cut out to be engineers," I think that's a somewhat dangerous attitude. I think the study of computation is profound & rewarding and should be part of a liberal arts education for everyone; even apart from that, programming is also a useful skill for people who won't get a job as an engineer.
"Re: sibling's comment that "some people aren't cut out to be engineers," I think that's a somewhat dangerous attitude. I think the study of computation is profound & rewarding and should be part of a liberal arts education for everyone; even apart from that, programming is also a useful skill for people who won't get a job as an engineer. "
Agreed that some basic skills are a good thing. But in the same sense that I have some grasp of writing but never will be a good writer or enjoy it, a lot of people are just not cut out to be software engineers. It would be a sad world if everybody had the same talents.
Thank you for posting this reply as I agree with many of your points. Also, I didn't know about Racket, which looks like a good tool for teaching. I would like to learn more about how you use Racket. Many years ago, my high school taught fortran first and then assembly as the AP course. This was later changed to Pascal. I believe that assembly was used for some of the same reasons the author is suggesting C. However, I also think that learning Python and Go is a better starting point. Learning about pointers is going to cause a significant portion of the class to drop out or tune out. At some, they will need to learn more about memory addresses, but I think that it can come later when performance becomes more of a concern.
For our first semester, we use a modified version of "How to Design Programs": http://www.ccs.neu.edu/home/matthias/HtDP2e/ Highly recommended for teaching! I do teach pointers, but in stages — so that students can understand the mutability behavior of Python lists and other objects, we talk about how a Python list variable, for instance, is _really_ storing a memory address of a list. When we move to Go, they actually use pointers, but unlike C's, it's hard to shoot yourself in the foot with Go's pointers. When we finally get to C, pointers seem natural.
I whole-heartedly agree. A lot of C-purists will say things like, "you can't understand programming until you understand pointers". I think it's the other way around. I think one will have quite a difficult time trying to understand how pointers work if your very first language is C where their usage is so prominent and in many cases arbitrary due to a systemic aversion to copying data for no other reason than micro-optimization.
My first language was C, back in the early 90s -- from nothing but the K&R book -- and I did learn pointers before I learned my next language (like truly grok them, have the ability to visualize the concept viscerally). When people in interviews ask me to tell them about the most technically challenging thing I've ever done, I tell them about learning pointers in C as a teenager. Taking everything in its context, nothing else in technology has gone that far in pushing my intellect.
I agree, it's extremely difficult to go from zero to "understands pointers". I think it's a hurdle that a lot of people have failed to clear and have quit programming to go into other, less-lucrative fields. I don't think it has to be that way, and I don't think there is any virtue in it, either.
I was a C64 kid. Basic had peek/poke to read/write memory addresses directly. I still remember 53280 and 53281 as bytes to flip to change screen colors. So it came to me naturally when I saw assembler and Pascal pointers later on. It just depends on how you grew up.
If you don't understand when you're working on a mutable version of a data structure vs a copy, you can't get very far. So at least that part of 'understanding pointers' is crucial in any language?
I don't know what companies you've worked at, but you can clearly get pretty far without having that sort of mastery over a particular language. I'm not saying it's good to stay in that state of ignorance, but it's definitely a thing, and I think the more gentle introduction to learning the topics that dynamic languages provide makes it more likely people will stick with programming at all, rather than quitting and changing their major to History or something.
>>but you can clearly get pretty far without having that sort of mastery over a particular language.
Completely agree. If this weren't true, the fizzbuzz test wouldn't exist.
The 90s were filled with businessmen creating Access databases that wouldn't scale, and Visual Basic's drag-and-drop capabilities created a cottage industry for controls consumed by non-programmers.
That's both good and bad. Bad for the obvious reasons (lots of crap software) but good in that for the most part people were able to build what they needed to solve problems and move on.
The trick is to benchmark that "crap software", not against the architectural marvel you would build, but against the Byzantine Excel spreadsheet (or worse, binder of paper forms) that would have been used otherwise.
Nowhere is "worse is better" more true than line-of-business software.
I'd agree, and even in Python, we end up talking about how some variables store "memory addresses," to explain the sometimes confusing behavior of variables bound to mutable objects. What's less essential is understanding all the ways that (C's) pointers can bite you: pointer arithmetic that results in invalid addresses, use after free, dereferencing uninitialized pointers, returning a pointer to a local variable from a function, and so on.
I agree - you can present a topic before the student is ready and confuse. That happens quite often, in fact. But it's not the fault of the language. And the student must have that background to be prepared.
C was the "universal assembly language." In its day, it was unimaginable that one would learn to program a computer without an understanding of the hardware underneath, or computer architecture in general.
>>a systemic aversion to copying data
But this isn't an issue with the C language or pointers, per se; an "aversion" to unnecessarily moving data is the very essence of optimization. [As part of choosing the right algorithm to begin with, of course.] As Michael Abrash wrote: "the fastest code is the code that never runs." So eliminating work (such as moving bits across a bus) is key.
I never claimed it was the fault of the language. It's the fault of advocates pushing it as a viable first language. My point on premature optimization was precisely because of that: optimization should not be a concern for raw beginners, and C programmers as a culture value optimization in all things, even to the point of error (fast code is not necessarily secure code).
Agree with you there & appreciate the clarification.
I would only add that many of those who obsess with optimization and "clever" programming, have never had to go back and maintain that code. I've looked at far too many modules and said "what was this idiot thinking?" before realizing it was my own work from years' prior.
With a bit of that perspective I now write code favoring clarity over cleverness/performance. And if it is unavoidable to write something dense, I document the hell out of it.
But you're right, there is a definitely a culture of writing impenetrable code full of clever & cute little tricks of the sort that come back and bite the author later. Prevalent in the C world, and I see it also in equal measure in Perl.
I agree that learning C gives you a great foundation to understanding a computer. I notice that a lot of people who have only learned languages like Java or JavaScript often have a fundamental lack of understanding of what's going on under the hood. Some assembly would be helpful too.
My SO decided to go to a web dev bootcamp a few years back. Before they went, I taught them 6502 asm so that they'd have some idea of how a computer works under the hood. It seems to have helped, and they were clearly the top of their class.
Albeit it's hard to separate the effects of that from simply living with someone who can talk with you at the end of each day of class.
If I were to go through it again, I'd do it with an MSP430 instead. The 6502's lack of registers that are the same width as the address bus (and therefore the multiple steps of indirection required for full pointers) obfuscated some of the concepts I was trying to convey.
The best class I ever took was a computer architecture course where we built up an 8-bit minicomputer from individual gates onward in a logic simulator. It was based on the Tanenbaum Structured Computer Organization and the Patt and Patel "Introduction to Computing Systems* books. By the end we were programming it in a limited dialect of assembly and also with a minimal version of C that compiled down to that assembly dialect. Very cool, and I've never been mystified by pointers since then.
I almost think a better post would be "Learn Computer Architecture and the rest will come". For that, I can't recommend http://nand2tetris.org/ enough.
> With C code, there’s no going into a program without fully knowing what it’s going to do at every step of the way...
This guy seems to be putting C on a pedestal. It's OK, for certain jobs. Like kernels, CLI utilities, C/C++ for games, etc. But even there Rust and Go and other tools are invading that space and doing well.
When I want to make something, I usually use whatever I think can get me to something usable fast. C is rarely the answer. I think the same holds for someone trying to learn programming. Python or Go removes a lot of cruft that new programmers shouldn't have to deal with, at first. They are good intros to "you want X? Well import X, then start using X.foo" instead of "You want X, well first let's start a Makefile so we can get our libs linked in properly depending on if this is debug or release, and then maybe CMake so we can use it on other systems, and so on and so forth..."
Agreed! I also agree that K&R "The C Programming Language" is a classic book that everybody should read, at least to admire how well written is it. And of course C is a good language to learn. Not as a "first language" but as one of the "languages to know".
>Also, C is flexible. You can do more with C than you can do with newer languages.
What?! Yes, you can do anything with it, only if your time doesn't cost. The same is true of Assembly code.
There are many languages that are more expressive and powerful than C, and some of them aren't really that slow.
-- And of course C is a good language to learn. Not as a "first language" but as one of the "languages to know".
This is how I feel about it. Better to learn with something a bit more forgiving (IMO something like Python is good, even if these days I prefer compiled languages), but as a 2nd, 3rd, or 4th language C or at LEAST C++ should be required. Having to actually deal with memory yourself makes such a huge difference in how you treat your programs.
I disagree. Some of my friend and me were able to implement by ourselves RB tree algorithms by the 18th day of our month-long formation, and only one of us knew how to write code before this (and we almost reached his level the day before, when we implemented linked list algorithms in C).
After this formation, we were able to write a hashmap algorithm that handled collision in less than a day and a half. I honestly don't think we would have been able to do the same if the tools we were given during our formation were python, php or javascript.
I don't think resolving the philosopher dinner problem in Go can teach you much. In C, it does. The biggest advantage C have is that you start from nothing, so you have to understand everything.
I would not understand BTrees this well if i did not first understand linked list. And i think the thing that taught me the most about OOP was writing a raytracer in C.
I feel like in the end most of it just comes down to how memory is managed and the type system, at least if you consider C++ instead of C.
Personally I just wish for something like C# without the garbage collector (and some good options for static compilation). Right now it just seems like the only two choices are C(++)/Rust, where your only option is to deal with everything manually even if the compiler or optimizer would know better, and most dynamic GCd languages, where your only option is summoning Cthulhu to do it for you.
I have slight hopes for Zig. Maybe there will finally be a real C replacement. I never understood why C has not been brought into modern context. Go is probably nearest but it has GC and C interop seems cumbersome.
Looks like a mash-up between C and Rust, though with somewhat funky syntax. Still, C(++) with no preprocessor and non-nullable pointers is a huge improvement on it's own, but is that full-blown CTFE with first-class types I see? Hot damn. I could definitely see myself using that if it evolves further.
In Common Lisp there are tricks to avoid doing dynamical allocations within your critical (read "requires high performance") code, so the GC doesn't bother you. Couple this with type declarations, "inline" declarations (which tell the Lisp compiler to inline certain functions) and, if you know what you're doing, the Lisp code will compile to pretty good (read: fast, optimized) machine language that will run seriously fast (at the same speed or close to the same speed than C or Fortran.)
So you can choose to avoid needing the GC on your high performance code, and enjoy the comfort of garbage-collected dynamic memory management on the code that is not so performance-critical.
Bigger question for me was when does one truly learn C?
Finishing C course on college didn't felt like I learned C. Nor implementing some common algorithms and data structures. [Lets mark my experience until this moment as `phase 1`] This attitude made me stay in C for a bit longer, getting my self interested in systems programming and that is when I got to see truly what programming and debugging C really is. I was learning and experimenting for 2 years with that, and I felt recently like I was ready to move on, (Python and Clojure taking over for the most part) and the conclusion was rather strange:
Everything after phase 1 didn't brought me that much, considering cost/benefit factor I was not impressed by the end results. While whole first few years and college in C were significant and I recommend it to everyone to learn C even up to a point where you write basic CRUD program that read some data from txt/bin and create linked list with that data and represent it, my venture into systems and lower level programming didn't bring me as much of "general" knowledge. Sure it was good, and I learned C++, and combining those two now I do some other things that are related to the things I learned during that period (like GPGPU), it wasn't that mindblowing as I expected initially.
I will never understand this trope of "pointers are hard to understand." Yes, they can get you in trouble if used without care. But hard to understand? What's so hard to understand about an address?
> I will never understand this trope of "pointers are hard to understand." Yes, they can get you in trouble if used without care. But hard to understand? What's so hard to understand about an address?
I don't think anybody ever said that pointers are hard to understand, they aren't. Go is a proof of this as even people coming from Python or Javascript can easily pick pointers up even if they have never heard about the heap and the stack before.
There are posters in this very discussion claiming that pointers were remarkably difficult to learn.
I'm firmly in the "WTF is the big deal?" crowd. Every time I see posts like that I think, "maybe I've thought I understood pointers just fine, and even used them without issue, for years and years, but somehow I actually don't understand them?"
I had a harder time with OO when I first encountered it than pointers, because I kept running into terrible "Sally wants to buy some lemonade from Tommy"-type introductions in crappy, cheap '90s C++/Java programming books and bouncing off it.
> I had a harder time with OO when I first encountered it than pointers
Pointers weren't too hard to learn (although, the deeper they're nested, the harder they are to think about, for me). OO felt immediately intuitive, using the "shape class, rectangle class, square class" examples. Recursion is the concept that I understood the general idea of, but had to do immense amounts of work to be able to visualize.
I think that everyone has their own areas that they learn easily, that others have a hard time with.
I agree with the last statement. I think it's best to learn basic computer architecture before learning C, which assumes a close relationship.
I took http://nand2tetris.org/ before learning C; starting from a single logic gate, you build memory, CPU, assembler, and a compiler for small language (you implement malloc and free yourself). [0]
Though some folks say it sacrifices depth for breadth, I feel very comfortable learning C now.
I will state that the article itself was a fluff piece. However by posting the title it will bring interesting discussion on hn. I do agree with the title, very much so.
I think the Java then C then whatever else you fancy is the best way to start programmers/software engineers out. Put them in a structured environment (strong typing, little room for catastrophic errors), then learn why the structure is there with C, then let them free roam to Python/JavaScript/Rust/Go with the experiences gained by the planning at the C level
> I think the Java then C then whatever else you fancy is the best way to start programmers/software engineers out.
I agree. Managing memory, pointers, and potentially problematic compiling should come after you know how to loop over an array (or list, or whatever). Java or C# are two great places to start.
I think learning java first would be very very detrimental to someone's coding skills. They would spend the rest of their careers trying to deprogram themselves from that useless way of structurng problems. For me the best would be first c, then assembly, then lisp. In my experience people That started with java and then learned python or js or something else tend to write horrible convoluted code.
I don't think anyone should go with a language that allow him to do bad OOP. In my experience, this lead to poor code in the beginning, even when this person is one of the most brilliant you ever met.
People that learned with C (Pascal too) piss more straightforward code on average, and have better debugging skills. And they are more "aware" of the computer.
PS: I did go C, assembly (had to rewrite some libc functions) then emacs lisp (before trying to write stuff in Common Lisp). I don't think going assembly will help a webdev much (even if he work mostly on backend), but lisp is definitly helpfull for anyone. Assembly is great if you like having some fun, but beside CTF and "that one time" (i had a really nasty C bug), the stuff i learned while writing assembly were not really usefull to me.
You are right about assembly. I was thinking c, then a little bit of assembly just enough to demystify it. I think learning just a little bit assembly makes you a braver programer hahaha. In any case if ound a lot of people really damaged by java. Its horrible what that language can do to some people's coding style and framework when thinking about problems.
> In any case if ound a lot of people really damaged by java. Its horrible what that language can do to some people's coding style and framework when thinking about problems.
What problems are you thinking of, specifically? In high school, I had a semester each of BASIC and C++, but my first serious programming was with Java in college. That is, beyond things like a "guess the number" game, getting into actual study of algorithms and data structures.
I'm mostly a C++ dev now, and I've never had complaints about my coding style. Then again, I never bought into the WidgetFactoryFactoryImpl kind of crap.
Indeed. Like what's the first thing you need to learn to even get a simple program running: that there has to be a class with at method. Insane garbage!
A language like C is already bad because there has to be a function before anything is done, instead of just a sequence of instructions to the machine to do something.
Imagine you told me "can you go to the cupboard and fetch the sugar?" and I said, "Nope: you must wrap that in an instruction sheet called "main", and then ask me to compile and execute it".
I largely disagree with this article. C has too many pitfalls that a beginner can either be discouraged by a non-working program, or not understanding the subtle gotchas.
Avoiding undefined behavior in C is difficult, even for experts. Failing to understand UB leads to very inconsistent, often latent, hard-to-debug situations long after the bug has occurred. Other languages - including assembly language - don't have this extremely hostile concept of UB. C encourages bad habits like ignoring error codes from I/O functions. Also, doing concurrency correctly in C (or Java for that matter) is a grand effort, but is easier in languages like Go and Rust.
Managing language minutiae in C, like function prototypes and header files, is something not found in newer languages. Another problem is having a global namespace instead of a more structured, modularized one (C++, Java, Python, etc.). And finally, manual memory management means that a lot of extra code is written (contrary to the author's claims about conciseness), and it especially makes string processing hard.
Personally, I only use C to implement cryptographic primitives (ciphers and hash functions) and some numerical kernels. These are straightforward calculations that don't require memory management or tricky pointer arithmetic.
> C has too many pitfalls that a beginner can either be discouraged by a non-working program, or not understanding the subtle gotchas.
Totally agree with you - personally C would have been a very difficult first language for me. Java and Ruby were tough enough at the outset when I started down the programming path. Now I work primarily in C and have been doing so for the last four years or so. Still get hung up on subtle gotchas, especially when interfacing with hardware!
I don't think most of these pitfalls are very problematic for a beginner trying to play with the machine and to understand how things work.
> doing concurrency correctly in C (or Java for that matter) is a grand effort
And that makes it a great teaching tool for exploring how concurrency works, and what issues arise because of concurrency, in a way that other languages don't expose. It's definitely not easy, but understanding how to use (manual) locking/reference counting/memory barriers can be pretty fun (and I'm not saying it's relevant to every developer).
> contrary to the author's claims about conciseness
C is a conscise language in the sense that the language itself (its specification) is quite small. The code you write with it can be quite verbose.
Most should choose a more pleasant and productive language, so that the road to make something working and interesting is not uneccesarily long. Starting with C increases the chances of getting stuck and never getting a good feeling about programming - which is sorely needed to stick to it.
In my opinion, and assuming the author is talking about learning C as first language, my answer is that it depends. It depends on what you're trying to do with what you're learning, and what you will do after.
Basically, if you're studying to be an electronics engineer, I say that C is a good first language to learn the constraints of electronic devices. But if you're studying to be a mechanical engineer, I guess you want to learn programming to solve "data" problems (equations, graphical plots, etc). For the latter ones, something like Python with packages like numpy, matplotlib, etc., is probably more adequate.
In a nutshell, I think of learning C first as the bottom-up approach, and learning something like Python the top-down approach. Choosing one or another depends on how down or up you want to go..
With C, you are in control of everything your program does. That means you're also _responsible_ for everything your program does. C requires a very different mindset than dynamic languages.
As one who's working to get to a productive place with functional programming I disagree: Decades of embedded c preceded by asm hasn't helped out at all.
For me, C programming is kind of like Calculus, Physics to modern day Engineering.
If you don't know Calculus, Physics, you can be a very good construction workers but hard to know the fundamental behind why/how the Buildings, Cars, Airplanes, are designed that ways.
Same for C, if you don't know C. You can certainly a very good program Python, java script, web developers, but might not have deep understand of Kernel, Browser, Database, Network, nor why Java, Go and Rust need all those new language features.
Know how the tools are different will help you to choose the best tool for the jobs.
i could not disagree with this more, holy cow. C has so many weird and painful quirks, and is missing a lot of things that a novice programmer will need to know (like object-orientation, especially).
it's a great second or third programming language to learn though.
From what a saw, exceptionnal people aside, those who learned to code with a language favorising bad OOP (most OOP languages, maybe with the exception of Lisp) write nasty code. Especially when they work with a language like javascript, but even with c++ or Rust you can easely find abominations.
Everybody should learn proper data structure before learning OOP. I don't like Go, but i'd much rather work with an intern who was first taught Go than any OOP language, even one i like. OOP is a great tool, but people that learned with it have the tendancy to use it poorly and everywhere, and to use abstractions of abstraction when this is not needed. Maybe for you, it was not an issue, because you were brilliant enough, but for average devs like myself, it is.
My first two languages didn't include object orientation (QBASIC, and badly-taught "C++" that was essentially C with iostreams). From experience, OOP's an easy paradigm to learn later. Early on, you're learning functions, loops, variables, breaking problems into pieces, how to hold a useful amount of the program in your mind at once, etc.
BASIC would've been better with better explanation of how some of the keywords worked with memory segmentation. The "C++" would've been better if they actually stuck with teaching us plain C.
> C has so many weird and painful quirks, and is missing a lot of things that a novice programmer will need to know (like object-orientation, especially).
I don't think you can understand OO properly without C or another sufficiently low level language. You might learn the concepts at a high level, but it's still important to know at a low level, you know to know about the vtable of function pointers that makes inheritance possible, otherwise you will never truly grasp OO.
Also, I'd prefer most devs learn to write functions that operate on data long before they learn how to combine them into a horrible mess.
Counter-anecdata: My stepfather tried to introduce me to programming via C as a kid (with the very same Kernighan & Ritchie book), and it didn't help at all.
An environment with a better REPL and which can do more "fun" programs is better to start out.
Motivation to build fun stuff is important - I ended up starting my learning on GWBASIC of all things :) and there were some cool BASIC games.
I absolutely agree that C is very important to learn when formalizing a programming education.
Having learned Python, then C++, then JavaScript, then C, then Java, I have to say Java is probably at the sweet spot; it strikes a good balance between providing enough conveniences over C and C++ to help avoid frustrating a programmer (garbage collection, no need to use pointers explicitly) without encouraging total carelessness like Python and JS do (inadvertently) with their lack of built-in static typing.
With that said, if you want to provide instant gratification and therefore encourage a beginning programmer to keep learning, Python and JS may be the best for introductory classes.
MIT's approach of using Python to do a one-semester survey of CS and Data Science, then using Java to teach serious Software Engineering, appears to be an effective compromise (I did three of their CS courses on edX).
76 comments
[ 2.9 ms ] story [ 128 ms ] threadI can certainly imagine a different sequence, where students learn logic gates, hardware, assembly, then C, working their way up to higher levels of abstraction. Even in this case though, the goal isn’t to jump in and start writing big programs in C right away.
I think the commenter is correct. C makes you learn how a computer works. As your code gets more sophisticated you'll eventually have to know this or you can't take your work to the next level. I don't know C all that well and I'm currently facing gaps in my knowledge that would be filled in had I spent some time with a C compiler.
Agreed. Unfortunately the job market pushes people into professions they are not suited for. I bet 90% of lawyers do it for the career prospects and not because they like law.
I accept your premise that we don't want to push people into professions they are not capable of doing well in. But basic knowledge of programming is becoming important enough that I think providing a gentle intro is wise. Like Math and Physics, you can ramp up the difficulty in later classes.
C isn't the only language with such features.
https://www.freepascal.org/
https://www.embarcadero.com/products/delphi
https://shop.mikroe.com/compilers?programming-language*=pasc...
Basic:
https://www.visualstudio.com/vs/universal-windows-platform/
http://gambas.sourceforge.net/en/main.html
https://shop.mikroe.com/compilers?programming-language*=basi...
Z80:
Any Assembly will do, pick ARM, RISC-V, AVR, PIC, ....
- compiles to native code via ngen, mono, .NET Native, MDIL, IL2CPP
- has pointers in unsafe code
- has value types
- structs allow for layout control
- value types can be stack allocated, it also allows for alloca in unsafe code
- memory can be allocated outside GC via safe handles or marshaling services
- latest versions have support for zero allocation memory references
The only thing it doesn't do properly is to have global statics, but it can be worked around via a singleton.
The other option, and the one I choose, is to go from high-level languages down. What I'm not so sure about is just throwing people in in the middle, so that they are trying to grasp abstractions like functions and variables at the same time as they are trying to understand pointer arithmetic.
In the subset of Racket we use, all values are immutable, so an understanding of memory (stack vs. heap) is less necessary. We do still talk about "frames" to understand lexical scope, though, and students get used to thinking of each function call using up a new "frame" of memory to store its local variables. This allows them to understand the memory efficiency of recursive functions.
We then move onto Python, and _do_ talk about an "environment" (stack) and a "heap" (I call the heap "object-land" because objects live there). This enables us to talk about garbage collection. More importantly, students get used to thinking about variables as being bound to either primitives or pointers to shared memory.
In Go, there _are_ pointers, and students get used to thinking about memory addresses as values. They learn that pointers are useful both for sharing mutable references, and for avoiding expensive copies. (In Go, arrays are values, and passing a large array to a function copies it.) But none of the sharp edges of C's pointers are present. Go does "escape analysis" so that if you return a pointer to a local variable, that local variable is allocated on the heap; it is safe to point to anything and if you do, it won't be garbage collected. There's no pointer arithmetic.
By the time they see C, in the third-year course, a lot of the pieces are in place to have a good understanding of how the stack and heap (and manual memory allocation) really work. And along the way, because they were using higher-level languages, the students have been creating cross-platform, complex programs (web servers, games, etc.) that they're proud of. :-)
Re: sibling's comment that "some people aren't cut out to be engineers," I think that's a somewhat dangerous attitude. I think the study of computation is profound & rewarding and should be part of a liberal arts education for everyone; even apart from that, programming is also a useful skill for people who won't get a job as an engineer.
Agreed that some basic skills are a good thing. But in the same sense that I have some grasp of writing but never will be a good writer or enjoy it, a lot of people are just not cut out to be software engineers. It would be a sad world if everybody had the same talents.
Completely agree. If this weren't true, the fizzbuzz test wouldn't exist.
The 90s were filled with businessmen creating Access databases that wouldn't scale, and Visual Basic's drag-and-drop capabilities created a cottage industry for controls consumed by non-programmers.
That's both good and bad. Bad for the obvious reasons (lots of crap software) but good in that for the most part people were able to build what they needed to solve problems and move on.
Nowhere is "worse is better" more true than line-of-business software.
C was the "universal assembly language." In its day, it was unimaginable that one would learn to program a computer without an understanding of the hardware underneath, or computer architecture in general.
>>a systemic aversion to copying data But this isn't an issue with the C language or pointers, per se; an "aversion" to unnecessarily moving data is the very essence of optimization. [As part of choosing the right algorithm to begin with, of course.] As Michael Abrash wrote: "the fastest code is the code that never runs." So eliminating work (such as moving bits across a bus) is key.
I would only add that many of those who obsess with optimization and "clever" programming, have never had to go back and maintain that code. I've looked at far too many modules and said "what was this idiot thinking?" before realizing it was my own work from years' prior.
With a bit of that perspective I now write code favoring clarity over cleverness/performance. And if it is unavoidable to write something dense, I document the hell out of it.
But you're right, there is a definitely a culture of writing impenetrable code full of clever & cute little tricks of the sort that come back and bite the author later. Prevalent in the C world, and I see it also in equal measure in Perl.
Albeit it's hard to separate the effects of that from simply living with someone who can talk with you at the end of each day of class.
If I were to go through it again, I'd do it with an MSP430 instead. The 6502's lack of registers that are the same width as the address bus (and therefore the multiple steps of indirection required for full pointers) obfuscated some of the concepts I was trying to convey.
This guy seems to be putting C on a pedestal. It's OK, for certain jobs. Like kernels, CLI utilities, C/C++ for games, etc. But even there Rust and Go and other tools are invading that space and doing well.
When I want to make something, I usually use whatever I think can get me to something usable fast. C is rarely the answer. I think the same holds for someone trying to learn programming. Python or Go removes a lot of cruft that new programmers shouldn't have to deal with, at first. They are good intros to "you want X? Well import X, then start using X.foo" instead of "You want X, well first let's start a Makefile so we can get our libs linked in properly depending on if this is debug or release, and then maybe CMake so we can use it on other systems, and so on and so forth..."
Agreed! I also agree that K&R "The C Programming Language" is a classic book that everybody should read, at least to admire how well written is it. And of course C is a good language to learn. Not as a "first language" but as one of the "languages to know".
>Also, C is flexible. You can do more with C than you can do with newer languages.
What?! Yes, you can do anything with it, only if your time doesn't cost. The same is true of Assembly code.
There are many languages that are more expressive and powerful than C, and some of them aren't really that slow.
This is how I feel about it. Better to learn with something a bit more forgiving (IMO something like Python is good, even if these days I prefer compiled languages), but as a 2nd, 3rd, or 4th language C or at LEAST C++ should be required. Having to actually deal with memory yourself makes such a huge difference in how you treat your programs.
After this formation, we were able to write a hashmap algorithm that handled collision in less than a day and a half. I honestly don't think we would have been able to do the same if the tools we were given during our formation were python, php or javascript. I don't think resolving the philosopher dinner problem in Go can teach you much. In C, it does. The biggest advantage C have is that you start from nothing, so you have to understand everything. I would not understand BTrees this well if i did not first understand linked list. And i think the thing that taught me the most about OOP was writing a raytracer in C.
Well, these are not the "modern" tools I had in mind with writing my comment above...
>I don't think resolving the philosopher dinner problem in Go can teach you much. In C, it does.
Yes, I agree. But I never questioned C's value as a teaching aid.
Personally I just wish for something like C# without the garbage collector (and some good options for static compilation). Right now it just seems like the only two choices are C(++)/Rust, where your only option is to deal with everything manually even if the compiler or optimizer would know better, and most dynamic GCd languages, where your only option is summoning Cthulhu to do it for you.
In Common Lisp there are tricks to avoid doing dynamical allocations within your critical (read "requires high performance") code, so the GC doesn't bother you. Couple this with type declarations, "inline" declarations (which tell the Lisp compiler to inline certain functions) and, if you know what you're doing, the Lisp code will compile to pretty good (read: fast, optimized) machine language that will run seriously fast (at the same speed or close to the same speed than C or Fortran.)
So you can choose to avoid needing the GC on your high performance code, and enjoy the comfort of garbage-collected dynamic memory management on the code that is not so performance-critical.
Finishing C course on college didn't felt like I learned C. Nor implementing some common algorithms and data structures. [Lets mark my experience until this moment as `phase 1`] This attitude made me stay in C for a bit longer, getting my self interested in systems programming and that is when I got to see truly what programming and debugging C really is. I was learning and experimenting for 2 years with that, and I felt recently like I was ready to move on, (Python and Clojure taking over for the most part) and the conclusion was rather strange:
Everything after phase 1 didn't brought me that much, considering cost/benefit factor I was not impressed by the end results. While whole first few years and college in C were significant and I recommend it to everyone to learn C even up to a point where you write basic CRUD program that read some data from txt/bin and create linked list with that data and represent it, my venture into systems and lower level programming didn't bring me as much of "general" knowledge. Sure it was good, and I learned C++, and combining those two now I do some other things that are related to the things I learned during that period (like GPGPU), it wasn't that mindblowing as I expected initially.
Is it so hard to draw a couple of box diagrams to convey the image of pointer and actual memory contents?
Maybe we need to get back these kind of books, http://www.atariarchives.org/
I don't think anybody ever said that pointers are hard to understand, they aren't. Go is a proof of this as even people coming from Python or Javascript can easily pick pointers up even if they have never heard about the heap and the stack before.
I'm firmly in the "WTF is the big deal?" crowd. Every time I see posts like that I think, "maybe I've thought I understood pointers just fine, and even used them without issue, for years and years, but somehow I actually don't understand them?"
I had a harder time with OO when I first encountered it than pointers, because I kept running into terrible "Sally wants to buy some lemonade from Tommy"-type introductions in crappy, cheap '90s C++/Java programming books and bouncing off it.
Pointers weren't too hard to learn (although, the deeper they're nested, the harder they are to think about, for me). OO felt immediately intuitive, using the "shape class, rectangle class, square class" examples. Recursion is the concept that I understood the general idea of, but had to do immense amounts of work to be able to visualize.
I think that everyone has their own areas that they learn easily, that others have a hard time with.
I often advocate python as a first language, but when you discover that python uses references most of the time, it can get confusing.
Ideally, you would teach the basics of assembly, learn the basics of a compiler, and go forward.
I took http://nand2tetris.org/ before learning C; starting from a single logic gate, you build memory, CPU, assembler, and a compiler for small language (you implement malloc and free yourself). [0]
Though some folks say it sacrifices depth for breadth, I feel very comfortable learning C now.
[0] HN thread for part 2 of the course: https://news.ycombinator.com/item?id=14526344
[1] HN thread for part 1 of the course: https://news.ycombinator.com/item?id=13209452
I think the Java then C then whatever else you fancy is the best way to start programmers/software engineers out. Put them in a structured environment (strong typing, little room for catastrophic errors), then learn why the structure is there with C, then let them free roam to Python/JavaScript/Rust/Go with the experiences gained by the planning at the C level
I agree. Managing memory, pointers, and potentially problematic compiling should come after you know how to loop over an array (or list, or whatever). Java or C# are two great places to start.
People that learned with C (Pascal too) piss more straightforward code on average, and have better debugging skills. And they are more "aware" of the computer.
PS: I did go C, assembly (had to rewrite some libc functions) then emacs lisp (before trying to write stuff in Common Lisp). I don't think going assembly will help a webdev much (even if he work mostly on backend), but lisp is definitly helpfull for anyone. Assembly is great if you like having some fun, but beside CTF and "that one time" (i had a really nasty C bug), the stuff i learned while writing assembly were not really usefull to me.
What problems are you thinking of, specifically? In high school, I had a semester each of BASIC and C++, but my first serious programming was with Java in college. That is, beyond things like a "guess the number" game, getting into actual study of algorithms and data structures.
I'm mostly a C++ dev now, and I've never had complaints about my coding style. Then again, I never bought into the WidgetFactoryFactoryImpl kind of crap.
A language like C is already bad because there has to be a function before anything is done, instead of just a sequence of instructions to the machine to do something.
Imagine you told me "can you go to the cupboard and fetch the sugar?" and I said, "Nope: you must wrap that in an instruction sheet called "main", and then ask me to compile and execute it".
Avoiding undefined behavior in C is difficult, even for experts. Failing to understand UB leads to very inconsistent, often latent, hard-to-debug situations long after the bug has occurred. Other languages - including assembly language - don't have this extremely hostile concept of UB. C encourages bad habits like ignoring error codes from I/O functions. Also, doing concurrency correctly in C (or Java for that matter) is a grand effort, but is easier in languages like Go and Rust.
Managing language minutiae in C, like function prototypes and header files, is something not found in newer languages. Another problem is having a global namespace instead of a more structured, modularized one (C++, Java, Python, etc.). And finally, manual memory management means that a lot of extra code is written (contrary to the author's claims about conciseness), and it especially makes string processing hard.
Personally, I only use C to implement cryptographic primitives (ciphers and hash functions) and some numerical kernels. These are straightforward calculations that don't require memory management or tricky pointer arithmetic.
Totally agree with you - personally C would have been a very difficult first language for me. Java and Ruby were tough enough at the outset when I started down the programming path. Now I work primarily in C and have been doing so for the last four years or so. Still get hung up on subtle gotchas, especially when interfacing with hardware!
> doing concurrency correctly in C (or Java for that matter) is a grand effort
And that makes it a great teaching tool for exploring how concurrency works, and what issues arise because of concurrency, in a way that other languages don't expose. It's definitely not easy, but understanding how to use (manual) locking/reference counting/memory barriers can be pretty fun (and I'm not saying it's relevant to every developer).
> contrary to the author's claims about conciseness
C is a conscise language in the sense that the language itself (its specification) is quite small. The code you write with it can be quite verbose.
Basically, if you're studying to be an electronics engineer, I say that C is a good first language to learn the constraints of electronic devices. But if you're studying to be a mechanical engineer, I guess you want to learn programming to solve "data" problems (equations, graphical plots, etc). For the latter ones, something like Python with packages like numpy, matplotlib, etc., is probably more adequate.
In a nutshell, I think of learning C first as the bottom-up approach, and learning something like Python the top-down approach. Choosing one or another depends on how down or up you want to go..
With C, you are in control of everything your program does. That means you're also _responsible_ for everything your program does. C requires a very different mindset than dynamic languages.
If you don't know Calculus, Physics, you can be a very good construction workers but hard to know the fundamental behind why/how the Buildings, Cars, Airplanes, are designed that ways.
Same for C, if you don't know C. You can certainly a very good program Python, java script, web developers, but might not have deep understand of Kernel, Browser, Database, Network, nor why Java, Go and Rust need all those new language features.
Know how the tools are different will help you to choose the best tool for the jobs.
it's a great second or third programming language to learn though.
Everybody should learn proper data structure before learning OOP. I don't like Go, but i'd much rather work with an intern who was first taught Go than any OOP language, even one i like. OOP is a great tool, but people that learned with it have the tendancy to use it poorly and everywhere, and to use abstractions of abstraction when this is not needed. Maybe for you, it was not an issue, because you were brilliant enough, but for average devs like myself, it is.
BASIC would've been better with better explanation of how some of the keywords worked with memory segmentation. The "C++" would've been better if they actually stuck with teaching us plain C.
I don't think you can understand OO properly without C or another sufficiently low level language. You might learn the concepts at a high level, but it's still important to know at a low level, you know to know about the vtable of function pointers that makes inheritance possible, otherwise you will never truly grasp OO.
Also, I'd prefer most devs learn to write functions that operate on data long before they learn how to combine them into a horrible mess.
An environment with a better REPL and which can do more "fun" programs is better to start out.
Motivation to build fun stuff is important - I ended up starting my learning on GWBASIC of all things :) and there were some cool BASIC games.
I absolutely agree that C is very important to learn when formalizing a programming education.
With that said, if you want to provide instant gratification and therefore encourage a beginning programmer to keep learning, Python and JS may be the best for introductory classes.
MIT's approach of using Python to do a one-semester survey of CS and Data Science, then using Java to teach serious Software Engineering, appears to be an effective compromise (I did three of their CS courses on edX).