Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
The cost of forsaking C (medium.com/bradfield-cs)
292 points by Halienja on Oct 5, 2016 | hide | past | favorite | 372 comments


Ah, C, the love of my life...

Want minimal syntactic sugar and a language that's not prone to feature-of-the-week hipsterism? C

Want to make tiny binaries for some obscure system with virtually no storage or RAM? C.

Want to know where every last cycle goes and explicitly control everything? C

Want to trivially import any of the huge array of existing libraries? You guessed it...

Want to do something you really shouldn't, and any 'sensible' language might attempt to stop, but is called for right here and now...

It's not the safest language, it's not the most expressive, but the syntax is small enough to hold in your head and the body of existing, available work is HUGE. I've been working in C for 15 years and, while I enjoy forays into other languages, it will always be my love.


> Want to make tiny binaries for some obscure system with virtually no storage or RAM?

C++, D, Rust, ...

> Want to trivially import any of the huge array of existing libraries? You guessed it...

If C can talk to it, so can anything else. Granted, having the preprocessor means no bindings, but C++ has that too.

> Want to do something you really shouldn't, and any 'sensible' language might attempt to stop, but is called for right here and now...

C++, D.

Want undefined behaviour? C. Want segfaults? C. Want memory leaks? C. Want memory corruption? C. Want to write insecure code? C. Want to write your own containers? C. Want Go's approach to generics? C. Want to spend 3x as much time writing code as a comparable C++ solution (not to mention more productive languages)? C.

I've known C for 21 years now. I just quit a job as a C programmer for embedded systems. If I can help it, never again.


>Want undefined behaviour? C. Want segfaults? C. Want memory leaks? C. Want memory corruption? C. Want to write insecure code? C.

Want all of that and a whole bag of inconsistent design choices? C++. Because, yes, one day you will meet a C++ codebase which is a mixture of "forbidden C++" and "modern C++".


For the last decade or so writing physics engines has been a hobby project of mine; I shamefully read your comment and realized that absolutely describes my current toy. It's been in various stages of dev since c++11 was newfangled, and combined with being the "learning experience" of a programmer moving over from C; let's just say it shows... (mentally revising my plans to ever apply to C++ jobs and not just go hide in a forest)


Writing C++ is three times as fast?

Perhaps so, but reading it back takes 5 times as long.


No, not for similarly skilled writers. And C++ pushes certain pathologies into much more opaque, dark corners.


> Want to spend 3x as much time writing code as a comparable C++ solution

Coming from Python and dabbling in C for embedded software, this is the one that gets me the most. It's so frustrating to be spending hours doing something you can do with one line of the regex library in Python...


I don't understand your complaint. There are mature regex libraries for C found with the obvious Google search. Source code included. Why would you not just pick and consume one, as you would in Python?

Overall though I think what you may be seeing is simply the stratification of software development; what began in the 70s and 80s has come to fruition. There are different planes of existence, like thermoclines in the oceans. A developer may (and is likely to) live in one forever, without crossing above or below. Each has its purpose. While I wrote my own database and rendering engines in the 80s as part of applications development, I wouldn't dream of it today unless there was a very, very good reason. Same goes for network stacks. Or math libraries. Memory managers. Etc.

Sure, coming from the Python ecosystem, you will feel as though there is a shortage of auto-pilot-like assistance, but you must realize you are in a very different world now - Embedded work is (frequently) focused on predictability, responsiveness, efficiency, and even moreso with realtime work. You are the engineer in charge, and all the safe boundaries and cushions of the interpreted language world are now missing. Want to REALLY feel naked? Drop down to assembly on any platform and try to emit a simple "hello world." For those who have never done so, it's a humbling experience but rewarding.

Remember that C was designed as a "universal assembly language", easily portable to different architectures and close (enough) to the metal so the programmer can have full control over the machine. Best analogy I've heard: C is like a chainsaw - used carelessly one can cause enormous damage and destruction, but in the hands of a skilled/experienced craftsman, very clever solutions are possible.


> I don't understand your complaint. There are mature regex libraries for C found with the obvious Google search. Source code included. Why would you not just pick and consume one, as you would in Python?

Mostly because I'm used to one single place for libraries (PyPI) and I don't know how to find (and evaluate) libraries in C. I also don't know if they'll run on my MCU or how much RAM they require. I have found one or two embedded regex libraries, but they were very limited in what they could do or failed some other criterion.

Besides, that was just an example, it's just death by a thousand cuts with similar things one after the other.


-I don't know how to find (and evaluate) libraries in C

you can replace C with any language. you're evangelising about the toolbox you know instead of learning about the hammer you are familiar with and don't want to learn how to operate


Alright, I'm game. Where's the C library repository, and what's the manager I can use to handle dependencies automatically?


> Why would you not just pick and consume one, as you would in Python?

You probably wouldn't do that in Python; the regular expressions module is part of the language.


An alternative to the suggestion to use a regex C library is to embed Lua code into your C code. Lua+C is great for resource constrained embedded systems and can make text parsing a lot less painful than using pure C.

The Lua docs provide some examples of integrating Lua into C here - https://www.lua.org/pil/24.1.html.


The problem there is that usually you're constrained to 2 kb of RAM or so for an Arduino, so that's too heavyweight :/ Plus, it's outside my abilities currently, sadly.


> Plus, it's outside my abilities currently, sadly.

If your target only has two KB of RAM, Lua is a no-go, I guess. But skill-wise, I can highly recommend it. The reference manual is very well-written, the Lua language itself is very nice and easy to learn, and Lua's C interface is very pleasant to use; and again: the documentation is very good. (Also, the community is very friendly!)

If you ever take a shot at it, the Lua Users Wiki (http://lua-users.org/wiki/) is a good starting point.


Hmm, the example you posted doesn't actually look very complicated at all! And I agree, Lua is a very nice language, I've used it for other projects.


Perhaps because I've done it all my adult life, I've never understood how text manipulation in 'C' is all that painful.


I love python too. It's my favourite language for getting stuff done fast.

However it doesn't give you the control or the performance of C without some serious work, some of which is usually work in C!


Python let's you get stuff done fast, but it's almost more dangerous than C in some respects. Without a compiler, you run the risk of deploying code that will not run, and you don't know until that section of code is actually executed. Requiring a massive set of tests to actually validate your code.

On top of the additional testing, you also will have a performance problem at some point after success. And then people will discuss or even plan for a rewrite in a more performant, language. But we all know that rewrites usually fail to materialize. PoC's turn into products, products turn into companies, and companies bottom lines can't generally spare time and money to rewrite the entire service.

I'm not advocating for C, but I wouldn't put Python up as a comparable thing.


> Python let's you get stuff done fast, but it's almost more dangerous than C in some respects. Without a compiler, you run the risk of deploying code that will not run

C code that compiles can and does blow up at runtime.

There are languages whose type systems do go a long way toward minimizing the risk of this happening, but C is not really one of them.


I completely agree. C sucks at runtime. I was trying to point out that Python sucks at runtime too.

I personally wouldn't choose either language for a new project.


Having the correct type doesn't guarantee correctness.

As a noob C programmer, in my programs segfaults often occur along with undefined behaviour (which is much worse, as your program will silently give the wrong output).

While in Python, you'd get a traceback which is much easier to debug.

> Requiring a massive set of tests to actually validate your code.

Both require tests to validate your code. Sure, static typing can verify some trivial mistakes, but they can't check that array accesses are in bounds, integer overflow, etc.


> As a noob C programmer, in my programs segfaults often occur along with undefined behaviour (which is much worse, as your program will silently give the wrong output).

> While in Python, you'd get a traceback which is much easier to debug.

Well, C's segfault will also generate core dump, which is essentially the same thing as Python's traceback - it tells you where the program crashed.


You're actually talking about typed vs untyped, which isn't so much about C vs Python. Python is slowly acquiring static type checking (and it's working rather well nowadays).


Yes, I should have been clearer that this wasn't in support of C over Python. I'm commenting on the relative safety of Python and the downstream costs of using it.

C is not typesafe either, and blows up for other reasons at runtime.


Yep, agreed. I'm very excited about the optional type checking in Python, and can't wait for it to improve so I can use it in all my projects.


Give Cython a try, you can get huge gains in memory/speed by carefully transitioning from Python to C with it. (Or use it to "write C in Cython"[0])

[0] https://explosion.ai/blog/writing-c-in-cython


This is why (depending on exactly where in the stack you're working, and on what kind of device; I'm assuming you're developing a graphical application on an embedded device with an ARM-like CPU and a small screen for this example) you should be using Qt Embedded. It's C++, and has a very good regex library. You get the power and speed of C++ and all kinds of nice features like regexes.


Sure, but you're giving up control. There's a trade-off and there is a whole world of things which simply aren't practice to do in Python, but work just fine in C.


It's (nearly) trivial to write FSM in 'C' that do what regexp do in Python.


Yes, if you're matching constant regular expressions on an embedded device using a general-purpose runtime regular expression processor, you're probably Doing It Wrong™.


There are regex libraries for C. Worst argument so far.


>There are regex libraries for C

Which everybody here knows and nobody doubted. So, a big duh!

What the parent means is with Python you don't need to go and install one, adjust your makefiles, etc. Regex are right there in the standard library, and things that are not are immediately usable in just a "pip install" away.


Exactly. The biggest problem I have is just finding appropriate libraries. With PyPI I know where they are, how to get them and which one is popular. With C, I'm completely lost. I use PlatformIO's manager, but it's limited to embedded libraries, so I just don't know where to find things at all.


Its not just finding the libraries, its also marrying the makefiles.


With Linux that's trivial to know.


...google


Sure, and to get that Python requires a big runtime to be present wherever it goes. Trade-off.


apt-get install Done!

Do people actually use pip when you can just use the package manager of your distro?


I would upvote this if it weren't for the second sentence.


I think it's a bad thing that hacker news is so allergic to strong editorialization.


I do not. There is enough of it elsewhere on the web, it's nice to be somewhere relatively free of it for a change.

The conversations that occur here generally seem much more structured.


I agree that they are more structured, but I don't see any evidence that this is in any way due to how immediately people are attacked for wording things strongly.

Engineering is a life and death business more often than people think; it deserves people who feel strongly about it.

As an aside, I think the rules about trolling and personal attacks are pretty clear and easy to enforce. What I'm specifically concerned about is that those rules are evoked outside of the spirit in which they were created to attack emphasis in general. In fact, most of the discussion here is in clear violation of "Please avoid introducing classic flamewar topics unless you have something genuinely new to say about them." but obviously we rightly care a lot about this topic and, since people are acting like adults, no one needs to tap on the poster so to speak.


It looks like we agree that attacks are not helpful, but freedom of expression can be.

I appreciate that the majority of people in this forum express their opinions in a highly productive manner void of animosity, as you've just demonstrated. The previous's user's remark "Worst argument so far." did not convey this spirit. Whether intentionally or not, antipathy and pure negativity do not bring out the best of people in online forums. It is my hope that those are two things which this community seeks to discourage, but not all freedom of expression. This is how I interpreted the comment which sparked your initial response.


D? You sure you can get a compiler for that on an "obscure platform"? Do the libraries you want to use work when the GC is off?


You can use D on platforms LLVM amd gcc support. Some libraries work, some don't. Stick @nogc on main and you're guaranteed to not use the GC. Besides, on constrained systems you're not going to be wanting many dependencies.


I love Rust with a passion, but in all fairness it does not make tiny binaries. On my system, hello world in C is 6.5k, hello world in Rust is 805k (124 times bigger). After running 'strip' on the binaries it's a little better: 4.3k vs 345k, which is better but is still 80 times bigger.

(I know there are some "tricks" you can use to make smaller Rust binaries by chopping things out, but of course the same applies to C. For example, I can take the C binary and just truncate the file down to 2230 bytes without breaking it)


Hello world is large primarily because it statically links in jemalloc and libunwind, which are obviously fixed overhead. Both can be removed (and indeed I would expect jemalloc to be turned off by default sometime soon because each jemalloc release seems to be less stable than the last). If you do this you'll get a much healthier binary size.

C binaries are small because they're assuming you've installed what is ultimately the C runtime on your system (libc).


A direct comparison like that is somewhat comparing apples to oranges. Don't get me wrong, defaults matter, but Rust still does offer the control required to get small binaries, and, those defaults are not nearly as bad for low-level domains where one isn't using the standard library, which is where binary size is most critical. See also https://lifthrasiir.github.io/rustlog/why-is-a-rust-executab... and even http://mainisusuallyafunction.blogspot.com/2015/01/151-byte-... .


Those aren't tricks. Rust does not choose the same defaults as C, and, among other things, statically links to the stdlib and jemalloc.

The tricks you describe are just changing the compile options to those that C uses by default.

on top of that you can apply the same tricks as C; e.g. by running strip, truncating the binary, writing a linker script, etc.


You're not comparing apples and oranges there.

For more: https://lifthrasiir.github.io/rustlog/why-is-a-rust-executab...


You forgot to respond to the first one: "Want minimal syntactic sugar and a language that's not prone to feature-of-the-week hipsterism"


That's a lot of words to declare oneself a masochist

Or if being verbose is desired, do you want to do a job for a computer that you are not suited to do but the computer is?


I didn't forget to respond. I consider minimal syntactic sugar a big, not a feature.


The answer to this is Scheme.


Why in God's name would I want minimal syntactic sugar?

I want optimal syntactic sugar.


Everyone has a different idea of what 'optimal' means here.


> I want optimal syntatic sugar

Like Rust or C++ templates? I prefer C syntax over the "optimal" alternative.


Why in god's name would Rust or C++ templates be the optimal amount (or optimal syntactic sugar design)?


Well, that's my point! There is no alternative with "optimal syntactic sugar design". You have to choose between something 'minimalistic' (C) and something less minimalistic (i.e. Rust).


> Want undefined behaviour? C

Can be solved by not doing stuff that are considered as undefined. Think of them as the standard's say of saying "don't do that". It's quite easy to do in most cases.

> Want segfaults? C > Want memory corruption? C > Want to write insecure code? C

There is absolutely nothing in C that makes it more prone to these in comparison to other languages. Moreover you can easily solve these by using an implementation that protects you. (such as one of the multiple C compilers that guarantee to produce safe code without UB or the sanitisers in gcc and clang)

> Want memory leaks? C

Can be easily avoided by avoiding dynamic memory allocation. Moreover nothing stops an implementation from using a GC.

> Want to write your own containers? C

This applies to all the languages as none of them happen to have an implementation of every container in their standard library. It is true that C lacks that in comparison to some other languages, however implementing the common ones (or using a library) is an extremely trivial thing to do.

> Want to spend 3x as much time writing code as a comparable C++ solution (not to mention more productive languages)? C

You must be quite the shitty C programmer to need 3 times less time to write something in C++.


> Think of them as the standard's say of saying "don't do that"

I have a list of "don't do that" rules. I still do them, usually by accident.

> There is absolutely nothing in C that makes it more prone to these in comparison to other languages

If you believe that, then there's nothing I can say that will change your mind.

> Moreover nothing stops an implementation from using a GC

Many applications can't use a GC. For tiny systems, forget it.

> none of them happen to have an implementation of every container in their standard library

That completely sidesteps the fact that in pretty much every other language the containers one would like to use 99% of the time are implemented in the standard library or the language itself.

> You must be quite the shitty C programmer to need 3 times less time to write something in C++.

Only a shitty C++ programmer _doesn't_ write code 3 times as fast as they'd write C.


Don't drag go into this.


If you spend 3x the time writing equivalent code in C as you do in C++ you're either a bad C programmer, a bad C++ programmer, or likely, both.


Concatenate two strings with resource management in C then C++ and see which is easier.


This is a relatively trivial thing in either - depending on how you define "with resource management." I understand completely that people want to translate patterns directly across languages, but since "string manipulation with resource management" as a delta-detail going from 'C' to say, Python was a design goal, it's somewhat specious.


It's not trivial in C, it's a pain is what it is. I'd put it another way: if someone can't write C++ 3x faster than C they're not a very good C++ programmer. Maybe they're writing C with classes.


You just won't get agreement from me here - string manipulation in 'C' is quite easy. Concatenation operators and the like mostly get in the way. This has nothing to do with design approaches. The str() suite from the standard library is very easy to use safely, if you're careful in your constraints. You do have to think "this is a buffer" and behave accordingly. The resulting code is pretty tedious, but there's no way it takes 3x as long.

Throw in the level of control with sprintf() and there's a pretty clear win.

What is easier with C++ ( and most scripting languages ) are things like combinators and regexp.


The CVE database shows how easy it is to use correctly, bah.


Warts like no guaranteed NUL termination for strncpy and strncat if the buffer length is reached are especially fun to contend with in code reviews.

As is *cat being O(N) with respect to destination length.

So easy that there's been constant flamewars over nonstandard alternatives such as strlcpy, strcpy_s... https://news.ycombinator.com/item?id=6940368


I actually can't picture a universe in which there'd be a clear win manipulating strings in C. If you believe that and have done string manipulation in other languages, then you're a very different person from me and nothing I can say or do will change your mind. We'll have to agree to disagree.


As if it were yesterday. Late 89 or early 90, I was learning my first programming language properly (after Logo, BASIC and other stuff). It was C. Common wisdom was that C was bloat and you'd do everything in assembly anyways. I stuck to it (did asm as well). Then, a few years later everything changed with real bloat that was C++ of ye olde and C was back in full force and never quit being there. I've dabbled with C++ before STL craze and never went into it as some of my friends did, but it wasn't quite different. Just a different mental model that it evangelised. These days, I can't even tell what's going on when looking at modern C++. I'm not sure I even want to. C just works for me, and has for a long time.

I've worked in numerous languages and platforms, even changed careers from programming (graphics) to film and television. But I'll always be a C programmer.


I have been coding in straight C lately on an embedded personal project and have loved it immensely. I know what is going on and have a good shot at predicting the resulting assembly.

However, for me, C falls down on large projects. I know there a lot of hate on objects lately, but the complexity of a project decreases while the maintainability increases when you use properly designed interfaces. You “force” the new guy to do it right because the interface makes it obvious. I know there are ways to enforce encapsulation with C, but I’ve never worked on a (large) codebase where this has been successful.


Objects are in C as well, with structures. You can do abstraction like that in different ways in comparison to classes. For example, components are common. Difference being that objects are components, but not all components are objects. Also function objects, aka functors.

I try to organize into specific components and piece them together into a sequential or threaded or both model, depending on what I do. Of course, it is specific to what I do. But there are large C projects that are successful - for example, Linux kernel.


And yet, in spite of all code reviews, they just got an use after free exploit this week and the CVE database is full of exploits due to memory corruption.


I've encountered a lot of C programmers who feel that C++ is silly nonsense.

C++11 really does put that to rest. Move semantics basically enable complete avoidance of manual memory management. There are many other powerful and convenient aspects, but in C++11 and on managing memory essentially goes away. I essentially never allocate memory explicitly.


I essentially never allocate memory explicitly

Depends on what you do, I guess. I process huge image sequences and have them run (play at least) in real time. I cannot afford to not allocate memory manually. In fact, I have to and I have to manage heap myself. I also have to manage thread affinity, in order to have threads (producer/consumer) share L2 cache to get the speed I want, and all that on multiple processors.

Sometimes you must do that manually.


> Sometimes you must do that manually.

You say that, but I deal with almost the exact same things. What I mean by manual is something like malloc where the freeing of memory is manual. Sean Parent would call this 'no raw pointers'.

For starters, the fact that memory allocation is even an issue seems odd to me, I'm not sure why you wouldn't allocate large chunks of memory rarely.

Even if there are OS specific calls I'm making to deal with special memory, I make sure it is wrapped with some simple class or struct that has a destructor and likely a move constructor. Alignment issues can be wrapped as well, then there is no doubt who owns the memory, who frees it, and where/when it is freed. I've never seen a situation in which raw malloc/free are necessary or any control is lost using C++.

I doubt you suddenly want to use C++ still, I'm willing to bet you know C extremely well, but at least realize that your choices are not based off of technical reasons.


Oh, I malloc from OS or hardware (external custom hardware) rarely. Usually once, a big chunk at the start and then heap manage manually. Well, semi-manually too since I took most of my idioms into a library of mine. I had a lapse of thought. Keeping everything aligned is not an issue these days. Affinity can also be done from other languages if OS supports it (looking at you, MacOS), intrinsics or asm directly also, etc.

One thing is sure though. You're right most of reasons aren't technical. I'm very confident about what I know and what I don't know in C and I don't have to second-guess (usually) what I want to do, because my experience lies with C. It's a subjective choice, but also a technical - since that knowledge and experience is my technical asset. It's not like you can't do something in C that you can't do in C++. In fact, you can do it almost the same if you want to, but with C++ you would be a better fit if you follow C++ idioms, which I haven't picked up over the years (especially after STL cambrian explosion) and something I think I won't in the near future. Reason is simple. Everything I do, I do mostly alone and so far C has been good to me. I venture into other things, out of curiosity and sometimes necessity, but I always kind of fall back to C. Sometimes it's due to the fact that I know of top of my head how to do something quick (which I often need to) without resorting to googling or I don't know how to make it run quick enough since I'm not intimate with the language (not C++ in this case, but in general).


Agree entirely with all this.

You know what you get with C and it just works as you expect always. I always feel refreshed after writing C.

There are perhaps a few very minor enhancements I'd suggest, but I'd be very reluctant to open the floodgates and ruin it.


It always works as you expect? Really?

C is one of the few languages, along with C++, that revels in undefined behaviour. It can be very hard to reliably know what a C program will do if it's not written very carefully because there are so many constructs that look benign but which are technically wrong, and the compiler will mercilessly exploit in order to optimise your program into nonsense.


This, While C and C++ are both very powerful languages and could still be considered the "industry standard"(loosely used) It is definitely not a language that will make your applications work as expected, I would say that C#/Java or higher level scripting languages like Lua or Javascript are languages that will let you make "code it and it works" applications


C and C++ are the only languages I regularly use wherein I need to resort to disassembly to debug the damn thing.


I find this statement hard to imagine. I've been coding professionally in C++ for just shy of 20 years now (and in C for a few years before that) and have only resorted to assembly when I needed to actually use assembly for performance reasons.

Actually - not completely true. I do like to look at the assembly from time to time for other reasons, but this is rare.


Even when I have performance to worry about, intrinsics are usually an option - possibly even a superior option - when writing performance sensitive code. But here are some of the cases where I resort to looking at program disassembly - I'll note a theme here of debugging optimized code (because who doesn't love heisenbugs):

1) Diagnosing a crash which turned out to be the result of a 1 byte vtable pointer corruption bug in an older codebase, which turned out to be a bad static_cast in relatively removed code (a good case for boost::polymorphic_downcast!). Simply understanding which pointer was bad in the first place required looking at the disassembly - when you can't rely on your debugger's results thanks to optimization.

2) Figuring out the actual values of variables in crashes and crash dumps of optimized builds to properly root cause a bug, when the debugger gets confused - or simply aggressively inlined and reordered everything so aggressively that there's no sensible values to even display (so, most crash dumps.)

3) Noticing when the optimizer has reordered code "unexpectedly", alerting me to the fact that supposedly thread safe code is in fact nowhere nearly remotely safe and is in fact missing many memory barriers (possibly because their portable macros "helpfully" defaulted to a noop on whatever new and previously unrecognized platform I'm porting to.)

4) Noticing when the optimizer has removed or rewritten code in an "incorrect" manner, helping me debug code that would've worked if it hadn't technically invoked undefined behavior, so I can a) fix it, b) attempt to explain to my coworker that, yes, it's really undefined behavior, and yes, it's actually a problem (typically with a combination of citing the standard and linking INVALID WONTFIX-ed "bugs" in some compiler's bug database), c) be reasonably certain I've actually found the real root cause of a bug and fixed it.

Now, yes, I'll admit this isn't 100% of my debugging sessions. And perhaps I'm an outlier. My coworkers generally learn that I can (eventually) tackle pretty much any weird bug they might be struggling with and that I'm happy to help. All the porting I've done reopens a whole codebase's worth of wounds - latent undefined behavior that another compiler's optimizer didn't take advantage of.

But on the other hand, I've been lucky enough to never encounter a codegen bug in all the compiler and linker bugs I've found. So far. That I know of. And while "rare" by incidence, these are the debugging sessions that can eat weeks at a time for a single bug, when sufficiently nasty and novel.


"It always works as you expect? Really?"

Well, it works as defined in standards. It's just not every programmer knows what to expect. C is simple, yet powerful language and with powers comes responsibility. It's not like you can throw some libraries/modules/objects (or whatever it is in other, safe languages) together, upload to server and call it a day -- static testing, debugging, unit testing is a vital part of any semi serious C project.


I hear this, but I've rarely experienced it. Maybe I unconsciously avoid such cases with long use. I started with assembler, and debug C/C++ with disassembly turned on, so maybe that's why.


It's just really not that hard to avoid undefined behavior. Really. The worst is arguably integer overflow, and that's not even all that challenging.


C++ is far more prone to undefined behaviour and even compilation across systems with the STL.


Undefined behaviour is not as prevalent in real world code as reading articles from HN might make you think.


This is a very naive statement. Sure there are a handful of good companies that enable every single compiler warning, and fix those warnings, and then run the code through Coverity, and fix all those problems too. Almost no one else does. The amount of terrible C in the real world is enormous.


>> Sure there are a handful of good companies that enable every single compiler warning.

You think so? Every company I've worked for, or that I've known people that worked there, always enabled -Wall for their C and C++ code. Most OSS software compiles with all warnings enabled.

I think the issue with undefined behavior in C/C++ is extremely overblown, aside from fun academic examples like 'what does i++++i++ evaluate to' there isn't actually all that much undefined behavior or gotchas in C/C++. I would say there are less, compared to other languages I know.


Signed overflow problems are everywhere, even in carefully written code. Using 'int' instead of a more specific type is a code smell. Security code which presumes that because you wrote ptr != NULL, that the check is actually carried out. Code that does type punning. Code that doesn't know about aliasing. It goes on and on.

You need to know that the problem exists in order to know that you have a problem. There are many C programmers who learned C back in the 1980s who don't even realize these are issues.


I'd say things have changed quite a bit since format string bugs...


Since?

I'm still adding the compiler specific annotations to add format string checking to custom variadic logging functions in codebases I inherit, and finding multiple bugs.


> always enabled -Wall for their C and C++ code

Of course you want -Wall -Wextra -Werror -pedantic. ;)


...but please, for the love of Mike, don't ship source code with -Werror.

There's nothing like the experience of trying to fix somebody else's code which compiled fine on gcc version 8.97 but which now fails to compile on gcc version 8.98 because the new compiler has some new warnings, which it's now treating as errors, and now fails to compile.

...and you've got stuff to do, and the program isn't even broken.


> … and you've got stuff to do, and the program isn't even broken.

Well, it may be — that's one of the problems with C: you never really know for sure if a warning really matters or not. But man, there sure are a lot of them!


Or if you don't have to use gcc, just -Weverything in clang.


I used to work with a guy who would regularly get upset about the idea letting the compiler return warnings because he knew better and didn't want to be bothered with it.

Last I checked he has a couple hundred points on the hacker news internet forums.

Also just last week I found and reported some undefined behavior in a major c++ package that's used by almost every player in as many as several industries. I don't expect it will ever make any difference in production, but it still snuck in.


"The amount of terrible C in the real world is enormous."

I'm sure you could say that about pretty much any programming language: "The amount of terrible X in the real world is enormous". There are also plenty of clean, nice, safe C code around (and any other language), there's no need to over-generalize ("Almost no one else does").


> I'm sure you could say that about pretty much any programming language: "The amount of terrible X in the real world is enormous".

But the damage is far greater in C. In other languages you won't have arbitrary code execution or privilege escalation just because the programmer is not careful. Nor will there be, in other languages, so many nondeterministic bugs that show up once in a blue moon.


> In other languages you won't have arbitrary code execution or privilege escalation just because the programmer is not careful.

Sure you do. Remember the YAML fiasco with Ruby? How about the thousand-and-one RCE issues with PHP? eval isn't evil for no reason.


"In other languages you won't have arbitrary code execution or privilege escalation just because the programmer is not careful"

No, it's possible to make system insecure with pretty much any language if programmer is not careful. SQL injection, cross-site scripting, cross-site request forgery and the list goes on..


Yeah, I do web development. I've worked with javascript, PHP, and, sigh, classic ASP.

There's bad code everywhere. Some languages make it a bit easier, but it's really not the languages fault.



There are very few programming languages where the total lines of code written is larger than the amount of bad C code written.


There are very programming languages where the total lines of code written is even comparable to C, so of course there is more of bad code too.


There are two kinds of languages: Those everyone hates and those nobody uses (according Bjarne Stroustrup, but I tend to agree... ;-) ).


There's a kernel of truth there, but I find myself regularly using languages which I hate much less.

If your best defense of a language is "well, at least people use it", that's a bad sign.


I always read Stroustrup's quote as saying, roughly speaking, that if nobody uses a language, nobody will notice its shortcomings, and if lots of people use it, lots of people will notice. In other words, popularity is no excuse for sucking, but if one is not popular, no one will notice how much one sucks.


You're right - it's far more prevalent, if the bug database for basically every C or C++ codebase I've ever seen is any indication.


it's a heatmap.


There are some very interesting surveys and studies that suggest otherwise. Undefined behaviour due to integer overflow seems to be very common.

http://www.cs.utah.edu/~peterlee/papers/tosem15.pdf


I would tend to disagree. For example, casting void * to other pointer types is undefined, but this construct is often used, for example:

  void func(void *ptr)
    {
    uint32_t *ip = ptr;
    ptr[0] = 123;
    }


This is wrong. Casting to and from void * is defined for all pointer types except function pointers, according to the standard.

Otherwise most every assignment after call to malloc would be undefined.


Yes, you are right, void * is an exception. However, any other pointer cannot be reliably casted:

From C1X, section 6.3.2.3:

"A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned for the referenced type, the behavior is undefined."

Though that is quite odd, since any pointer can be converted to void* , which only needs alignment to the char type. So converting from x* -> y* is undefined, but x* -> void* -> y* is defined.


That might not necessarily work. If you have something like:

>> uint8_t x[100];

>> uint32_t *y = &x[1];

And then dereference y, most RISC architectures will trap on the unaligned access. It doesn't matter if there is an intermediate void pointer or not.


I am not trying to say it'll work, I'm trying to show that most non-trivial C programs invoke undefined behaviour, according to the spec.

According to my reading an intermediate void pointer allows the pointer casting to stay well defined. However this seems unsafe, even without getting into dereferencing, because implementations are allowed to store omit bits if they assume aligned pointers.


I'd say my example demonstrates the spec's statement:

"A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned for the referenced type, the behavior is undefined."

The resulting uint32_t pointer in my example is not correctly aligned for the reference type, so undefined behavior (e.g., a trap on RISC) occurs. What's an example of a statement in a "non-trivial" C program that is in common use but you think is undefined?


Okay, now there are two different topics.

(1) I didn't say your example didn't demonstrate a violation, but it misses the point, because it doesn't invoke an intermediate void pointer:

"A pointer to void may be converted to or from a pointer to any object type. A pointer to any object type may be converted to a pointer to void and back again; the result shall compare equal to the original pointer."

(2) That was my attempt at coming up with a good example, but it seems, due to the above clause, the casting between incompatible pointers via void * is technically "legal".


To try and give some actual examples:

Undefined:

  int64_t a = 42;
  void* p = &a;
  int32_t* i = p;
  printf("%i", *i);
Implementation defined, as type punning to char is legal (allowing the implementation of memcpy):

  int64_t a = 42;
  void* p = &a;
  char* ch = p;
  printf("%c", *ch);
Exercise left to the reader: Implement a "fast" memcpy (e.g. one that will copy more than 1 byte at a time for large copies, as your standard library implementation likely does) without violating strict aliasing rules.


If you try and hit your finger with a hammer, your subsequent behavior is undefined. Please do not do that.


Where in the standard does it say your first example in undefined?


Since I don't have a copy of the C standard handy, I'll reference this which covers the relevant sections of C++03, C++11, C99, and C11: http://stackoverflow.com/a/7005988/953531 . Quoting the C99 version bellow (§6.5 ¶7):

  An object shall have its stored value accessed only by an lvalue expression that has one of the following types 73) or 88):

  * a type compatible with the effective type of the object,
  * a qualified version of a type compatible with the effective type of the object,
  * a type that is the signed or unsigned type corresponding to the effective type of the object,
  * a type that is the signed or unsigned type corresponding to a qualified version of the effective type of the object,
  * an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union), or
  * a character type.

  73) or 88) The intent of this list is to specify those circumstances in which an object may or may not be aliased.
Bullet 6 is what allows the second sample to have defined behavior. For the first sample, unless I'm seriously mistaken, int32_t isn't considered "a type compatible with" int64_t. Bullet 2 talks of "qualified" versions of types - I believe this is referencing const/volatile qualified types. Bullet 3 apparently allows you to type pun (unsigned int) to (signed int) or vicea versa? Which is an interesting bit of new trivia to me. Bullet 4 is much of the same, bullet 5 requires a nonexistant union, and bullet 6 requests a character type.


Okay, good point - so it's the deferencing step that evokes the clause your mentioned. Apparently, what I learned today, is the cast is fully legal even though it could produce an invalid pointer.

I still wonder if my snippet counts as undefined behavior, since it does dereference an "unknown" void pointer, which may have come from an incompatible object type.

BTW, the latest C1X draft is only at http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf


> I still wonder if my snippet counts as undefined behavior, since it does dereference an "unknown" void pointer, which may have come from an incompatible object type.

It counts as potentially undefined behavior - depends on what you pass in. NULL? UB. Pointer-to-uint64_t? UB. Pointer-to-uint32_t? Perfectly defined behavior! ...well, assuming we use ip[0] = 123; instead of ptr[0] = 123;, which won't compile as I've just noticed.

That said, there are some ways to construct pointers which are in and of themselves undefined behavior for merely constructing the pointer:

http://stackoverflow.com/questions/23683029/is-gccs-option-o...

More samples:

  int a[] = { 1, 2, 3 };
  int* b = a+0; // Perfectly defined/legal/normal
  int* c = a+3; // Perfectly defined/legal/normal, just don't deference it (as it points past the end of the array)
  int* d = a+4; // Undefined behavior.  HAIL SATAN!
  int* e = a-1; // Undefined behavior.  Also apparently potentially caused optimization induced breakage in practice.  HAIL GCC!


Casting pointers is well defined. What is undefined behavior is to dereference a pointer whose type does not match the pointed-to object.


That's not correct in general; casting pointers is possibly undefined. However, it does seem a made a mistake trying to use void * as an example.


One of the best things about C is it is not upgraded frequently like other languages. So there are no frequent <language> <x.y> released posts here like we see for other languages.


On the downside, when it does get updated, compilers take ages to implement the new features, and in the meantime make up busywork like "let's break OS kernels or crypto code to get faster in some random benchmark nobody cares about!"


Don't need C updates for that, a GCC upgrade is quite sufficient! :(


gcc is generally used as a testing ground for new C/C++ features. So in most cases, the compiler supports new features before they are 'officially released' into the language.

It's the complete opposite of waiting for a feature to appear in the compiler.


GCC didn't get sort-of-complete C11 features until 4.9 (2014) and still omits (largely useless) optional features.


> You know what you get with C and it just works as you expect always.

Every language works just as you expect if you have the right expectations.


"minimal syntactic sugar" made me think of this quote:

"C is memory with syntactic sugar." Dennis Kubes


> Want to know where every last cycle goes and explicitly control everything? C

So... you compile with optimisation switched off?


There's something to be said to be familiar enough with the optimization that you can predict assembly that is not a direct translation of what you're writing.

For instance, when writing a scalar-processing loop over a fixed length array with no dependency between iterations you can reasonably expect the compiler to convert to SIMD instructions and do some unrolling. It's not too difficult to check the binary that this happened, and if it doesn't happen you just write it yourself with intrinsics.

I often think of C + compiler's optimization passes as a "scripting language for assembly".


In some cases you may wish to, yes. Particularly where it comes to things like explicitly zeroing memory before freeing it, something that has recently been addrezsed with memset_s


That's exactly my point: when the compiler can decide not to do something you told it to you don't have full explicit control over everything. That's not a bad thing though - the compiler usually makes good decisions on your behalf. Usually.


I love C with all my heart, but minimal in the syntactic sugar department it ain't. Other languages of its day like Fortran or Pascal would not let you write an atrocity like

  while (*++targ = *++src)
    ;


Clear and concise, and a thing of beauty!

Unless the data is user-generated, in which case you might have a security issue on your hands... :)


Well, that's up to you whether you write that way or not.

Kinda like with everything else related to C!


> Well, that's up to you whether you write that way or not.

The problem is, it's also up to the programmers who worked on my codebase before I did. I would never write that atrocity, but I've come across that atrocity quite a few times in code I needed to modify.


I'll.never claim there aren't a ton of terrible coders out there. They exist in all languages, but perhapd C gives them the tools to be really, extra awful..


It also doesn't give you very good tools for refactoring to clean up the awfulness. Unit testing tools and the type system in C are such that it's fairly difficult to ensure that your refactor hasn't broken anything.


Not really an atrocity. More like an idiom.


perfectly readable :)

(but usually generates compiler warning without an additional set of parentheses)


I think the strongest thing in C's favor is that it is an extremely simple language, so it's easy to port and easy to write (semi-broken) compilers for. A language like C++, D or Rust, being more complicated, can take longer to provide ports for.


All three of those languages have at least one compiler with an llvm backend, so writing ports should be as hard as C. Perhaps easier, since you don't actually have to write a parser for anything, just an IR->asm pass.


up to 'trivially import...' I thought about FORTH, not that I have any experience with it besides some tutorials.


I agree John Regher's take on teaching C: http://blog.regehr.org/archives/1393 In summary, it's a useful skill to have but you're not doing your duty as an instructor if you don't make your students aware of its many shortcomings.

I particularly want to take issue with the claim in the original post that "C helps you think like a computer". C's machine model might have been a good model for the machines at the time at which it was developed, but it hasn't been an accurate model for a long time. A simple example is C's assumption that memory is flat and all locations have an address. With multiple layers of caching and nearly a couple hundred registers in current CPUs this is far from the truth. If you think programming in C is programming at the machine level you are deluding yourself.


> With multiple layers of caching and nearly a couple hundred registers in current CPUs this is far from the truth.

Add to that the aggressive optimisations performed by modern compilers and that mental model breaks completely. It may be a good conceptual model, but it's not the model under which your program actually executes.

However, even programming in assembly keeps you very "far from the truth", since modern CPUs are very complex beasts internally and that complexity is mostly not visible in the ISA.


Yup. Caches, prefetching, NUMA, out-of-order execution, speculative execution etc.


The thing about speculative execution and out of order execution to remember is that you always get the expected result from a single threaded perspective. If you throw in interrupts or threads, you know you must tread cautiously in C.

Few things have made me more nervous in my career than seeing a ISR making use of C++ standard library features. When I asked the engineer how they were sure this was safe and worked under the covers, I was immediately dismissed as "the compiler takes care of all that." The project only ever worked at -O0 and was still buggy. The engineer blamed the hardware.


> The thing about speculative execution and out of order execution to remember is that you always get the expected result from a single threaded perspective.

Could you elaborate on this? I thought at least speculative execution was handled internally in the CPU and essentially not visible to the user apart from the performance impact?

I’m not sure what you refer to as out-of-order execution, is this execution reordering within the C compiler?

> If you throw in interrupts or threads, you know you must tread cautiously in C.

Naturally, yes.


Ahh, blaming the hardware. A sure sign they haven't looked into their code closely.


You're right as far as results, but both can still mess significantly with performance even when everything is single threaded.


The x86 instruction set (and other instruction sets), abstract away much the underlying hardware architecture. The "flat memory" abstraction originates from and is presented by most CPU instruction sets -- C just tries to be as close to a "generic" assembly language as possible.

I wonder when we'll get an instruction set that gives us fine-grained control over the different levels of caching, or even the ability to create FGPA-esque microcode functions, etc. Compiler writers could have the time of their lives with such an ISA.


I think with today's CPUs a generic assembly language would at least make a distinction between registers and memory locations. Programming with a conceptually infinite number of registers would be fairly simple and should make reasoning about memory locality easier. Current x86 CPUs have 192 registers internally, so you'd almost never go outside the register file unintentionally.

I'm not very experienced with GPU programming, but my understanding is that the programmer has more control over memory locality there.

Another point to remember is that CPUs have coevolved with programming languages. I think if languages tried to expose more detail in their machine model, ISAs might evolve to match this.

Finally, hardware designers mostly aren't programming language people, and programming language people generally haven't been interested in languages in the same space as C (though that is changing.)


CPUs had registers back when C was invented too :) The reason C doesn't expose registers is because the flat memory model is more universal and more uniform between CPUs than the registers, which often have a lot of special-case behavior, especially on non-RISC CPUs: https://vanemden.wordpress.com/2016/03/15/why-does-c-not-hav.... Besides, C has a "register" keyword, although it's probably ignored by almost all compilers today in favor of their own optimizations, which are usually better (and if they're not, you're usually gonna go to assembly for maximum performance anyway).


The reason C didn't expose registers is because it was specifically made to run on a PDP-7 and PDP-11. That's the sole reason for many of its internal decisions. Doing things differently than a PDP-7 or PDP-11 would've hurt performance. So, they kept it consistent whenever performance or complexity was critical.

Decisions like that and x86 is why stack operations are still dangerous despite B5000 (1961) having protected stack and MULTICS (UNIX/C predecessor) having reverse stack immune to overflow. C kept the shitty PDP stack instead of MULTICS for performance. Reverse stack on x86 still has performance hit due to that decision. It's why Itanium did shadow stack in hardware.

Not clever design or forward thinking. Just making it work on a PDP with long-term problems following.


> The reason C didn't expose registers

Uh ... http://ee.hawaii.edu/~tep/EE160/Book/chap14/subsection2.1.1....


Yeah, I've used the keyword myself but it was discouraged. Instead, we were taught to work with the local variables, stack, frame, heap, etc. It's technically in there at some point as a compiler hint but the internal structure reflects the PDP's. So, I partly take the comment back in that it was there but not standard practice.


I've done a bit of reading and I believe the blog post you linked is incorrect. For example, it claims that an innovation of C was to have assignment as a generalised move operation, rather than load and store operations (and introducing registers).

  One of the secrets behind C’s success is that just about any machine architecture has byte-addressable random-access memory. Moreover, pick just about any pair of machine architectures, and they differ in their register structures. By excluding registers from its computational model, C has hit the sweet spot in being close to the machine, yet not too close. So there is no place for LOAD and STORE in C’s computational model, while the assignment operator is an operation on random-access memory in the presence of an unspecified complement of registers.
However Wikipedia claims this was an innovation of the PDP-11 ISA: https://en.wikipedia.org/wiki/PDP-11#Instruction_set_orthogo...

Similarly, you can find many C features, like ++ and +=, mirror PDP-11 instructions (see https://en.wikipedia.org/wiki/PDP-11_architecture, double-operand instructions for instance)

Finally the PDP-11 had 8 general purpose registers, with two taken for program counter and stack. It seems that main memory loads cost about an extra cycle. So register allocation was not a pressing issue. There aren't enough registers to make sophisticated register allocation worthwhile, and you don't pay a high penalty on a register spill. This is in contrast to all modern machines (e.g. current Intel chips have 192 registers internally and a cache miss can cost a hundred cycles [http://stackoverflow.com/questions/4087280/approximate-cost-...)

I'm coming to the same conclusion as nickpsecurity: C was designed specifically to run on the PDP-11, taking advantage of its instruction set, and we're still paying the price of those decisions.

It's interesting to imaging what a better low-level for, e.g, the Parallela Epiphany-V currently on the front page would look like.

https://www.parallella.org/wp-content/uploads/2016/10/e5_102...

Highlights:

- 64MB(!!!) of on-chip SRAM - 1024 processors - 64 registers


still better to have some concept on how memory works though, even if it's still an abstracted version of what happens on the machine. If all you ever see is Java or Python, the mental step to understanding memory optimization and caching becomes much larger.


That is a great post on teaching C.

I think there are a couple of ways that it could be argued that "C helps you think like a computer": most CPUs have been designed around running C code quickly and the oddities of the language force you to think like the compiler (which has both positive and negative aspects :/ ). For that matter, things like UNIX and POSIX make more sense if you underderstand C (and vice versa) and there are multiple complete and fairly popular open source operating systems you can examine that are written in C (and the mailing lists of these operating systems can be excellent places to learn a bunch of low level stuff). On the negative side (as you mention), I think it is very common to think of C as a lower level language than it really is (I'm planning on learning Ada, which it sounds like has more low level features, although still not the same as learning CPU architecture).

C forces or encourages you to learn a lot more about everything than other languages, which not a great thing if you just want to write a reliable program but isn't such a horrible deal if learning such things is part of the point. D, Ada, and to a lesser extent Rust seem similar in some respects and with significant benefits but without the easily accessible operating system projects (so far, that I know of).

So I guess my addition to that excellent post would be that if you are going to learn C you should get involved with an OS project. The BSDs are particularly good for this due to being more integrated systems and slower moving with more design consideration given to new features than in Linux. I'd personally recommend NetBSD, where the tech-* mailing lists are high quality and fairly low volume, but any of them should work well.


There's actually a very strong hobby kernel chunk of the Rust community, and it's fairly education-focused. http://os.phil-opp.com/ kicked it off, and my own project based on it, http://intermezzos.github.io/ is coming along.

There's also more serious efforts: https://www.redox-os.org/ has made tremendous progress in a relatively short period of time, https://robigalia.org/ is an extremely interesting effort for Rust on top of seL4.


Very cool, although not yet the same as well established production operating systems. I think Rust will be a great alternative to C in a decade and worth considering even now.

In case it wasn't clear, my "to a lesser extent Rust" comment was just about my impression that Rust can more easily be used without worrying about optimization level details vs. D or Ada or C. Which is a good thing if true since Rust can also deal with the low level stuff, although possibly it is just my mistaken impression (I only actually know C out of these languages).


Yeah, totally. Gotta start somewhere :)

I do think that you, overall, need to care less about optimization-level-details in Rust than in those other languages. But that's because we've made it so that the default ways you do things are already plenty fast; the idea is that the nice way of doing things is also the fast way of doing things, so there's no "well, looks like I can't do it how I want, time to replace it with something gross but performant." Of course, this can still pop up from time to time, but it happens more rarely.


I think you're definitely correct but it begs the question: is there another modern language that does help one get "closer to the machine" conceptually in a correct manner?


Amen.

Further, C helps you think like a particular evolutionary path of computer, and the language and architecture developed mostly in parallel. It's like saying that giraffes have long necks because they need them to reach the tall branches.


It's hard to over-estimate the historical importance of C, and the amount of groundbreaking work and important knowledge codified in C codebases.

Also, it can't be denied that there will always be areas where the exact control of memory layout that C gives you is not just nice-to-have for performance reasons, but a prerequisite for a good implementation or understanding of some algorithm.

Neither of those points, however, mean that C is the only suitable language imaginable for doing low-level work, or that the past forty years of programming language research are irrelevant to the spaces that C excels in. I think Rust shows quite succintly the immense value of the combination of an expressive type system and C-like zero-overhead abstractions.

Learning C, like learning some assembly languages, to some extent is probably necessary if you're serious about understanding computers, just to understand what's going on. But when it comes to actual use in production systems, I am glad that we are seeing serious alternatives to a language so prone to simple bugs as C. Think of all the time and money that have been lost to buffer overflows and segmentation faults.


As much as the Rust community does right with respect to the technical aspects of the language, as much they still don't (seem) to understand the non technical problems it has.

Do you know which non technical advantage C has? Multiple implementations. Don't like gcc, use clang. The Keil compiler has a certain bug that screws your project? Use IAR. And so on.

That is one thing. Another thing (at least at the company i work for and the ones i know) is, companies that write code for infrastructure or machines that need to rung a looong time won't ever trust a language (and toolchain) that comes in versions and not in IEEE standards. That may or not be stupid, but it is the reality i've seen so far.

P.S.: Before someone says C only NOW has multiple implementations, yes, at some point in time that was true, but i was VERY easy for compiler vendors to implement a C compiler (speak ROI). Time will tell if this will be true for Rust as well.


> companies that write code for infrastructure or machines that need to rung a looong time won't ever trust a language (and toolchain) that comes in versions and not in IEEE standards

The Rust team has a very high commitment to stability, and does way more to maintain stability than any other language/compiler I know of (by testing new versions and potentially-breaking-changes against the entire ecosystem)

> still don't (seem) to understand the non technical problems it has.

Just because multiple implementations of Rust don't exist (there are a couple but nothing 100% feature complete iirc) doesn't mean that the community doesn't understand the need for it. Alternate implementations are hard to write and take time. The language is still new, but work is being done towards speccing it (e.g. the unsafe code semantics work being done right now). Note that most languages have gone for years without getting a proper spec or alternate implementations. This is an advantage C has over Rust, agreed. It's usually not a dealbreaker, though.


>The Rust team has a very high commitment to stability, and does way more to maintain stability than any other language/compiler I know of (by testing new versions and potentially-breaking-changes against the entire ecosystem)

Yes, but my experience is that they don't seem to manage generating the kind of trust needed to get my peers to give it a serious go. It's a purely social and subjective problem.

>Just because multiple implementations of Rust don't exist (there are a couple but nothing 100% feature complete iirc) doesn't mean that the community doesn't understand the need for it.

As i said, it SEEMS to be so. That means that it is a subjective impression my colleagues and me get. It is the job of the community to generate trust, not the (potential) user's.

>Alternate implementations are hard to write and take time.

Yes, see my P.S. Time will tell if it's worth it to compiler vendors. C made it easy for them right from the start.

>Note that most languages have gone for years without getting a proper spec or alternate implementations. This is an advantage C has over Rust, agreed. It's usually not a dealbreaker, though.

Not a dealbreaker for whom? In the infrastructure and big machine business you will see C, some C++ and Ada. All standardized.

If the Rust community wants to offer an alternative for C, C++ and Ada, AND want Rust to succeed as an alternative, they need to understand that trust (in the toolchain) is generated in different ways in different industries.


Not a dealbreaker for whom? In the infrastructure and big machine business you will see C, some C++ and Ada. All standardized.

What are you talking about? There was a C standard because so many groups picked up C. The Ada mandate actually reduced its uptake with a bit of a rebound latter on when the tech itself was better (esp compilers). C++ got uptake before a standard plus fed into the C standard. So, no, the lack of a standard isn't stopping adoption by industry outside select firms that won't pick up tech unless it has a standards document which often isn't even accurate.

What industry does care about are compilers, IDE's, existing libraries, available talent, ecosystem, who they can pay for support. These things aren't at a level they like yet. Adoption stays low.

Note: Most infrastructure is written in C due to talent and compiler availability more than anything. That's what most embedded people tell me.


That was then, now is now. Today, at least in europe, big companies do care about standards. Go ahead, try proposing using a Compiler that does not implement any standard like C,C++ or Ada in the Automotive industry.

As for the rest, i agree.


Safety-critical industries here and over there are regulated by standards. They're an exception. Average, embedded system can use whatever it wants so long as it produces code to run on target board. Making your tooling generate C as output is easiest way to cheat. It's what I did with my BASIC 4GL. People were none the wiser.


> Not a dealbreaker for whom? In the infrastructure and big machine business you will see C, some C++ and Ada. All standardized.

Sure, there will be some cases where you absolutely need a standardized language. And Rust won't work there for a while at least. But that's a far cry from the picture painted in the original post.


>And Rust won't work there for a while at least.

Yes, and that is why those industries will choose to ignore it for a while at least.

Chicken and egg.


It's not a chicken and egg. Like I said, the Rust team does want to make this happen (and is already doing so from the specification side, though I suspect multiple implementations will come from elsewhere since we already have a couple partial ones). Rust already has lots of folks from other industries trying to use it -- there's plenty of momentum for it to get there. It will take time, but it's not like Rust will never get there without support from the industries you mention (which would be a chicken and egg problem)


Until this happens i will remain skeptical if the Rust community can pull it off without the backing of said industries. On the other i like being proven wrong. Time will tell.

> though I suspect multiple implementations will come from elsewhere since we already have a couple partial ones)

>Rust already has lots of folks from other industries trying to use it

I'm not up to date. Input would be appreciated here.


As for alternate implementations, https://github.com/thepowersgang/mrustc .

As for specification, https://github.com/rust-lang/rfcs/pull/1643 is what's currently happening, i.e. specifying exactly how unsafe code behaves (this is more important than specifying the rest). But the rest will come.

As for the industry, https://www.rust-lang.org/friends.html


Thanks.


> The Rust team has a very high commitment to stability, and does way more to maintain stability than any other language/compiler I know of (by testing new versions and potentially-breaking-changes against the entire ecosystem)

I'm all for this but I really don't see how Rust's stability can hold a candle to the international standards committees that govern C and C++ (not to mention the investment from the big tech companies in developing and implementing these standards)


Rust is far more stable than C was six years into it's life. It hasn't caught up with C yet, but C had a 38 year head-start.


> I'm all for this but I really don't see how Rust's stability can hold a candle to the international standards committees that govern C and C++ (not to mention the investment from the big tech companies in developing and implementing these standards)

That's an orthogonal issue; that's adoption. More adoption means more stability, regardless of whether or not the thing has a standards committee.

Of course Rust can't hold a candle to the level of adoption C has.


  > as much they still don't (seem) to understand the non 
  > technical problems it has.
What makes you think that? The Rust developers have had their eye on a written specification and a path to standardization for a while now. And I've seen nobody arguing that alternate implementations are undesirable, only that that's hardly a priority at the moment, especially in this day and age (the "you need multiple compilers" argument was more veracious before compilers were all FOSS and all grew retargetable backends, today multiple implementations are useful mainly to rule out reflections-on-trusting-trust attacks).


>And I've seen nobody arguing that alternate implementations are undesirable, only that that's hardly a priority at the moment, especially in this day and age

Yes, for most developers this is true, but not all (see post above). Try looking at it from my perspective. If i suggest to my peers to give Rust a (serious) try and evaluate it for use in a project, this "hardly a priority" generates the impression of "we don't care right now, maybe someday". So they say "fine, i will give it a try in x years, when multiple implementations are available. It the meantime we will wait and C.".

So basically, chicken and egg. Again, the Rust community needs to understand what kind of impressions they generate vs. which impressions they want to they generate.

Update: Just to make that clear, i have to look at it from the perspective of an industry that produces infrastructure and complex, big machines.

Update 2: Fixed typo.


> Try looking at it from my perspective. If i suggest to my peers to give Rust a (serious) try and evaluate it for use in a project, this "hardly a priority" generates the impression of "we don't care right now

Interesting, but if you try to see the perspective of the Rust developers, they're a relatively small group trying to first develop the strongest language possible, after which other implementations will rise in priority. The idea is, it's better to develop a great language first and focus on other priorities later, rather than having a mediocre language with many implementations. Nonetheless, they're taking the necessary steps.


>The idea is, it's better to develop a great language first and focus on other priorities later[...]

Agreed, but when is Rust done? 1.0, 2.0, 3.0?

Why does the Rust community talk more about adding optional garbage collection than IEEE standardization and alternative implementations (for example for gcc)?

You see, there's always this "one more feature" before it is a "great language".

If you want to sell it as a C alternative/replacement and keep going this way, it will be neither a technical nor a spiritual successor.


> Why does the Rust community talk more about adding optional garbage collection

What? There is only one person talking about that, and that's me, and that's pretty low priority right now. There are three people total who are working on it (I worked on the higher level design, and felix/niko/I will implement bits of it at some point) and I don't think any one of us is currently doing anything here.

OTOH there is a whole group of people actively working on standardizing how unsafe code is supposed to behave. There are pushes for standardization elsewhere too, though unsafe code is the more pressing one since that's where actual UB can happen and that's where you need a standard the most. There's a group working on formal verification as well (which could produce a standard as a byproduct). Not sure if there are plans for making it an IEEE standard, but a standard it will be.

Standardization is a topic that crops up pretty regularly in discussions. Optional GC is not.

The type system is still in flux with specialization and a bunch of other type system changes on the horizon. These don't affect language stability, but they do affect how the standard would look since they may completely change how you describe the type system. Starting work on a complete standard right now doesn't make sense with these bits missing.

Edit: it's also not "optional garbage collection", really, it's just hooks so that it's easy to write your own GC, or, more importantly, integrate with another one. Rust is getting a lot of attention from folks wanting to speed up portions of their Python/Ruby/whatever codebase, so having these is important.


I'm pretty sure C did not have a plethora of compilers six years after birth. Even if it did, they were probably wildly inconsistent.


They were absolutely inconsistent. It wasn't until 17 years after its inception that a C standard was agreed upon.


Multiple implementations combined with loads of undefined behavior is a pretty big disadvantage for C as well.


Yes, but if a compiler screws my project because of a bug, and the company (maintainer) can't/won't fix it for reason x, i at least have a chance rescuing the project by switching compilers.


The huge advantage of C (before the advent of LLVM) was that it was relatively straightforward to write a C compiler for any exotic or not-so-exotic system, since the language has very simple semantics and many operations would correspond to single machine instructions. There are literally hundreds of production C compilers written to this day.

The syntax of the language is also designed to help the compiler writer and not the user – think of "declarations mirror use", the irrelevance of the order of type specifiers, etc.


> The syntax of the language is also designed to help the compiler writer and not the user – think of "declarations mirror use", the irrelevance of the order of type specifiers, etc.

Err, I would agree with the statement that the syntax of C (mostly) favors the compiler writer, but "declarations mirror use" is decidedly not a good example for a couple of reasons:

    1. The grammar for type declarations is actually quite complicated[1]
    2. Agol, which inspired C's type system, actually had a much more "compiler friendly" type syntax[2]. C's syntax is designed to be more programmer friendly.
    3. Dennis Ritchie makes it pretty clear that "declarations mirror use" was intended to make use and declarations visually similar, which implies that it was done for the benefit for the programmer[3].
[1] Compare https://www.lysator.liu.se/c/ANSI-C-grammar-y.html to [2].

[2] http://www.csci.csusb.edu/dick/samples/algol60.syntax.html#5...

[3] "Analogical reasoning led to a declaration syntax for names mirroring that of the expression syntax in which the names typically appear...In all these cases the declaration of a variable resembles its usage in an expression whose type is the one named at the head of the declaration." from http://www.jslint.com/chistory.html


The declarator looks complex because of differences in the precedence and associativity between the asterisk, `[]` and `()` operators. The point is that the language uses a set of the same operators with the same precedence and associativity both in declarations and in normal expressions.

In my view, a "user-oriented" declaration of an array of pointers would look like `pointer[10] of int`, and not `int *arr[10]`.


I'm guessing you mean `array[10] of pointer to int`.

This is pretty close to Pascal syntax already: `array[0..9] of ^Integer`, where ^ is pronounced 'pointer to'.

Pascal is a language that is genuinely easy to parse; it's designed to be LL(1). C is not; in particular, typedefs effectively introduce keywords, and the easiest way to parse C is to inform the tokenizer about typedefs as they're found so the parser can disambiguate things like `a (b);`; this looks simultaneously like declaring a variable and calling a function, depending on the type of 'a'.


Maybe I'm missing something, but I think Pascal "TYPE" blocks and C "typedef" statements do pretty much the same thing.

(they don't look the same, but you can use either to define record/structure types or enums, for example)


The distinction is in the syntax.

    int main()
    {
       foo(bar);
       foo * bar;
    }
If foo is a typedef, these are variable declarations, otherwise the first is a function call and the second multiplication (syntactically). These are different kinds of things that would normally be up to the parser to figure out (rather than semantic analysis later). These are well-known ambiguities relating to typedefs in C (well-known to compiler authors, at any rate).

The situation doesn't come up in Pascal because variables go in the var block (starting with the 'var' keyword) and functions and procedures both start with keywords ('function' and 'procedure').


OK, thanks. I was focusing on the "definition" part, rather than the "usage" part of the types.

It never occurred to me to put variables names inside parens in a declaration when I did C, though. But OF COURSE it's legal obfuscation.

As stated elsewhere, I don't know why C had to screw up Algol's syntax so badly. I guess K&R liked curly braces much more than BEGIN/END, and why stop there, once you get going. Maybe somebody had a love affair with APL???


> The point is that the language uses a set of the same operators with the same precedence and associativity both in declarations and in normal expressions.

Using the same precedence and associativity in declarations and normal expressions doesn't really help the compiler writer though, so I'm not sure how this supports your point. If anything, it complicates the parser by making it harder to decide if a given sequence of tokens is a declaration or an expression (e.g. the classic "a * b;" problem).

> In my view, a "user-oriented" declaration of an array of pointers would look like `pointer[10] of int`, and not `int *arr[10]`.

It sounds like you're saying that C's type syntax isn't easier to read than other, similar, possible syntaxes. On that, I completely agree (in fact, I think C's type syntax is horrendous). However, that's orthogonal to the issue of Dennis Ritchie's original intent when designing C. From his writings at least, it seems like he wanted to make it easy for the programmer to reason about the syntax and I'd even argue that he took steps to complicate the compiler to accommodate his chosen syntax. I'd also argue that he failed horribly in accomplishing his intention and the syntax you're describing would have been much easier to read.


There is a remark in Fraser & Hansen's LCC book noting how complicated C declarations are to parse because type information is spread out through the declaration. You can see it in your example. Inside the compiler, you'd parse that to something like [VAR arr [ARRAY [* INT] 10]. But to get there you're doing something like: "oh what follows is an INT, no it's a pointer to an int, it's named 'arr', oh fuck it's actually an array of 10 pointers to int."


OR, it would look like "decorate a semantic tree until the declaration is complete. Now, what do we have? An array of pointers to int. Fine."


Yeah, I don't know what these fools are complaining about. You put up a struct, parse the whole statement into it. Doesn't seem so tough to me.


You can't put up a struct because the structure of a C type is arbitrarily complex. E.g. Multiple levels of pointers. So you have to maintain a type as some sort of tree, and then as you're parsing it you have to fill in the right parts of the tree.

Not rocket science, obviously, but the original statement was that C types were designed to be easy to parse, which clearly isn't true. If the types were prefix or postfix, it'd be trivial to build the trees in a recursive descent parser.


Is it arbitrarily complex? Maybe nested array types, but every other type can be expressed with an indirection count, and a leaf type. That said, if it can't go in a struct, it can easily go in a linked list with the same information for each level of indirection.


What about function types? That alone gets you to "tree" complexity.


You get used to anything. I find that example no better.

And remember declarations can be arbitrarily complex. How do you declare a pointer to arrays of pointers to pointers to arrays? In free prose.


But C doesn't give you "exact control of memory layout". Bit-fields, structs, virtually everything except arrays has implementation-defined or unspecified memory layout.

If you look at another old language, Ada, you'll find much more precise control.


>implementation-defined

In theory that's important, in practice you rely on implementation-defined behavior and put it behind a layer of macros. It's not perfect but it works - low level capabilities are not the problem in C.

Lack of high level abstractions make scaling it up and maintaining a PITA.


C11 has `_Alignas(...)`, which works both with a size or a type. Before that, compiler-specific alignment attributes could be used, for example, GCC's `__attribute__((aligned(...)))`.


That's true but at the end of the day it's fine as long as you aren't using those constructs for serialization/deserialization of data between machines. Rob Pike has a very good post about how Plan 9 needed no endian specific #defines:

https://commandcenter.blogspot.com/2012/04/byte-order-fallac...


There’s a lot of problems with C, like the fact you have to break out of case statements, or string concatenation, or overuse of the static keyword. These are actual problems, which C++ doesn’t even begin to try and solve, instead introducing useless features and bloat. C is still one of the few popular languages that allows you to talk to the machine directly. It’s a language that doesn’t rely on hidden run-time logic, or hidden implementation costs. It doesn’t enforce any particular programming style on you, and lets you do anything a process is capable of doing.

Software engineers these days are so lost in abstractions and design patterns that they forget that at the end of the day a program is just data, and a set of operations that transform that data. There’s huge value in writing code that does everything explicitly from top to bottom, both from a readability, and from a debugability standpoint. Instead, these days, software is designed using UML diagrams and OOP concepts while pretending that objects are ideas that float in some abstract disconnected space. All in the name of concepts envisioned by people who write books instead of programming, and who never saw even a glimpse of a problem you’re trying to solve.

Languages like C will never become obsolete. There’s always a new frontier of hardware that needs fast, tailored software. Few years ago that was phones, and watches, next it might be tiny implants. Anybody who thinks languages like C is obsolete should ask themselves “why was my Comodore64 more responsive than a PC i bought in 2016?”, or "why is my dual-core Android smartphone with 1GB of RAM barely usable after a year”. Modern languages and programming practices have this amazing ability to completely negate the massive progress hardware has made.


> “why was my Comodore64 more responsive than a PC i bought in 2016?”

Not because of C; that's for sure. The two biggest reasons are:

1) Your Commodore 64 is doing at least a couple orders of magnitude less actual work. This has less to do with language choice than it does with a programmer who is fully in touch with the problem to be solved and can identify unnecessary work to eliminate.

2) Your Commodore 64 is effectively designed in a way that prioritizes latency over throughput. Pipelines and buffers are small-to-nonexistent. When you read the joystick port, you sample the state of that joystick at that very microsecond; you're not asking one microcontroller to ask another microcontroller to start thinking about sending the input it sampled 10 milliseconds ago. The worst-case latency from changing the video data to seeing it on screen is measured in lines, not frames (assuming a low-latency display like a dumb CRT). Virtually nothing is asynchronous. Systems aren't built that way anymore because we want to be able to push massive amounts of data through subsystems that can't all run in lockstep to one fast master clock, and that means lots of buffering and burst transfers.


I don't understand why you guys hang on the fact C64 wasn't coded in C. I never said it was. I talk about C-like languages, so low-level, with little to no overhead.

I understand that computers worked differently back then, and that software usually had full control over the machine, but please, don't talk to me about buffering, etc. when we're talking 6502 versus an i7, or an A8. I understand that input and output aren’t instant these days, but that’s an order of tens to hundreds milliseconds of delay. It doesn’t explain why Visual Studio takes tens of seconds to compile “Hello, World!” on a 16 core processor. Or why your Chrome tab eats 600MB of RAM.

Since you want to talk about buffering and built-in input delay, here’s a comparison of text editor performance:

https://pavelfatin.com/typing-with-pleasure/#windows


I guess what I'm getting at -- and could have communicated better -- is that this approach of "buffer/queue/cache/defer everything because it's faster right now, never mind that we might have to stall and flush the pipeline all at once later" has worked its way up the stack, with the worst-case stall growing in magnitude as the path cascades through progressively more layers and bigger buffers (cf. bufferbloat in networks [1]). Among other issues, it fools people into thinking that expensive operations are cheap. I think that pattern is far more responsible than language-imposed overhead for the weird quasi-random delays that are seen in modern systems. You can certainly connect this to popular idioms and some standard library facilities in some higher-level languages, but I think that connection is largely a historical accident rather than having much to do with the languages themselves.

[1] https://en.wikipedia.org/wiki/Bufferbloat


>>Few years ago that was phones,

ObjC and Java

>>Anybody who thinks languages like C is obsolete should ask themselves “why was my Comodore64

Assembly and Basic.


Surely Basic is not responsible for the Commodore being responsive. Java was a total dog on phones for a long time, too.


Well C wasn't responsible for the Commodore either. And whether Java was a dog or not, thats what people went with, from your SIM card to JavaME snake games to Dalvik/ART. People looked at these resource constrained environments and went "yeah lets use Java".

ETA: it's probably worth put scare quotes around "resource constrained" when talking about Android and IOS but phones were dreta's example.


and promptly went out of business when the iPhone showed up


a phone that launched full of ObjC and Javascript with a SIM card running Java...


ObjC is a native language and a direct descendent of C, and powered the phone's built-in apps. JS was provided for 3rd party developers, who incidentally rejected it. I wasn't aware of the SIM card running Java. I guess Java was good enough for that job since I never noticed any performance issues with the iPhone, unlike my Palm Pre which was plagued with them, and had a terrible battery life.


I still remember the load times for Last Ninja - I'd prefer an ssd any day.


>>>Few years ago that was phones,

> ObjC and Java

To defend the parent's point, Objective-C is a pure superset of C. And there are quite a few frameworks on the Apple platforms that are pure C (not Obj-C), e.g. CoreGraphics, CoreAudio, ImageIO, OpenGL, etc.


Take a look at the useless features and bloat in this video. Modern C++ isn't what you think it is.

https://www.youtube.com/watch?v=zBkNBP00wJE


That's a great talk that really shows that with modern C++ you can do some really nice optimizations and code minimizations, but it seems to require a discipline that can require a lot of thinking in the complex world of C++, to find those methods that for example Jason is talking about here.

Was pretty eye opening video to see that, that some of those complex looking instructions with a modern optimizing compiler can be surprisingly neatly optimized away.


I think discipline is a keyword here. It feels to me like being a professional means a fair amount of discipline should be required. Unless you're the nephew's uncle in law of the founders sister that knows VB and is "pretty good with compooturz" or the like.


Could’ve easily written that in plain C. All the usage of C++ concepts like templates did, was made him write more code than necessary, and made the code harder to understand.


I agree completely. I couldn't believe it when he made that Frame struct and completely abused RAII just to execute some code at the beginning and end of a block. The only really big wins were the wrappers for writing to different memory locations, all of which could have been implemented in C using macros or inline functions.


You're not worth the effort.


If i’m missing something obvious, please explain to me how using any of the C++ concepts made the code any better.

You have to keep in mind that this is a very simple piece of software. I wager you that using things like templates made the code unnecessarily harder to understand. I’ll also wager that by using templates he made the code harder to scale, since any remotely complex template, just like an overused base class, eventually becomes a set of special cases, or a set of specialised templates which defeats their purpose in the first place, and makes your code a nightmare to step through.

This is a typical example presented in a talk. It doesn’t reflect any large real-world software, where complex language solutions (useful ones, not the ones in C++) could actually benefit the programmer.


I used a c64 for many years in my youth, and "responsive" is not at all the way I remember it. Have you forgotten things like "the loading times from the 1541" and "geos"? (for example: https://www.youtube.com/watch?v=qpX6TIa3U1o and warning: soundtrack)


Whoa now friend. The 1541 is a computer onto itself. The slow loading was because of Commodore’s desire to keep backwards compatibility with the VIC-20 drive, which used a buggy 6522 chip, not because the 64 couldn’t go faster.

BTW, if you want to run GEOS faster, get an REU and let the DMA transfer fun begin. This is 2016 after all; you must keep up with the times :)


The industry took a giant step backwards when C displaced Pascal. The original Macintosh apps were in Pascal and the MacOS API were all Pascal-oriented.

C has three fundamental issues:

1) How big is it?

2) Who owns it?

3) Who locks it?

C's lack of help in dealing with all three is the main cause of most memory-related program bugs. I once tried to fix #1 in a backwards compatible way. See [1]. After much discussion on some mailing lists, there was general agreement that this was technically feasible but not politically feasible.

(I once had hope that Rust would replace C, but Rust has gotten too fancy, with too much input from the functional programming people, the template enthusiasts, and the type theorists. Rust isn't bad, but it's at least as complex as C++.)

[1] http://www.animats.com/papers/languages/safearraysforc43.pdf


   Rust has gotten too fancy
Could you identify a simple Rust core that has all the good parts while avoiding the complexity you mention? What is superfluous in Rust?

By "templates" you mean macros? If so, how should a language implement the functionality that's currently delivered by macros?

I'm asking because I'm thinking about adding HKTs (higher-kinded types) to Rust.


  > it's at least as complex as C++
You repeat this phrase like a broken record, but have yet to back it up every time that I have asked why you have this opinion. C++ is an order of magnitude more complex than Rust.


- You need templates to do anything, because the standard library uses them so heavily. They're rather opaque.

- The functional control flow style, where everything involves closures, is much harder to understand than imperative control structures.


Generics, not templates.

They're not opaque. Templates are more opaque because of the lack of self documenting bounds, perhaps (i still wouldn't call them opaque, just more so than Rust generics)

This is the millionth time you're repeating that garbage about imperative control flow. It boils down to a use of closures in a few stdlib functions. We've had this argument before, the essence of your point is that it is different from C. Of course there is a learning curve but that is there the other way too. Once you get used to it the control flow is pretty straightforward. And in most cases you can avoid the closures and write your if or loop manually.


Rust's control flow primitives are coming to be defined by libraries, not the language, and tend to grow over time. For a long time, we've been able to get by with a small, well-understood set of control flow primitives, those defined by Wirth many years ago. Now we have this in Rust:

Type Option. The 2014 version.[1] The 2016 version.[2]

(Coming soon, the "?" operator.[3])

Here's an example of excessive cleverness [4]:

    fn main () {
        let number: f64 = 20.;
        // Perform a pipeline of options.
        let result = Some(number)
        .map(inverse) 
        .map(double)
        .map(inverse)
        .and_then(log) 
        .map(square)
        .and_then(sqrt);
        // Extract the result.
        match result {
            Some(x) => println!("Result was {}.", x),
            None    => println!("This failed.")
       }
    }
(It's surprising that you can use .map on a singleton. Does this actually generate collections of one element and discard them, or what?)

The designers of Go knew when to stop. The designers of Rust don't.

[1] https://web.archive.org/web/20140814230239/http://doc.rust-l...

[2] https://doc.rust-lang.org/std/option/enum.Option.html

[3] https://github.com/rust-lang/rust/issues/31436

[4] https://hoverbear.org/2014/08/12/option-monads-in-rust/


You're redefining "control flow primitive". Those are not primitives, and they affect control flow as much as functions do, aside from `?`. Closures are a concept that C doesn't have, but once you get them they obfuscate nothing. These library calls obfuscate control flow only as much as regular function calls would.

Nor are you forced to use code like the excessively clever version you talk of. People who are used to monadic programming have the choice to.

You're making the same crappy arguments like a broken record. I'm not having the same tired old discussion again.


I've only looked at Rust in passing, but looking at the documentation, that example is pretty trivial.

'.map' maps over the option. '.and_then' is the monadic bind operation, that you find in pretty much every modern programming language (frequently named 'flatMap'; even Java has this nowadays).


>The original Macintosh apps were in Pascal and the MacOS API were all Pascal-oriented.

Yeah, i remember using the Mac OS written in Pascal. Strangely enough, i remember it being buggy as hell and crashing pretty often (no memory protection at all).


I've read that large bits of it were written in 68k assembly too, so those parts might be responsible for the crashiness.


You can't write systems programs in Pascal without escape-to-assembly. My first big 'C' project was a significant quality improvement over the Pascal it replaced, partly because of this.


Funny I managed to do that in Turbo Pascal.

Also C standard library cannot be implemented without using Assembly, unless one makes use of extensions not defined in ANSI C.


You mean you managed with assembly, right? We were able to Vastly reduce the assembler footprint when using 'C'.


No I managed to do that with Turbo Pascal.

If I was talking about inline Assembly, which Turbo Pascal supported and to this day still isn't part of ANSI C, I would have said so.

The only times I actually was forced to use inline Assembly was to optimize graphics rendering and create a mouse device driver.

Both of them were a one time project.

Once upon a time I used to have some fun asking C devs for features not available in Turbo Pascal, while at the same time showing off all the safety and large project features that Turbo Pascal had and C to this day still lacks.

Back on those MS-DOS and Windows 3.x days there wasn't any feature that C compilers had and Turbo Pascal didn't had a similar feature.


How would you do an INT 21 call? It's been too long; I don't remember that being possible except as either just assembler or inline assembler.


With the MsDos procedure.

    program demo;
    uses Dos;
    
    var
        regs : Registers;
    
    begin
        regs.ah := $02;
        regs.dl := $07;
        MsDos (regs);  { Beep! }
    end.


I'd say 1) is completely tractable, politically or not. If you're careful, "everything has a size." Only use an opaque pointer if there's... metadata that forces correct usage.

But this is up to you to enforce.

2) & 3) are pretty much covered in undergrad CS programs. If it hurts, you're doing it wrong - you don't have to dip that deeply in to OO to find excellent patterns for solving it, of you even need to go that far.

On certain platforms like the Win32 API, 2) &3) are hopeless by, it seems, design. Just do your best then :)


Aren't things like bounds checking (#1) an implementation detail? You could implement C with bounds checking if you wanted to. For example you say 'void* can't express size information' - well it can if you chose to implement it as a tuple of address and size. Out of bounds is already undefined, so you could make it abort with a nice error message if you wanted to. There's no need for new language features in order to do that - just change your implementation.


I'm looking at writing a compiler for my own language, and I can certainly use ideas for anything that's technically feasible.


>The industry took a giant step backwards when C displaced Pascal. The original Macintosh apps were in Pascal and the MacOS API were all Pascal-oriented.

The latter sentence sounds like a non sequitur.


The author uses C and C++ interchangeably, but these are two distinct languages.

Although technically a superset of C, C++ has constructs and paradigms which make you think in totally different ways.

In fact C++ introduces many language features which (should) protect programmers from 'C' mistakes in their C++ code.

For example, in modern C++ you shouldn't manage memory directly, but use smart pointers and containers. You can, but you shouldn't.

And there are dozens of such recommendations. Which is the right approach, imho. You can still do the low-level dangerous stuff if you feel brave, but officially you should stick to the 'safe' paradigms that the language now offers.

However, the paradigms that C enforces on you are the paradigms of how to do things fast. You wear no protection so you are much more nimble, but much more vulnerable at the same time. Which is good for some types of projects, but not for all.


C++ is not even technically a superset of C...

`auto` has very different meanings in both languages, and you can't omit the return type of an int-returning function in C++


Have you ever tried to compile an old C program which extensively used variables called 'old' and 'new' with a C++ compiler? I have.

Also, C++ doesn't allow implicit casts from void* to any pointer type, which is a common C idiom.


"The author uses C and C++ interchangeably, but these are two distinct languages"

Sure they are. C/C++ are often cobbled together in a sense that they are both low level, non-memory safe and non-type safe languages, as opposite to high level, safe, memory managed (aka "modern") languages.


C++ is a type-safe language (unless you use C-features like void*, which you shouldn't in C++) it is high-level (thanks to templates likely more high-level than many others). If you think of C++ as "C with classes" then you can mix them, but C++ evolved quite a lot with C++11 and following standards and implementations.


I somewhat agree with that, but unlike in most other modern languages, type-safety is not enforced, thus you can't call it a pure type-safe language even though you can write programs in type-safe way. Same with low/high level -- you can use C++ as low level (C with classes) or as high level according to modern C++ standards (C++11, C++14) and best practices. There's no strict classification, I just wanted to highlight C++ similarities with C.


The C++-fan would say you have the tools to ignore type safety, so you can opt-out. But that's of course a matter of perspective. A good, modern, tutorial should only teach type-safe tools to beginners.


There's too much undefined behaviour for even modern C++ to be considered type safe: a dangling reference or invalidated iterator allows you to break it easily, accidentally. (Even "opt-out type safety" is a stretch, opt-in-with-extra-tooling-and-tests seems more appropriate.)


Speaking of type safety, has modern C++ solved the object slicing problem yet?


Yes, they're called references.


In the context of the first paragraph in this post, they are not. The stuff he talks of in that paragraph applies to C, not C++.


> C helps you think like a computer

I recently bought K&R, doing a bit of micro-controller stuff without the Arduino abstractions, and this I can agree with. I now have an idea how code really gets executed on a CPU, and how the contract between the bare metal and the assembler code for doing so looks like.

That the concept of a stack is built into x86, and why calling conventions are important, all of this stuff I had no idea doing Java development for all these years. What do I need a linker for? Object files, what?

I don't really like it, but there is something strangely beautiful about it - if you look hard enough, you can see the machine shining through the unforgiving cold blue ice that is C.


>calling conventions are important

Curious what you mean by that, in what way learning C made you understand this better than Java or any other language did?


As mentioned, I don't particularly like C, so I've explored Rust a bit. I tried to expose a Rust function to C, heres the signature:

  #[no_mangle]
  pub extern "C" fn count_substrings(value: *const c_char, substr: *const c_char) -> i32
So I came at it from the opposite direction, and once you go down the rabbit hole of what "no_mangle" and "extern C" does, thats where it leads you.

Along the way you inevitably pick up topics like alignment and struct packing.


I had the same experience with low level machine control using compilers for dialects of Basic and Pascal, back in the mid-80's early 90's.


TLDR: Goto last line of this comment

> It is still one of the most commonly used languages outside of the Bay Area web/mobile startup echo chamber;

Fair.

> C’s influence can be seen in many modern languages;

Saying that that's a reason to learn C is a non sequitur. Why?

> C helps you think like a computer; and,

So does C++. And Rust. And D. All the stuff about databases can be done in any of these languages, C doesn't have a monopoly over letting you be close to the metal. It's one of the only languages that pretty much forces you to be close to the metal, but that's not a plus point.

> Most tools for writing software are written in C (or C++)

True. Hacking on your tools is great. But hacking on your tools rarely requires proficiency in a language (unless you're doing something major, which you're not), I've hacked on tools written in all kinds of languages that I don't know.

And the justification behind this point follows the same flimsy reasoning as the previous one, the reasoning that C is the only low level language out there. The post gives "browsers, operating systems and languages" as examples, but all three of these exist in Rust (Servo, redox/intermezzos/a bunch more, and rustc). I'm sure that D is up to the task for such software too, if it doesn't have them already. C++ of course has all of these.

Sure, you need to know some C when doing low level programming and/or FFI. But nobody's arguing against that. "Everyone “has been meaning to” learn Rust or Go or Clojure over a weekend, not C." is a false dichotomy. With Rust for example it's pretty easy to learn enough C to manage once you've learned Rust (there are tons of folks in the Rust community who have done this). You're not forsaking C, you're just focusing on something better. You can learn enough C to be able to read code and do FFI, and you're pretty much set for writing databases or operating systems or networking stacks or whatever. Reading C isn't too hard and if you speak another low level programming language you can pick this up over a weekend.

The only valid argument I see here is that C is is still used a lot in existing codebases, and you should be proficient if you want to be able to contribute significantly to them. Of course. But cut all the crap about C being the only language that can handle these use cases.


  > The post gives "browsers, operating systems and 
  > languages" as examples
Actually, I can't think of any browsers that are written in C. Unless, wait, does Lynx count? :)


netscape was when it was popular, then they re-wrote in c++ (when compilers for that language were much worse than they are now) and everything went to crap. Templates work now and are pretty much consistent accross platforms, they didn't then or so says JWZ, wasn't there to confirm it but seems very plausible based on what experience I had of visual c++ 6 at the time


netsurf?


> > C’s influence can be seen in many modern languages;

> Saying that that's a reason to learn C is a non sequitur. Why?

To better understand how and why those modern languages work the way they do. The same as learning assembly helps you better understand how computers work (and what C really does under the hood). Learning about layrs under your abstraction layer typically leads to better understanding of your abstraction layer.


C is not a "layer under the abstraction" for C++, D, or Rust, that's my whole point. These languages can be as low level as C, and their higher level bits are implemented as an abstraction layer in the same language.

I understand the need for mechanical sympathy. This is not mechanical sympathy. At best, this is _historical_ sympathy.


Good points.

As a programmer, one has no choice most of the time on what language a program should have been written, in my career, I have mostly been modifying and adding code than writing new code from scratch. This mostly means C, for anything close to hardware, as electrical/electronics engineers are familiar with it. Another way of saying this is that, "I program in whatever language, my team is programming in".

Embedded systems also have size limitations and the problem of bootstrapping on new hardware. C has time and again proven to be easy.

If you examine a typical GNU/Linux distro, you see all kinds of programs written in C that ought to have been written in a type safe language because most of the time, C's features were not required for the program.

I wish more and more "new" programs are written in all kinds of languages (not just Rust, but in Python or Haskell or OCaml ..) other than C and that we restrict C to the things we really need it for.

Overall, knowledge of C is only going to help. Whether you want to use it to use it for your next project is something that depends on the problem at hand.


> Overall, knowledge of C is only going to help. Whether you want to use it to use it for your next project is something that depends on the problem at hand.

Of course! To be clear, I'm not arguing against learning C in general. I'm very happy that I learned C.

I'm arguing against the point made in the post that you should learn C over newer languages. And like I said, "shit my codebase is in C" (i.e. point #1 from the post) is a totally valid reason to learn C. The other points are not.


Although there is little need for C to evolve (apart from certain security holes), I think there is space. Thankfully, there are still persons that care about its evolution.

The K&R is a classic, but it shows its age, especially as we have reached C11 by now. And C2x might be right around the corner. A third edition of the book won't be that bad.

http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2021.htm


There are other books on C besides K&R. My current reference is C: A Reference Manual, Fifth Edition [1], which I usually pull out when I need to check on syntax and standard libraries.

- http://careferencemanual.com/


Isn't K&R's book littered with code examples that were canonical in the 80's but have glaring security holes? I wonder whether it's a good idea to have students work through "the bible". Hopefully they provide extensive exegesis in the lectures.


> I wonder whether it's a good idea to have students work through "the bible".

That's probably a pretty good analogy - it's useful maybe for historical value, but it has to be understood that some of what's listed is no longer best practice and you can't, for instance, go around stoning adulterers and amputating thieves' hands.


Introductory programming language texts do not concern themselves with "security holes" usually. This is a strange criticism.


Teaching C without teaching buffer overflows and undefined behavior is reckless.


Sure they do.

To clarify, GP here is probably talking about UB security holes, not stuff like XSS. Most intro texts I've seen have "watch out!" notes all over the place.


Neither K&R nor K&P include any network-facing or security-boundary-crossing code, if I remember rightly.


Most of this just says C was a popular implementation language following UNIX’s fame. So, many things are written in C. So, knowing C will help you understand how they work. People doing cutting-edge work that pick C are usually clear that they pick it for compiler and talent availability, not technical superiority for problem at hand. Now for the No’s. You don’t need to know it for systems programming as plenty of better languages exist for that. It barely contributed anything to good programming languages compared to ALGOL, Simula, Modula, or LISP. Many key designs pushing OS’s forward were not in C: Burrough’s ALGOL, IBM’s PL family, MULTICS in PL/0 & BCPL (UNIX started as MULTICS subset), VMS in BLISS, LISP machines in LISP, Smalltalk-80 in Smalltalk-80, Oberon systems in Oberon dialects, Spin in Modula-3, JX in Java, and recently unikernels in Ocaml.

So, C is a necessary evil if you want to understand or extend legacy systems whose authors preferred C. It’s also trendy in that people keep it mostly like it is while defending any problems it has. That’s a social thing. Like COBOL and PL/I are with the mainframe apps for companies using them. It’s not the best language for systems programming along a lot of metrics. One doing clean-slate work on a platform can use better languages to do better in the long-term. Short-term benefits matter, though, so such projects often compile to C or include C FFI to benefit from legacy stuff.


While everything that the author wrote is true, and I will forever love C, sometimes I wonder if I didn't get short changed by never having been taught ANSI Common LISP at the university.

LISP is worth learning for a different reason — the profound enlightenment experience you will have when you finally get it. That experience will make you a better programmer for the rest of your days, even if you never actually use LISP itself a lot.

http://www.catb.org/~esr/faqs/hacker-howto.html

Functional language? Check. Stateless? Check. Reentrant? Check. Has a compiler which can generate machine code ELF binary executables? Check.

That's not to say one shouldn't learn C - after all, C is the contemporary portable assembler. But if one should also learn C, one should learn assembler too, preferably on a processor with an orthogonal instruction set architecture, like a Motorola 68000. Understanding both, we come back to the author's point - without understanding how the machine works, fast, small, efficient and elegant software will remain forever elusive.


Ada and Pascal are not dead and are viable alternatives to C. If you don't know them, check them out.

The C philosophy "the programmer knows what they're doing" does not necessarily scale. It works when your team is strong or well supervised, but if that is not the case due to location, budget or whatever reason, unless you can afford a static analysis tool like Klocwork that can weed out mistakes while not sacrificing productivity, by going low level you have picked a hard battle.

C gives you a lot of power, but also a lot of rope to hang yourself with. I don't even want to imagine what kind of apocalypse I would see if you give multithreading and memory management powers to the caliber of people I've seen recently.


C has one quality that no other language has. You can write libraries in it that can easily be used from all other languages on every platform.


That is only true if the OS was written in C.

What many wrongly call the C ABI is actually the OS ABI.


The ABI is certainly helpful, but I don't think it's the only reason why C libraries are easier to call than libraries written in other languages. C doesn't have generics, inheritance or a GC, all of which make integration a lot more difficult.


A set of features shared by many other, safer, system programming languages.

Their OSes didn't had a C ABI, rather an ESPOL, NEWP, Modula, and so forth ABI.


Sure, theoretically there could be viable alternatives to C.


What makes many of us sour, is that it isn't theory, rather already proven in the past, back when C was only a UNIX language and barely relevant outside it.

It was the commoditization of UNIX in the industry that pushed C outside UNIX, just like we nowadays have JavaScript outside the browser.


It sounds remote today, but you're right that it didn't have to be this way.

What should make you even more sour is that another opportunity for starting over seems to have been passed up when Google only recently chose to build Fuchsia on top of a C++ based kernel.


"...C ABI is actually the OS ABI."

Can you please elaborate on it please?


I don't think that's a special or unique quality of C. I'd say it's equally true of Pascal:

http://www.freepascal.org/docs-html/prog/progse55.html


I think Free Pascal supports all sorts of features that don't translate very well across type systems. Things like classes, inheritance and even generics I believe.


I'm not sure what point you're making. You don't export those types or features in your library's API. Here's some discussion of type compatibility between C\C++ and Pascal:

http://www.rvelthuis.de/articles/articles-convert.html


The point I'm making is that you can write reasonably idiomatic C and export the result for use in other languages.

There are some things in C that are not easily exported, like varargs or complex macros, but on the whole the impedance mismatch is a lot less dramatic than in any other language I know, including of course C++.


> The point I'm making is that you can write reasonably idiomatic C and export the result for use in other languages.

And as I say, you can do the same in Pascal. We're back where we started. :)


Maybe your experience with translating complex type system features and generics from one language to another has been more pleasant than mine.


>> Learning about operating systems would be much harder without C. The operating systems that we use are mostly written in C, the C standard library is tightly coupled to the syscall interface, and most resources dealing with operating systems concepts assume knowledge of C.

I wonder what if Kernighan, Ritchie et all decided to write UNIX in Pascal, not in C.

Hmmm.... :D


I don't think that was ever very likely, it kind of seems like Mr Kernighan didn't like Pascal: https://www.lysator.liu.se/c/bwk-on-pascal.html. I'm not at all clear about the detailed timeline, but this link is a pretty good answer to that thought. :)


It's a shame Modula wasn't available until the early 80s (as far as I know), but unfortunately, you are right.

C is vastly preferable to writing assembler on multiple platforms, though.

The real tragedy is IT Managment(TM), which insisted on C++, and after that inevitably failed (for most things outside of video games), insisted on Java due to all the core dumping craziness.

Can't find the original source doc, but here goes: https://blog.acolyer.org/2016/09/07/the-emperors-old-clothes...

At least K&R took quicksort(), even if they didn't take the array subscript bounds checking part (etc) to heart.


One other C inspired tragedy: WHY, OH WHY, did they insist on putting identifier names AFTER (most of) the type information???

Algol had it right, why screw it up?

I guess it doesn't matter if your (optional) type is word^H^H^H^H int, but this has descended into chaos in Java land.


On that famous rant he selectively chosed to ignore all Pascal dialects that offered the features he complained about, while at the same time ignoring that outside UNIX there weren't full C compilers available, rather dialects.


Pascal. Dialects. That was the problem.

Pascal was designed as a teaching language, then people realized it sucked as a language in the real world, so they extended it. There were quite a few Pascal variants in the late 70s / early 80s, and none of the useful code you could write in them was portable.

C had all the features you needed in an OS-level language, without resorting to variants (the X86 near/far bullshit notwithstanding, you used macros and typedefs to shift that stuff around). So Pascal more or less died in the industry.

I was at Apple when they converted from a Pascal shop to a C shop. There wasn't any big managerial push, just individual engineers or small teams realizing that the nightmare was over and they could use a language that sucked a lot less.


However I remember the chore that was writing portable C code when you could not rely what compiler was available, specially outside UNIX.

For those of us in MS-DOS and Windows, only Turbo Pascal mattered, to the point it was more relevant to be TP compatible than supporting ISO Extended Pascal, which also sorted out most of the complaints.

Complainants which were nonexistent in Modula-2 anyway.

The industry choose the easy path, now we all pay with our mainstream OSes built on top of a quicksand language.


That's why I think UNIX was a very good marketing vehicle for C. In the 1970s, universities began to adopt C.

Since UNIX was written in C, academists/students began to learn C, and that was how the rise of C, now known as "the lingua franca of computing", started.

Personally, one valid reason for me to learn C today is to understand/hack stuffs written in it. A fine example is the Linux kernel.

I still don't get the "C is close to machine" mantra. If you want to understand the machine, why don't just learn assembly instead? :D


In my experience, assembly makes you do a lot of busywork over and over to achieve certain constructs. Loops and function calls come to mind immediately.

They're not hard things to do in assembly, but you'll need to write those every single time you need one. So then you start making macros to handle this for you. By that point the conceptual leap to C isn't very far.


There was TUNIS in the early 80s, an operating system binary compatible with V7 Unix and implemented in a Pascal-like language. I've been meaning to find more information on it, but there's almost nothing online. It might be the only Unix clone that isn't implemented in C/C++.


They would have re-invented, Burroughs OSes from 1960's and made computing a safer world. :)


Or if Wirth had had the marketing nous to call Modula Pascal+ and Oberon Pascal++.


if anything c is more popular now than 5-10 years ago. not c++ but normal c. especially with the pendulum swing away from oo

it's almost an ideal hipster language.


Ugh, that's the last thing I want to see. I didn't start diving into Rust until very recently for two reasons: 1) the instability of the language and 2) the terrible, juvenile hatred for C and C++ that seems to almost be a pre-requisite for participation in the ecosystem. Rust until recently was an anti-C hipster's wet dream ("upstart"-y, not really yet ready for wide-spread use, and a convenient goto for folks wanting to have a bit of trendy "bash C (or C++)" fun.

Both items have diminished considerably lately, though, and that's something I can appreciate.


> the terrible, juvenile hatred for C and C++

I'm surprised (and sorry) to hear that. The Rust community tries very hard to avoid language zealotry. If you see it on any of the Rust forums (can't speak for other sites), please report it.

I've seen lots of well-reasoned complaining about UB and other issues that C/++ are prone to, but not much juvenile hatred.


I hate C since 1992.

By now juvenile is something I surely am not.



Very good reading, including the two links after the transcript.

I feel that both the OP and Dijkstra are right in their own respective way.

I do hope that Rust, for one, will turn out to be part of a path to better practise.


Besides the reasons already listed, one more is C can be used to program almost all of the micro controllers, an essential electronic element in modern life.


There are also Basic, Pascal, Ada and Oberon compilers available for such processors.


With GHz level processors getting so cheap and developer time being so expensive, I wonder if that will always be the case. I've recently moved from using microcontrollers and C to using raspberry pi and beaglebone with python and it's made my hobby projects more pleasant. I'm curious about whether I'm going to run into issues later, though.


It'll always be the case because of size-weight-power constraints. I have a project on right now where we need it to run on a CR2032 style coin cell for year or two.

The OO nature of Python can also get in the way of certain things.


Nobody is "forsaking" C.

As mentioned in the article it is still a good way to learn about computers and it's the true "lingua franca" of our times - that said it needs to be put to rest for infrastructure software now.

I for one welcome our new Rust writing overlords.


While K&R is a nice book, it's really not sufficient to become productive with C. IIRC it doesn't cover things like make, valgrind and libraries. There's a book 21st Century C which does the job of addressing that.


I ended up with a book, back in the late 80s, called (I think) "C, An Advanced Introduction". To me, the K&R book was just too "circular", as the authors were too close to the subject.


"Most of our students use interpreted languages with popular implementations written in C."

Well, guess what? Most of the 'C' code I've written in the last ten years pretty much reflects the selfsame reality - next to nothing is done directly in 'C' but rather as posts of "transactions" to an interface. These may or may not be in text form but it's not much work to make 'em be in text form.

By the way - 'C' is excellent for text manipulation, even using the crufty old library calls.

If you do this and you do it carefully, all the horrors of 'C' become at least manageable. Done carefully enough, you don't even need all the new "safety" furniture.

I blame nobody for not wanting to do this, but can we at least self-identify as "will" or "won't" and behave accordingly? 'C' is no bugbear and your favorite interpreter can't save you from a bugbear that doesn't exist. When problems occur, it's because people didn't take the fifteen minutes or fifteen seconds to prevent them.

I just find myself gobsmacked that people feel so utterly powerless against the language.


C helps you think like a certain 1970's abstraction of a computer.

I really hate the "C is how a computer really works" argument in favour of learning C.


That 1970s abstraction is certainly closer to modern computers than any higher level language, and is quite a nice introductory route to learning more accurate models.

We still start with the Bohr model in chemistry for the same reasons.


Do you teach assembly language too? It's a lot better at #3 (teaching you to think like a computer) than C is, and it makes it a lot easier to fully grok C concepts like the strange equivalence between pointers and arrays, between character arrays and strings, et cetera.

C is trivially easy to learn if you know Assembly Language plus any other imperative language.

My school taught assembly language as a full semester course (along with other low level concepts). That and an intro to computers course that used either Pascal or Fortran were requirements for the 200/300 level courses on operating systems etc. that used C but didn't actually teach it. They made seminars available for those who wanted to be taught C rather than having to learn it themselves. I believe that this is the right approach.

Of course, soon after I went through they fell victim to the hype wagon and switched to Java.


From TFA:

> Most engineers should take head of reason three, although our students also learn computer architecture and at least one assembly language, so have a firm mental model of how computers actually compute.


Thanks, I missed that. Do they learn assembly language before or after they learn C? Are they explicitly taught C or do they learn it while using it in a course like Operating Systems or Data Structures?


To an extent, so does C++ help think like the machine, but you can skip a lot of syntactic red tape. Until you get to advanced uses of templates.

It is also useful in order to show how those "smart" things in higher level languages work, using standard library as examples.


C + Lua == 21st Century "C Programming"

There is no silver bullet for everything, obviously, but for me the above combination is a match made in heaven. Lua is small and adds only the necessary tools to take care of higher abstrations in many different paradigms.

Even if these two are different languages, they are amazingly complementary. I feel that a good introductory CS course could simply teach in one semester C and the next Lua. By the end of the year students will be able to tackle a huge amount of problems, as well as learned a useful systems and scripting language.


C + Scheme (with Guile) can be more fun. Try it.


So, I quite like C. I don't use it very often, but I think that it's like a sharp knife.

I don't quite see the point of C++ though. Can someone explain me why we should continue to bow into it?


If you want a C-level sharp knife, and you want to work with abstraction (not just OO and abstract data types, but also generics and metaprogramming), then C++ is one of the few choices. D is maybe another option (if you can live with GC). Maybe in the future also Rust, but my impression is that the Rust generics and metaprogramming story is not quite there yet. C++ is mature, so it's a safe choice in that respect: large installed base, ISO standardised, mature tooling with multiple vendors.

If you don't need the C-level sharp knife, then there are a lot of options -- and a lot of reasons to choose them over C or C++.

Personally, I value that C++ is multi-paradigm (I can use classes when they are a good fit, generics when they are a good fit, asm when it's a good fit, etc.), also: trivial interop with C and assembler, no GC, runs directly on the metal. These are reasons to use C++, but not necessarily reasons to "continue to bow into it."

Did I mention generics and metaprogramming? Even if you don't use these directly, their availability makes for flexible and powerful libraries. The STL container library is the obvious example, but modern C++ metaprogramming goes way beyond that.[0]

[0] For example, see Hana: CppCon 2015: https://www.youtube.com/watch?v=cg1wOINjV9U https://github.com/boostorg/hana


The really short summary is that C++ provides abstractions, which allow to write more complex code simpler while many of those can be eliminated by the compiler leaving no runtime overhead.

Sure there are things (i.e. virtual classes) which cause an runtime overhead, but even when doing the same thing in C you'd need similar logic to what the C++ compiler produces for you.



If you're interested in modern C++...

https://www.youtube.com/watch?v=zBkNBP00wJE


So I dropped out of college long ago and it is actually so ironic and yet also refreshing to me to hear this viewpoint from a college professor. What a beautifully explained rationale for a subject that I find so hard to articulate to my colleages in our echo chamber. I'll definitely be pointing to this in the future.


Quick question: would the c loving crowd like an AWS lambda type offering supporting coding in C? You know, to quickly supplement your embedded projects with some intelligence and data processing on the server side, without having to learn a new language or fussing with server infrastructure and scaling


Getting serverless PAYG patterns working in any language is going to be useful.

Thing is, this exists. It's AWS Lambda. It allows running of arbitrary executables in the sandbox: https://aws.amazon.com/blogs/compute/running-executables-in-...


What you mention has more overhead that being able to directly run in the main process. Though, I don't know if it's noticeable for most use cases, especially given how fast processors are today


> It is still one of the most commonly used languages outside of the Bay Area web/mobile startup echo chamber

Is C all that commonly used these days? I rather suspect that it's an unforced error to start a new project in C, and that many wise organisations are replacing buggy C software with slightly-less-buggy software in safer languages.

> C’s influence can be seen in many modern languages

True, but … is this actually that important in a learning situation? Knowing Java will help learning Go (or vice-versa) about as much as learning C would, I think.

> C helps you think like a computer

Like a computer, sure, just not like the computer actually running on one's desk or in a data centre. Modern computers really aren't anything like the C model.

> Most tools for writing software are written in C (or C++)

I suppose that's true, because lots of people have used C, and that really is a good reason to know enough to get around in it, but … maybe one should teach students to use better tools in better languages (Lisp & SmallTalk come to mind).

Seriously, in 2016 it's obvious that C is just wrong.


> Is C all that commonly used these days? I rather suspect that it's an unforced error to start a new project in C, and that many wise organisations are replacing buggy C software with slightly-less-buggy software in safer languages.

It most definitely is. Outside of the fact that existing huge projects are written in C (Linux, many compilers/toolchains, databases, etc), it remains the default language for embedded systems. Most smaller embedded device (think Cortex-M class) is running code written in C. This represents an enormous amount of projects spread over dozens of industries and use cases. Someday that may change but right now even C++ has failed to gain traction in these environments.

> Seriously, in 2016 it's obvious that C is just wrong.

This is not true. It all depends on your use case. C is the most well-understood language on the planet - a sort of defacto default. Practices, processes, and tools have been developed over decades to help mitigate problems, often in systems that run for years without SW updates. This can't be discounted.


Hence why the CVE database gets updated daily with memory corruption exploits.


You can just say something is wrong because its obvious in 2016. Please say why C is wrong.

Is it because it doesn't fit your problem domain? That doesn't mean it's wrong for everything.

Every language has a strong and weak point. Knowing how to use their strong and weak points to elegantly solve any problem is important.

No language is "wrong" or "right"


> Please say why C is wrong.

I think it's obvious, or should be: manual memory management and poor types.

There's no problem domain for which I'd ever want to write C again: not for OSes, not for word processors, not for shell utilities, not for mathematics, not for graphics.

C's strong point is that Unix assumes it.

> No language is "wrong" or "right"

Counterpoints: JavaScript; PHP. Quod erat demonstrandum.


Have you ever written a program for a 50 MHz microcontroller with 128 KB of RAM? This is a problem domain in which C is well-suited, where manual memory management is quite important. In fact, there are not many alternatives. It turns out there are a lot of these out there in the world.

The world of software development is a lot bigger than PCs running high-level operating systems.


Yes, using Pascal dialects.


> Have you ever written a program for a 50 MHz microcontroller with 128 KB of RAM? This is a problem domain in which C is well-suited, where manual memory management is quite important.

I think that Forth would be a far better solution in that situation. C would be a mistake IMHO.


>> Please say why C is wrong > manual memory management

This is arguable.

There are cases where automatic memory management is not a viable option, and other cases where it is a poor choice. Just one example: consider all the trouble people go to with off-heap allocation in Java in order to get reliable low-latency operation.



Is C really this disparaged in Silly Valley?


Yes. Keep in mind the vast majority of people on HN only work on end-user software (native and web apps). Yes, there are much better choices for the web when you need security, etc.

In certain enterprise applications C are still the best choice. In finance, where I work, C and C++ still reign supreme. Why? There's already a huge multitude of great libraries developed over decades that are mature and battle-tested. Sure, you can write bindings for Rust/Go/D/whatever. But that's a ton of work for minimal benefit.

Fact is, when you get on a plane the avionics software was likely written in C. NASA extensively uses C. The automotive industry extensively uses C. Oh, and a lot of this C is embedded so forget easy software updates.

I think a lot of the hate for C comes from the fact that it lets you do bad things without alerting you. C is a language that mandates solid engineering practices. A good C codebase can be elegant, correct, and fully testable. The language doesn't force this, however. But a nice, well written C library will stand the test of time.


Pretty much what I thought. (I've been programming C since 1979, and C++ since 1987, but left Silly Valley 15 years ago).


[flagged]


Please don't comment like this here, we ban accounts that continue to do so. We've detached this comment from https://news.ycombinator.com/item?id=12644986.


You're right, I could have tried harder to express my disagreement without resorting to "newspeak" or "memespeak". Duly noted with apologies to the commenter and for lowering the discourse.


[flagged]


We've banned this account for repeatedly violating the HN commenting guidelines, and detached this comment from https://news.ycombinator.com/item?id=12643960.


"Early in the morning he [Jesus] came again to the temple. All the people came to him, and he sat down and taught them. The scribes and the Pharisees brought a woman who had been caught in adultery, and placing her in the midst they said to him, “Teacher, this woman has been caught in the act of adultery. Now in the Law, Moses commanded us to stone such women. So what do you say?” This they said to test him, that they might have some charge to bring against him. Jesus bent down and wrote with his finger on the ground. And as they continued to ask him, he stood up and said to them, “Let him who is without sin among you be the first to throw a stone at her.” And once more he bent down and wrote on the ground. But when they heard it, they went away one by one, beginning with the older ones, and Jesus was left alone with the woman standing before him. Jesus stood up and said to her, “Woman, where are they? Has no one condemned you?” She said, “No one, Lord.” And Jesus said, “Neither do I condemn you; go, and from now on sin no more.”

The Bible is useful certainly not just for historical value, but for making you wise for salvation. The Bible is God's master plan for forgiving you, and restoring you to friendship with himself, through Jesus' suffering, death and resurrection explicitly on your behalf. "Believing" in Jesus is not believing whether he exists, or whether God exists. "Believing" in Jesus means not claiming to be without sin, but rather acknowledging your sin, and trusting Jesus' work on the cross alone to save you from it.


We detached this subthread from https://news.ycombinator.com/item?id=12642207 and marked it off-topic.


If you believe it's god's master plan how do you choose weather to stone adulterers or not stone adulterers? It says you should do both. I'm hoping your answer will help me choose where to put my open braces.


Unfortunately, Islam has an answer to this sort of question - the doctrine of abrogation. Basically, all conflicts are to be resolved in favour of the latest version.

I say unfortunately because this gives precedence to the Medina verses, which are marked by cruelty and violence.


Upvoted because this is actually a polite, thoughtful response. Also wrong, but.


> The most recent edition of the canonical C text (the excitingly named The C Programming Language) was published in 1988; C is so unfashionable that the authors have neglected to update it in light of 30 years of progress in software engineering.

This is an odd argument. C has hardly changed or evolved since 1988, so there's not really a need for a new edition.


The essence of the language has not changed. But C99 came out with a few nice things. There is also C11 which brought in a few new things as well. It would have been nice to bring the book up to C99.

"Modern C" is a good book on some of the new features.

    http://icube-icps.unistra.fr/img_auth.php/d/db/ModernC.pdf


After reading up on C99 I take back what I said. I definitely thought some of those features were older than they are.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: