Let’s Stop Bashing C
h2co3.org
h2co3.org
Every C program I have ever worked on has been riddled with integer overflows, undefined behavior, buffer overflows and bugs. This was perfectly acceptable for a Vax or a Sun workstation in the 80s, and at forgivable in the mid 90s when plenty of developers still only had 8 MB of RAM and wasted half of that on Netscape Navigator.
But we live in an age of Pwn2Own and ClusterFuzz, where even the tiniest of bugs can be used as step 4 in a 6-step exploit, and where C compilers reserve the right to completely and silently break your program at the faintest whiff of undefined behavior.
I know my limitations: I'm not smart enough to write C code that deals with hostile data off the network. Neither are the authors of 99% of the C code that I've seen, with the possible exceptions of djb and a few OpenBSD contributors. It's long past time to invent and use better systems languages. Choose what you want, but choose something safer.
You'll even start writing code which relies on integer overflow (rollover from #$ff to #$00 again) because, depending on the situation, it will give you a performance boost, or make the algorithm implementation simpler, or both, and you'll know it.
It's long past time to invent and use better systems languages.
I can't comment on Go, but the textbook examples of Rust and even treatises on the subject from recognized experts in the computer science field (Adam Leventhal) show it to be an utterly overcomplicated language for which there should be a criminal trial and a prison sentence of several decades. Immutables, borrowing, no, I have to stop; my head hurts already, and I'm getting a migraine thinking about solving even the simplest of classic input-output problems with such an overly complicated language. (And my head wants to explode when I compare Rust with AWK for solving input/output problems, the biggest reason why computers were invented.)
So... C, C, and more C, because it's infinitely flexible and simple. I like simple, because simple is smart. Complicated and complex isn't.
Let's all please be humane to one another and stop inventing more languages. We need less languages, not a bazillion variations on the one central task: making the computer do something. And for that, we already have all the languages we could ever want or need, and for the theoretical case where we don't, there is always the almighty assembler, in which exist no limitations as to what one can program with it.
> no more overflows, no more pointer screwups, no more forgetting to free the memory, because you'll know exactly what will happen in the chip and the memory when that code compiles
To whoever wrote this: are you ready to share the code of a sufficiently complex program in C (or assembler, why not) you wrote and pay $100 for every "pointer screwup", buffer overflow, memory leak and generally every bug people find, which could have been prevented by a strong type system or borrow checking and other such complicated features?
Knowing how processors work doesn't prevent people from making mistakes.
I knew assembly long before I knew C, because I grew up on computers that were too small to run a C compiler. I've contributed to a half-dozen compilers. I've written disassemblers for weird embedded DSP chips. And by the standards of C programmers, I'm pretty paranoid.
None of this makes me superhuman. I make mistakes. I've written network-facing C code, and I've left people exposed to security holes. Want to see the carnage?
https://people.canonical.com/~ubuntu-security/cve/pkg/xmlrpc...
That's 6 CVEs against an obscure C networking library that I wrote. Technically, none of them were found in my code (not yet). All were in the third-party XML parser that I chose to use. But that doesn't absolve me of fault, and I bet there's a few more bugs lurking in my code that could be found with modern fuzzers.
I don't care whether people use Go or Swift or D or Rust or some brand new language. But wherever C goes, bugs and security holes almost inevitably follow. It's going to take decades to reduce the proportion of C in our systems, and we ought to start now.
While it is commendable that you take responsibility, you mis-diagnosed your error: it isn't the C programming language, but what you chose to do with it: send/receive extensible markup language for remote procedure calls. Seems like a great idea, until you actually have to deal with it. I just happen to be working on an application where I'm forced to generate XML-SOAP and send that over the network to a server, and then process the XML-SOAP response I get back with XPATH, using xsltproc. And all that because the designers of the server application use Java and didn't even know they were using XML-SOAP and XPATH, since by the time they work with it, Java libraries treat them as objects. (And neither did I, until I reverse-engineered the responses I'm getting.) I have caused the server to crash innumerable amount of times.
You're arguing for better systems languages, and I'm telling you that is a terrible idea because we have nobody so smart as to design something so complex to be simple, and the closest we have ever come to it is C. We already have way too many languages, that it's become a nauseating torture to work with computers.
... exactly.
- don't use XML for anything;
- use the correct tool for the job, but first define the problem better. What was XML-RPC attempting to solve?
He considers that a positive feature, not a bad thing. You're the inverse.
I can see both sides of that tbh. It's a justifiable - but myopically idealistic - point.
But man he's being a pretentious ass about it.
If C is that bad at managing complexity (and complexity doesn't only arise from bad design choices, some problems are inherently complex) then it should be used as rarely as possible, period.
It's not about idealism, it's about ill-placed, almost comical elitism ("C is a low-level-hacker language and only real programmers use it" while a 3rd grader could understand pointer arithmetic) to the point of rather spending 5 days writing bug-ridden code in C instead of 5 minutes in whatever else, just because it's the right way.
It's ridiculous.
This, in my opinion, is why even if C were somehow free of buffer/integer overflow, UAF, and other vulnerabilities, it would still be a poor choice for many real-world (read: complex) applications.
Bingo, give the man a prize! As we say on UNIX: C gives one enough rope to hang oneself. It is expected one knows what one is doing, but in return, one gets complete control and infinite flexibility. No programming language can compensate for bad architectural decisions, or an absence of such decisions, or absence of insight. Such a programming language does not yet exist, and it is questionable whether it ever will. So yeah, you got exactly what I was trying to say.
Now, back to C: nobody, including me, has all the solutions, but from experience, I have some tips when writing C code:
run the damn thing you're building through a debugger; feed it all kinds of garbage while doing so; try to break it in every way, shape, or form you can think of. As the author of the program, one should have unique and powerful insights into where the weak points are. And after all of that is done and fixed, have someone completely unrelated to it try to break it. This last piece of advice is good for any language.
I have a friend. I'd beat the daylights out of my programs, break them every which way, fix all the bugs I could find, then I'd let him have at it. He'd usually break my program in 15 - 25 seconds. Then the cycle would repeat, until he could no longer break anything. At that point, we had something remotely resembling stable software. And a lot of those programs I had him break weren't even written in C!
One of the best techniques I found while developing in both assembler and C: run the program you're working on through the debugger as you're writing it. As soon as I write a new function, I fire up a debugger (or shell tracing, or...) One wouldn't believe how many bugs I've found and fixed before the program saw the light of day. And it saves so much time, even though that's completely counter-intuitive.
That's an excellent question! To answer it, we need to rewind to the year 1998. Nobody had ever heard of JSON (2002) or REST (2000) or SOAP. If you wanted to do remote procedure calls, you needed to use Sun RPC or Corba or another binary RPC stack. Some of these required tens of thousands of lines of C code to implement, and I don't even want to think about the security issues.
Basically, XML-RPC was trying to be REST+JSON before that was a thing. It had already been implemented for most scripting languages, but one of my consulting clients needed a C implementation to interoperate, so I wrote xmlrpc-c in a couple of busy weeks around the end of 2000, I think.
Back in those days, XML had a very clear 30 page spec and XML-RPC had a 2 page spec. If you want to argue that I was wrong to use C to implement 32 pages of pretty good specs, or that I was wrong to use the best off-the-shelf XML parser of the day, well, I agree. But I think we need to demand more from our system languages.
Then you add the inefficiencies of XML encoding, and the utterly crappy nature of most XML translation layers . . . yeah, really easy to see the train wreck coming.
If C isn't up to implementing an only slightly complicated network protocol, it's not much of a systems programming language, is it?
It turns out, C is fine for writing such systems and getting them working fairly well, but very poor at making them really reliable and secure, and that's no longer a good tradeoff.
But people had heard of S-expressions, which are far more appropriate for data transfer than XML.
And the canonical S-expression spec, from 1997, is rather less than 30 pages: http://people.csail.mit.edu/rivest/Sexp.txt
This just made me laugh. I'm not making a value judgement about your statement, I neither agree nor disagree.
There were "too many languages" before C was even conceived of. If that were a reason to not use new languages, you'd never even have heard of C.
Any more than five becomes counterproductive and destructive, because it's physically impossible to keep up with all of the new languages coming out, and no language solves all the problems simply and efficiently. Or elegantly, for that matter.
In my experience, it takes about ten years of heavy use to master a programming language and use it not only to its fullest extent, but correctly and efficiently as well. I would be interested to meet a person who has that kind of time, biologically.
If that were a reason to not use new languages, you'd never even have heard of C.
Back in my day, the number of computer platforms was pragmatically limited: either you wrote software for the Commodore Amiga, or you wrote software for the Atari ST. If you were a software company, you wrote for both. If you were a programmer with lots of experience, you split the core from the hardware dependent subroutines, but I don't think anyone thought about this very UNIX-specific approach to programming back then (most of us had never heard of UNIX). We all wrote software in assembler, and were just lucky enough that both major platforms had the exact same processor family, the Motorola 68000. The C programming language was a solution to a problem nobody had or asked for. I remember making fun of my mathematics teacher and berating him for writing programs in C, because compared to assembler, it was lame: bloated and slow. It was impossible to code something of any significant speed and small enough size in C on either Atari ST or the Amiga. We viewed C as something lamers who couldn't figure out assembler dabbled with. One just couldn't compete in C with code written in assembler, which effectively meant that if you didn't code in assembler, and your code wasn't small or fast enough, or both, you weren't competitive, and the lamer label was soon to follow.
Times have changed, C is now the portable assembler, but they have changed only somewhat. When push comes to shove, understanding what happens at the hardware level is still crucial when performance problems strike... and they strike a lot. Most importantly, understanding how the hardware functions at the lowest level is most important when writing in really high level languages. The higher the language abstractions, more crucial understanding of how low level hardware functions becomes.
Nowadays, when I have multple incompatible processors to deal with, completely different hardware architectures, and plenty of raw processing power, C is a very viable, even desirable choice: if I write my code cleanly enough, it will just compile and run everywhere without modification, and I'm done, but back then...
...C was the answer to a question nobody asked or cared for in the personal / home computer space in those days. If one couldn't figure out assembler, one dabbled in GFA-BASIC or C, or Pascal. It was a very elitist, eat or be eaten environment. If one's code was too large or too slow, one became the public laughing stock of the scene. It was a very effective, and highly motivating selection measure. I miss those days.
.. for the specific chip you've targeted. And possibly even the specific stepping.
And that's assuming that you're diligent enough to manually review all that assembler. Very, very few people are. This is why typesafe code was developed. This is why all those checks exist in Rust. Limitations are good. Limitations are your safety equipment. Assembler is the guy who scrambles up on the roof without tether or hardhat to "get the job done".
> language for which there should be a criminal trial and a prison sentence of several decades
Commentators who overuse hyperbole should be executed.
You sure are angry with me for working on a programming language.
1. Creating something non-trivial in language X, 2. Identifying things that language X made difficult, 3. Brainstorming ways to implement solutions to those issues within a set of acceptable trade-offs, 4. Concluding that some of the brainstormed ideas might actually work, 5. Working really hard to implement and iterate on them.
I can't figure out which step the parent commenter scorns. I guess probably step 2, where they see no pain points to begin with. That's fine, but trying to convince people they haven't felt pain that they have felt is a silly battle. I guess they're actually implying that people who have felt that pain are just not smart enough.
And the worst part of it is, no such language covers all problems effectively; they all have flaws in one way or another... so the next guy comes along thinking he can do it better, and just repeats the mistake of his or her predecessor(s).
Instead of realizing that this would just exacerbate the problem, they go ahead and make it worse.
I guess they're actually implying that people who have felt that pain are just not smart enough.
It is extremely difficult to solve a complex problem in a simple manner. One literally has to be a genius with a lifetime of experience and insights from that experience in order to be able to pull that off, and even then, it doesn't always work and requires multiple iterations, and lots and lots of experimentation. Case in point: Go, still a work in progress by exactly such people. That should be instructive, but it seems that it isn't.
Which is exactly why you don't try to create a language that covers all problems effectively, you define the problems you'd like to speak to. If you have defined those problems in such a way that they are shared by many people, and if you solve those problems in a way that many people find effective, then you may have created a compelling solution. How does that exacerbate "the problem"?
Honestly, what even is "the problem"? Why does it bother you that there are new languages created? The "literal geniuses" from the Bell Labs era experimented a ton, and that was a great thing then, and remains a great thing now. It's always valuable to point people to prior art, but it makes no sense to bemoan research and experimentation.
The problem is that new languages have such fatal flaws, that they make the problems they are trying to solve even worse.
The second, and much more serious problem, is that it takes at least ten years to truly master a programming language. Humans only live about 70 years on the average, which means that it is impossible to keep up with every new language that comes out. I mean I live for this stuff, but I would literally have to be chained to the computer my entire life without time for anything else. That is nonsense.
Anybody who thinks they have mastered a computer language in less than ten years is either a genius, or unbelievably egotistical and arrogant; and knowing a whole bunch of computer languages in a perfunctory manner, enough to make one dangerous leads to a really shitty job of sorting out that mess afterwards... that's the problem. My problem, and likely other people's problem.
What I don't understand about your position is its absolutism. You keep talking about people being egotistical and arrogant, but it seems like you're the only one on this thread being those things, by claiming your subjective opinion as objective fact, and then making ridiculous hyperbolic statements about throwing people in jail based on it. It's not a contradiction for you to personally dislike a language's philosophy and for that language to have been a worthwhile creation and valuable to many people.
Your feelings about different languages are just, like, your opinion man.
Just wait until you invest ten years of your life to master a language, only for everyone else to move on to the next new trend, with you stuck holding the bag.
AngularJS, Rust, and node.js, is it? I can't wait to ask you how you're feeling about all that time you invested into all of those, when all the kids move on to something else and they're no longer popular, for no reason other than the newcomers' ignorance, or because something shinier came along, but didn't actually add any value, just re-implemented the old thing in a worse way.
We can then have a discussion about how you're feeling, having wasted a decade or more of your life away on what will then be perceived irrelevant. That's of course under the (optimistic) assumption that HN is still going to be popular in ten years...
The fathers of C created AWK as C on steriods, and even though they were a success in this endeavor, they were wise enough to create it as a domain specific, and not a general purpose language. If you study their works afterwards, up to Go, they always developed domain specific minilanguages, and always split the difficult problems into many small programs, hence Plan 9. This is very instructive in the context of their previous achievements. They even recommend to solve problems by creating a domain specific minilanguage, but only when this is appropriate to the problem at hand.
Now fast forward to Go. These guys really know what they're doing, and still Go is a research project, unlike Rust, they're not trying to actively sell it to the populace at large, because they know just how difficult it is to create a good systems programming language. So yeah, you bet your patootie I'm angry at anyone who thinks they can do it better. It's not even that they screw it up, that's part of learning and gaining insight, but that they actually actively attempt to sell it as a good solution. Meanwhile, it's even more complicated than the previous solution! And then let's suppose someone invests about a decade to master that new language, such as it is: by that time a pile of other people come along thinking that they know it better and invent their own languages; sometimes they invent a new language just because they think it should have a different syntax, as was the case with Ruby, in the author's own words! Unbelievably arrogant! And now that becomes the new hotness. Congratulations, you just wasted a decade of your life on something that's not in fashion any more. That would be no problem if we lived for 500 to a 1,000 years, or if we were immortal, but we only have this one life, and the length of our life is a blip in the universe. So it turned out to be a huge waste of time. That's the part that really angers me.
The level of contempt for other people and utter lack of perspective shown by this comment makes my head want to explode.
No you won't. Assembly is an abstraction now, at least on x86.
The chip does all sorts of things in ucode behind the scenes
I used to write a lot of C, and I no longer use it for anything serious either, because I'm too stupid and the stakes are higher. Maybe better tool support could change that, but I'd prefer a better language.
For an idea of what it would take to make C actually secure (and even then only in so much as you reasonably can) take a look at all the things Rust has had to do. Nearly everything that exists in Rust is there because they started with a C feature and asked themselves "what do we need to change to make this safe/secure?". Most other languages as a minimal first step towards improving security remove direct access to pointers, they're simply too powerful to do securely in the manner C does. Rust has (mostly) secure pointers, but only after putting an insane amount of work into bolting a massively complicated ownership and lifetime tracking system on top of it, and even then it's still not perfectly secure since they had to include unsafe blocks to provide a trap door for certain edge cases (also for C interop).
So yes, you could make a secure C compiler, it just wouldn't compile C code anymore, it would compile some "safe" subset of C which would probably end up looking like either Rust or C# (or maybe ObjectiveC).
Your compiler could track those ints used as pointers all the way throughout the whole program and prevent out of bound values either at compile time or at runtime for compatibility. There is nothing fundamental about C that makes it incompatible with memory safety.
The sad part is that they already existed since 1961 actually, long before C was born, but then some startups decided to base their workstation OSes on UNIX and got successful selling them.
https://www.youtube.com/watch?v=oKg1hTOQXoY
He mentions how Xerox PARC machines with tagged architectures from the 80s were only 1/50 the speed of the x86 systems in '97. This, in contrast to the 50000x increase in computing power over the same time frame.
https://www.smecc.org/The%20Architecture%20%20of%20the%20Bur...
http://homes.cs.washington.edu/~levy/capabook/
Bitsavers and Wikipedia are good resources. The summaries of most on Wikipedia are accurate enough with references to get more authorative information on. I found a lot of it that way.
<rant> Please, stop pushing this wrong rethoric forward! As soon as there is undefined behavior, it's a bug in the code. There is a problem, and the problem is on the developer's side. I appreciate the effort of compilers to help me get the most performant machine code I can, and I've had enough of people asking them to stop being smart in order to accomodate crappy code. </rant>
I think the emphasis on the original comment should be on the silent part. Nobody is arguing that these aren't bugs in the code; the problem is that you get no help from the compiler or runtime to find them and the damage is more severe than it would be if it simply crashed.
I think I see your point, but you are wrong when you write "simply". Crashing in case of UB is not simple! Actually, ensuring any type of behavior in case of UB is not simple! It requires huge lots of runtime checks, thus you obtain a ridden executable as output. If you want your program to be cuddled in a safe baby box (which is the right thing to want in several cases), then go for an interpreted language, but please let "gcc -O3 -march=..." be what it is: a masterwork coated with magic.
There are exceptions to every rule, of course, but we need to re-evaluate our priorities as times change.
In my experience, this is the right thing to do for most applications. Every time a program (ex: compiler) tries to guess what its user (ex: developer) meant, instead of just sticking to the input, there will be problems. In particular, you may help some users in some cases, but you'll prevent other users to get what they have the right to have.
> ... seems far less productive ...
And maybe it is, but it's not a compiler's business. Use automatic tools, write your own tools, decide coding convention, impose code reviews, but please let the compiler do what it is supposed to do and how it has learned to do in decades of evolution.
That's one approach to fixing errors, but there's another: tell the programmer that there's an error, and let them decide the best way to fix it. As you say, unsupervised "DWIM" is problematic, and so is a computer program that trusts its inputs absolutely.
> And maybe it is, but it's not a compiler's business.
What exactly isn't a compiler's business? Helping the programmer write a program that solves their problem? I would say that that is 100% of the compiler's job. It might be that part of the problem is "absolute performance", but that is almost always a secondary concern to correctness... computing the wrong answer (or pwning the user's data) is suboptimal, no matter how fast it happens. C defaults to exploiting everything for performance, rather than defaulting to correctness while also providing opt-in ("dangerous") tools for performance that one can use when necessary.
Yes, every time a C program relies on undefined behavior, it's a bug. And as djb observed, virtually all non-trivial C programs contain these bugs. This is the core of my argument.
Yes, we could rely on C programmers never making mistakes. But it would be better to design languages with no undefined behavior.
I do get a facial tick when people say "all significant C programs use (or contain) UB." It's never been necessary and it's always been shameful.
I just don't believe you for one minute. It's quite easy to guard against unwanted memory access over serial ports and sockets. You have to know the size of the buffer, but that's just not hard.
If you go throwing pointers-as-if-they-were-tokens/handles around and blindly following them, you'll get in trouble.
So don't do that.
If you constrain operation only to valid data ( which isn't all that difficult ) then ( and I ask out of ignorance ), how can they hurt you?
What do I mean by "constrain operation..." yadda yadda? I mean if somebody "misspells" something, you dump the input. Your recognizer accepts only valid tokens. Usually, the tokens are in a table, with an FSM knitting them together.
So the table can be wrong, or the FSM can be wrong. That's it. Recently, all commands were in a simple subset of XML ( by the customer's choice ). That was particularly easy.
I grew up in environmentally hostile environments where you'd lose things like PHY chips and serial port drivers, so this was how it was done.
If you mean exploits in the O/S itself, then yeah - I agree. But then my choice of language is irrelevant.
I won't disagree about the C compiler thing, but that shouldn't be at issue here.
The author demonstrated this by pointing to a list of CVEs caused by their code, so.
You do have choices to make in terms of techniques and tools you can apply (assuming you're aware of them) to modify the statistics, but you will not be perfect in your application of them, and the tools were not written by perfect people either.
There's another choice here - how high you set the security bar for "deals with hostile data off the network". Ekidd chose to set this bar high enough to rule out C. How high do you set it?
But as hard as I tried, I still failed, because I relied on 3rd-party code (by an extremely talented programmer), and he made mistakes. Perfection is not a scalable strategy.
[1] https://www.quora.com/What-reasons-are-there-to-not-use-Go-p...
We've had plenty of replacements waiting. C developers are just hyper committed to that language. Even though its inventors moved onto a Wirth-like language.
C is obviously very powerful. There's no denying that. But it does approximately zero of the things to help programmers that a modern language does. It's not even strongly typed in the usual sense (is there the equivalent of casting void pointers anywhere else?).
Finally, C is slow. Yeah, you read that right. Its main problem is that it has no non-wizardry high level semantics for parallelism. For example, modern languages have map() to say "do this operation on all of these items". In C, you have to write that as "for each item in this collection, do this thing, then the next, then the next, ...". If the compiler is exceptionally smart, it might infer what's going on and help out. But with map(), you're explicitly telling the language "I want all of these things to be done" and it doesn't have to infer anything.
I respect C. It's given us a lot of great stuff. But there's no way I'd willingly start a new project in C today given the alternatives. I am dumb, and given the choice between languages which help me and languages which are basically a small step up from assembler, I'm going with one that gives me a reasonable shot at writing safe, fast, and understandable code.
This was a simple fib(), not a map(), but I think the point still stands pretty well.
Map is just one example of a high-level construct that permits faster code, though. A lot of us here have done async IO in C because that was the option we had available to us for doing it, but I'd vastly rather do such stuff in Python (3), Go, or Node where the language itself gives you constructs for describing what you're trying to accomplish.
My impression is, no, outside of something like hadoop.
That's a pretty big exclusion, though! We use Hadoop-like stuff on, ahem, sizable quantities of data. That's not exactly an edge case.
Auto-vectorisation and parallelisation in Winter: http://www.forwardscattering.org/post/22
Could they? Maybe. But there is no solid evidence that they could.
Is the argument appealing? Yes. But it is lacking the evidence of these systems that could be better in other languages.
My 2 cents for why, is that c is basically the only game in town that can easily use new and special assembly. Of which there is a lot. Consider, what is the fastest way to calculate a square root? And how old is that instruction? (I'm assuming it is actually used by higher level languages by now.)
Also, almost every language has a way to use C code directly (For example, see Go: https://golang.org/cmd/cgo/). C may be the best way to write low-level driver code. If it is, use it for that! I'm not convinced that there's a single best language for Ethernet card drivers and HTTP stacks, though.
And, agreed, on truly expert systems, things do go polyglot. This has been true since pretty much the beginning of computers. I'm not sure what sort of evidence this provides, though.
So, to bring it directly to point. Where is the evidence that the systems people typically write in C could be written well in another language? About the best I have seen is some system utilities written in rust. Which is awesome, no doubt. And the beginnings of the evidence I'm asking for.
(To be fair, we often speak of mysterious wizards that wrote the symbolic lisp systems as though they succeeded at this. I want that to count as evidence, but it feels more like hearsay.)
What I don't fully understand is critiques of C that lump safety issues in with use of braces or semi-colons.
Personally I rarely use C because I currently write network services with REST APIs. C is close to worthless for that purpose for many reasons, not just errors. It's analogous to writing an accounting app in assembler. Most people stopped doing that in the 1960s.
Extended Algol, created in 1961. Almost 10 years before C.
There are tons of other examples.
C got widespread thanks to UNIX, just like a few decades later, JavaScript got widespread thanks to Web 2.0 and the browser.
> It's analogous to writing an accounting app in assembler. Most people stopped doing that in the 1960s.
Lotus 1-2-3 was initially written in Assembler, Ι will let you track down when it was created.
My last 100% Assembly program was for MS-DOS in 1996.
Interesting! I did not know that. I would still stand by the point that application programming by the end of the 60s was leaving the Assembler era and moving on to languages like COBOL and FORTRAN. (And weird cul-de-sacs like RPG which was my second programming language after BASIC.)
At 8 bit and 16 bit computers target for consumers it was a different story.
Forth, Basic, Pascal, C, Amos, Modula-2, Clipper were the "managed languages".
Anything that required performance was written in Assembly, or high level in such "managed language" (Turbo Pascal on my case) with lots of inline Assembly.
A modern program talking to the web will have encoding vulnerabilities and business logic vulnerabilities.
It's still progress.
There are cutting edge stacks which will effectively eliminate the encoding vulnerabilities, too, leaving more-or-less just the bugs you put in.
(Obviously I'm simplifying the types of vulnerabilities that exist to make a point.)
A high level language OTOH allows you to be more productive in terms of the business logic stuff, and therefore, I'm using a rough metric here, more lines of code, which also means more bugs in the business logic.
Like you said, these are all simplifications, but I think the primary problem is that people undertake projects that are way above their competency level.
Edit: If we could get some multiplayer-game like matching system where you are matched with the project depending on your skill level that would be awesome.
/s
Far as eliminaging logic errors, you can make a lot of progress on that using formal specifications combined with modular code that matches them well. That's how med-high assurance was done. Some companies also specify domain rules in formal logic then implement them in Prolog or Mercury. You will have a hard time straying from intended logic when executing the logic itself. ;) Toolkits like Allegro CL and sklogic's DSL scheme let you mix and match things like LISP for power, ML for safety, and Prolog for logic execution.
Really niche stuff in industry, though.
More conventionally, even something like Go's html/template [1], though I believe it uses a technique that can ultimately be fooled, is much harder to write accidental encoding vulnerabilities into. If you do the natural thing in the templates, the templates do the right thing.
Business logic errors I don't expect to be prevented by, well, anything. Oh, you can do some work to prevent certain sorts of errors with type systems, but a lot of business logic errors often start in the requirements, or the translation of the requirements into the code. Nothing will ever stop us from taking fuzzy requirements and translating them into wrong code, even if we can at least ensure the wrong code doesn't crash, corrupt memory, expose encoding errors, or violate simple invariants.
A HLL does indeed allow you to compose solutions to problems in terms of higher level abstractions, which I gather is your point.
Because nobody uses node.js in serious infrastructure. Show me a serious ISP whose DHCP server is written in node. Know any whose DNS server is written in node?
Your statement is equivalent to: we should build bridges out of legos and not out of concrete because fewer people have been killed by collapsing lego bridges
I was frankly a little disappointed in both the referenced article and the one it references. C basically is assembler. If you have a problem that needs hardware-level access you'll need things like pointers. The really hard parts of C (like handling null pointers or lack of concurrency primitives) seem to be the flip side of this access. Whether you use things like braces just seems to be a matter of taste.
Sure, with Cython or a Python C extension. :)
I mean, you could, there's no real technical blocker, but it'd be horribly slow. Plus many failure modes of touching hardware are very likely to break the VM process and make debugging very challenging
The problems include:
- performance: implementations being interpreted, and relying on dynamic typing mean that Python is almost always slower than compiled languages.
- the run-time: Python assumes that things like allocations are always OK, and generally requires more operating system support than is available in low level environments.
By comparison, C's runtime is very simple and assumes very little, so it's good for writing code in a kernel or integrated system where many OS services won't be available to you. It's also much faster, of course, and it gives more direct access to memory.
You could write one in Lisp, which is in many ways a language like Python, but which does support bit twiddling & pointers if desired.
Other examples include SQL, Javascript (though WebAssembly may free us from that before we're stuck with it in 2040), and Unix.
When I say this sort of thing, people tend to flip out and start defending those techs against the claim that I don't think they're good at all. That's not the problem. They are good. The problem is that they're not good enough to build on for the future, but they are good enough to entrench themselves.
The consequence is that it takes a lot of riling up the community with knowledge of the shortcomings to fertilize the ground for alternatives to arise and be supported enough to make progress.
So, please stop defending C. It is what it is. It had a good run. Nothing will ever make it stop being the most important language of the last four decades. But if you don't want it to be the most important language of the next four decades... and you really shouldn't... please stop defending it. Give the alternatives space to breathe.
$OBLIGATORY_RUST_CALLOUT.
I do this because if there is anything that calls into question the "science" part of computer science, it is the dogma of folks that refuse the empirical side of the field. It isn't like folks haven't tried to make better languages and toolchains throughout the years. You even acknowledge this. They just haven't delivered on their promises.
And, some of that is because you are ignoring all of the advances that have happened in C. If you are not using modern tools like valgrind/coverity/whatever, you are not comparing C fairly to contemporary languages.
I personally consider C + strong static analysis tools to be an entirely different language. I fully acknowledge this is my personal opinion.
Consequently, I consider it equivocation in this sort of context to be talking about C when convenient, then talk about C + strong static analysis tools when convenient. You're only programming in one or the other. My utter contempt is for C the language by itself. This is relevant because as near as I can tell, C has at least a 10:1 advantage in the field over C + strong analysis tools, and I'm probably off by at least a factor of magnitude. It is not realistic to pretend that C programmers are using these tools at scale. (Now, you can point at a ton of high profile C programs that do use these tools, because high profile C programs are exactly where they get used. But as near as I can tell they are firmly the exceptions, not the rule. And you can point at a ton of high-profile projects that don't use them, too.)
If everybody started using all these tools, including the ones that you actually have to buy because there's no open source equivalent of, it is true that most of my issues with C would be resolved. It would also be true that programmers would be a lot less gung-ho about C, though, because as anyone who has deeply integrated these strong tools into their workflow can attest, C + strong analysis tools is a much more complicated language. Now, in my opinion, that complication is being revealed by the analysis tools, not created, and all C code is actually that complicated and the programmers are merely kept in the dark by C's extensive structural deficiencies that make it so it's only safe to write in with these rather sophisticated tools, but the end result is still you're working in a much more complicated, demanding language with a much more baroque compilation process. Claims about C's productivity, compiler speed, probably execution speed, ease of programming, probably quite a few other things, all go flying out the window if this is the solution, and C looks eminently more replaceable.
Have I grown weary of the attacks on C? Also yes.
Are not enough people using advanced toolchains? Yeah. I agree with that. But, this is effectively the same complaint that not enough people are using higher level languages. With the exception that fitting some of the extra tools on there is easier than switching languages.
In Dennis own words:
"Although the first edition of K&R described most of the rules that brought C's type structure to its present form, many programs written in the older, more relaxed style persisted, and so did compilers that tolerated it. To encourage people to pay more attention to the official language rules, to detect legal but suspicious constructions, and to help find interface mismatches undetectable with simple mechanisms for separate compilation, Steve Johnson adapted his pcc compiler to produce lint [Johnson 79b], which scanned a set of files and remarked on dubious constructions."
Taken from https://www.bell-labs.com/usr/dmr/www/chist.html
We are in 2016, and static analysis is still ignored by the majority of C programmers that "just get work done".
I think it is still in the wishful thinking world to think "better tools" should have won by now.
Even the language authors were honest and:
1 - created a tool in 1979 that most C developers ignore even in 2016, 37 years later;
2 - realized that C was due its date and took part in Limbo and Go design
Yet lots of C coders continue to improve this NIST list, every single day:
https://nvd.nist.gov/visualizations/cwe-over-time
For me wishful thinking, is the mentality that there are C developers out there that manage to write memory corruption free without using such tools.
Never met any since my first contact with C in 1992.
I've used Lint. Early on. It was great training. After a while, you don't need it so much.
I think you are Vastly overestimating the skill level needed to write normatively-correct C code by quite a margin.
How many people did you had on those teams and how was the development process?
Me too I have written such software, when working in teams of no more than two people where we taken a Pascal like approach to programming in C.
Basically:
- Use translation units as modules
- structs are always exposed as Abstract Data Types, with accessors either via macros (when speed matters) or functions
- assert everywhere any assumption that can lead to memory corruption
- on debug code, make use of assert with pointer validation library from debugger, e.g. IsBadReadPtr()
- arrays are never accessed with pointer syntax
- use of compiler extensions for bound checking during debug builds
- all warnings enabled and considered as errors
And quite a few others.
I can count up to 5, the amount of friends and co-worker that managed to write good quality C code, since I learned C in 1992.
C++ unfortunately also enjoys the flaws of C due to its C copy-paste capability, but at least I can make use of the type system via structs, classes and operator overloading to enforce code that in C cannot be more than plain patterns.
> I think you are Vastly overestimating the skill level needed to write normatively-correct C code by quite a margin.
Anyway I don't need to proof anything when we have so nice CVE databases full of examples.
Or companies like Apple, which are supposed to have such ideal C programmers, do an OS release with 36 bugs, 31 of these (86%) related to C memory corruption bugs.
As I recall, you had six(?) defects identified on a rather significant subsystem. While that's hardly ideal, it doesn't sound like those six defects should cost that much to fix.
And I'm not trying to say I can achieve some abstract perfection - I will have the odd memory overwrite early in unit test. But SFAIK, I've caught the overwhelming majority of them. But I will also be able to spend several hours building scaffolding to flush 'em out... this is especially true of networked code. I tend to use Tcl to torture them.
I will say - all the old guys I knew who were good C coders just got out of coding pretty much altogether. When I see discussions from younger coders, based on the problems they have, I feel that there's been some loss of information.
Also - for a major release of something as big as El Capitan, 36 seems a not-unreasonable number of severe defects. Obviously, we'd prefer it was a smaller number, and we don't know how many will be found eventually.
>> 36 seems a not-unreasonable number of severe defects.
The point is that it should have been 5 defects (36 total - 31 memory defects).There you go. That's how I have done it as well. The degree of "Pascal-like"-ness is always .. something to be humble about. I really, really wish it had been Ada all along but that seems not to be likely.
The Free Unixes of the true UNIX DNA lineage have a lint that is a rewrite by Jochen Pohl (done for NetBSD sometime in the early 90's?) Somehow BSD didn't inherit lint. If you use lint on BSD, you're not literally using that program that dates back to 1979.
Other Lints like FlexeLint are proprietary.
C spread like wildfire in the 80's, but an implementation of lint did not follow. Most non-Unix systems that could be programmed in C did not have a lint accompanying that C compiler.
Definitely, it seems there was a lack of promotion of lint from the birthplace of C.
If I were to give a definition of lint, it would be: "that mythical C checking program everyone knows but hasn't used".
It must be suffering from a curse which affects all technology named using a four-letter word starting with L. (Or at least LI.)
Maybe on US, I only got my first contact with C in 1992, via Turbo C 2.0.
Before that already had used multiple languages and was using Turbo Pascal 6.0 by the time I learned C.
Then again, I hardly knew anyone with access to UNIX, my first contact being Xenix in 1994.
This type of experience was quite common in Portugal, who had money to buy expensive UNIX workstations....
Also the fact of being proprietary, well I usually paid for my tools, before FOSS started to be a thing.
And on the same place where they had Turbo C 2.0, they also just got Turbo C++ 1.0.
So there was a tool that had the type system that allowed me to bend C to be more like Turbo Pascal, provided I cared to use the said type system improvements.
Which lead to me never using C++ as an improved C compiler, rather taking advantage of C++ type system to ensure my arrays and strings were properly bound checked, and IO was done safely.
Oh and taking part on C++'s side on the whole C vs C++ USENET debates, since 1994. Or type safety in systems programming vs C.
So these HN and Reddit discussions regarding the C's unsuitability to write safe software, are hardly anything new to me.
And the justification is always the same, just as you said: "just get work done".
But.
Holy crap, the idea that you have to use something like Coverity just to make C code not suck is a bitter pill. I mean, I'm glad it exists but it's terrifying that it has to and that it's still being developed. After decades of development, the Coverity team is still finding enough examples of new ways to write bad code that they can still improve it.
I don't write C because I'm on a first-name basis with the only people on the planet I trust to get it right, and I am not among them.
The question also has to consider, why did they fail? And what is the criteria for failure? That may be entirely the point: the languages may be better by some metrics, worse in others, but they cannot easily beat the inertia factor, as a result of being so entrenched for 4+ decades. Everyone is very aware of the inertia factor; it seems to permeate all levels of technical decisions in all kinds of fields, for good and bad reasons. If the failure criterion is "not used as widely as C", then, like, everything has failed, so it's a pretty bad criterion, I think.
Ada has been around for a while now, and has seen a fair amount of high-assurance industrial use. It's really unclear if you can actually characterize Ada as a total failure for example, it lives on, although it's certainly less popular. But it was designed with the field in mind from day 1. I think a lot of this has to do with things like institutional knowledge and inertia, and vast amounts of time and money sunk into tooling for systems like C.
That's not bad, the money and stuff obviously helped; but it makes the narrative that all these other things are strictly worse/never attempted a little bit muddier. Does the "empirical" lack of better replacements imply that better replacements cannot exist, or never existed? I think Ada alone specifically negates the core of that argument, they've certainly been tried and seen some level of success. Or perhaps we didn't actually try enough due to resources, maybe? Or maybe it suggests that other contributing factors, externalities, gave rise to such a "monopoly" of computing intellect?
Also, it's easy to forget, but today, it seems general programming languages have enough demanding requirements in general (from things like expectations of package managers, to libraries, to editors), that actually supporting and promoting a general purpose language, to the point of wide usage, is ridiculously time and money consuming. Programming languages are a commodity, for the most part. Existing languages almost all either struggle to exist, have their primary developers financed by some corporations, or have existed for long enough to not simply drift into the void and become effectively immortal.
Yet, there's almost no money in it as a field. It's insanely hard to make a business out of things like compiler tech or programming languages these days, outside of specialized fields and clients. Unless you have deep pockets and a willingness to throw money into the void for a while, bootstrapping a "competetive" programming language is going to be insanely hard, on any kind of reasonable timeframe. Rust is a good example: Mozilla financed a vast amount of the development for a long time, and sustained it even in the beginning stages when it was much riskier as an investment. That's likely one of the major reasons it's even remotely as popular as it is today, because they ate the cost, to catch up with modern expectations.
All of these compounding factors are not going away. I agree there are few viable alternatives; but I don't agree it's so easy to explain it all away "empirically" with such a simple analysis as yours, that it was all either never tried or just "failed" (without defining what that even means).
> And, some of that is because you are ignoring all of the advances that have happened in C. If you are not using modern tools like valgrind/coverity/whatever, you are not comparing C fairly to contemporary languages.
The fact you suggest a 15+ year old, insanely complicated static analysis tool that's proprietary to ensure your code should basically be allowed to exist on the modern internet (because otherwise it will just be a nightmare hellhole security problem for someone down the road), yet continues to fail to find true high profile vulnerabilities that keep occurring, and still requires constant evolution -- all so that C can be "fairly" judged with modern languages... it's a bit alarming when you think about it.
Well, I'm being slightly tongue-in-cheek. I mean, every time I write C, I heavily indulge in these tools as my insurance to ensure I'm doing things correctly. I even tend to use CompCert to find undefined behavior! Coverity is really good, no doubt.
But when I do this, it definitely gives me pause at the investment I have to make. I don't sit around thinking "Yes, this is basically a great solution to the problem". It would almost be, like, I dunno... calling the story of the Titanic a success? Lifeboats hardly seem like the ultimate risk insurance when you're the one crashing into ice, especially when it seems we never have enough of them.
For the criticism of age of static analysis tool. This criticism doesn't make sense. I could make the same criticism for a 26ish+ year old and insanely complicated language toolchain (Haskel). And that is if I just pick the popular one. I could go with your example, Ada, for a 36+ year old option.
If the criticism is simply due to the proprietary nature. That cuts against much of your upper argument. The lack of money in the field is a large part of the problem.
I actually think SQL is a fairly good language for what it does and the alternatives have been fairly crappy (at the minimum it should not be lumped in with Javascript). If there is a Rust of SQL let me know.
In particular, relational database theory does not require tables to have homogeneous table rows that consist entirely of scalar values. That was an efficiency optimization introduced back in the 70s, for perfectly sensible reasons at the time. But writing that into the language at such a fundamental level bends the whole language around it in a really unpleasant way compared to what it could be. For a similar example, consider the difference between a language that has no first-class function references, vs. one that has them added in. The first language may do everything you need, but you'll be bending a lot of code around that problem.
Of course, a variety of extensions have been bodged on to the sides of SQL over the years to deal with the resulting problems, and generally in a good engine like Postgres or something you can do almost anything you want. I'd also say that if you can understand what I mean when I say "Postgres is actually a much better database than understanding SQL would lead you to believe", you're on the track of what I mean.
Again, I'm not saying SQL is bad, I'm saying its success is holding back the things that could be better.
It honestly would be helpful to see some alternatives that you think are actually good (as the ones I have seen in the past are fairly awful). The homogeneous rows point is valid but I'm not entirely sure even SQL requires that (as you can have non scalar data types as columns in many databases... although I suppose that is to your point about postgres).
And yeah I mainly only use Postgres so I'm biased :)
That's the point.
Are you sure about that? https://en.wikipedia.org/wiki/First_normal_form
Homogenous values are not required, but having atomic/scalars as a fundamental unit of a relation is a pretty core concept in database normalization theory.
Or are you saying database normalization is a performance optimization and we should judge RDBMS' by their support for relational algebra/calculus and not normalization theory?
Richard Gabriel made the same observation in his 1989 "The Rise of Worse is Better": https://www.dreamsongs.com/RiseOfWorseIsBetter.html
The worse-is-better philosophy means that implementation simplicity has highest priority, which means Unix and C are easy to port on such machines. Therefore, one expects that if the 50% functionality Unix and C support is satisfactory, they will start to appear everywhere. And they have, haven’t they?
Unix and C are the ultimate computer viruses.
[...]
It is important to remember that the initial virus has to be basically good. If so, the viral spread is assured as long as it is portable. Once the virus has spread, there will be pressure to improve it, possibly by increasing its functionality closer to 90%, but users have already been conditioned to accept worse than the right thing. [...]
The good news is that in 1995 we will have a good operating system and programming language; the bad news is that they will be Unix and C++.
In Steel Bank Common Lisp, one can just write a new VOP to emit a new instruction.
Lisp seems to have been damaged by the functional programming dogma more than anything else, the more I learn about it. Which is a shame, because the functional parts are good. But so were all of the toolchains around interop with other systems.
Isn't the right way to handle this for your code generator, e.g., LLVM, to know about this instruction, and your language implementation, e.g., clang or rustc, to just say "hey, give me a square root"?
I do buy your argument for very processor-specific things like writing a bootloader or a hypervisor.
C, however, gives you hold onto the generator to say "use this assembly". So, for new or one off instructions, it is a quick way to get an edge.
It is not exclusive to C compilers.
When you use inline assembly, the compiler (more or less) has to execute that inline assembly, right where you say it is. It can't speculatively execute it, it can't refactor the call into a different function if you're calling it twice, it can't optimize it out in certain circumstances, etc. It can't move the instruction around or choose a different form depending on register pressure. If you specify the right flags about what registers it clobbers, what memory barriers it requires, etc., the compiler can do some of it, but it's not going to be nearly as good as the compiler actually understanding what's happening, I think.
Telling the code generator (and C is a high-level language in this respect, if you want any performance out of C) what you're doing is likely to get you more of an edge, no?
I can certainly imagine cases where letting the compiler reorder a slow square root around a memory access will be faster in practice than a fast square root that can't be reordered.
And as for operating systems being written in C and failing, writing a general purpose, hardware agnostic operating system isn't exactly easy. This is not to say that C should be the systems language of choice, or even that C is easier or more stable to write operating systems; just that stuff will be buggy regardless.
And I think your main complaint is stuff like OpenSSL and its vulnerabilities... I don't think I would blame the language there. I would blame age / inherited bugs / lack of testing (amazingly that's the case, but we don't live in a world that's too concerned with the common good).
I agree with you for many, many use cases for a programmer these days... but only if you just contextualize all these assertions.
Wirth and Jurg did it in a year or two on custom hardware using Modula-2. They and thdir students repeatedly did it with Oberon variants. They were all safer and more reliable than earliest UNIX distro. Partly due go strong typing, interface checks, and GC's in later versions.
Similarly OS's written with Modula-3 (Spin), Haskell (House), Java (JX), and Rust (Redox) moved fast since the languages reduced number of problems they had. Spin could even dynamically link M3 code into running kernel with type system preventing many crashes or vulnerabilities. Made for great acceleration of networking apps, etc.
...the point I'm making is absolutely not that building an OS is equally hard in all languages, and equally likely to be buggy. My point is that creating a general purpose OS that only gets its rigorous testing from getting put into production before the majority of its target hardware has even yet been developed... that's going to be buggy in all languages.
Designing and running an OS on a known system with a few applications in mind can't really refute what I'm saying. I mean, I could of course be wrong. But I can't imagine a convincing counter-example that doesn't have for its evidence "largescale adoption on disparate systems."
(there could be an in-principle reason that I'm wrong that I just don't understand, but this is the risk we take in having opinions)
You can set the pointer to NULL, then freeing again won't hurt
>>not to use after free()
Set the pointer to NULL so it will crash.
>> Its main problem is that it has no non-wizardry high level semantics for parallelism.
Fair, the language doesn't have it. For everyday programming though you can use something like OpenMP where making things execute in parallel often requires adding one line above your for loop.
>>But there's no way I'd willingly start a new project in C today given the alternatives
There is no alternative for performance critical code (well, at least among popular languages of today). For other things - sure.
>>I'm going with one that gives me a reasonable shot at writing safe, fast, and understandable code.
If safety is your priority then likely C is not for you. As to performance it depends what you consider fast. If making stuff 50% faster no matter how fast it already is saves a lot of money you have no choice but C/C++/Fortran or just hand code in assembly.
>>> remembering to free(); not to free() twice;
> You can set the pointer to NULL, then freeing again won't hurt
>>> not to use after free()
> Set the pointer to NULL so it will crash.
I like C and think it is bashed too much, but double frees and invalid pointers are most often the result of multiple copies of the pointer... some other structure hanging onto the original reference.Let's unleash the superpower of C. You should've been using pointers to a pointer so that it is always a single reference to a memory block.
You can't really be programming in C if you're not thinking in pointers.
One more thing; The article should have mentioned "pointer increment" which has nothing to do with "+= 1".
What do you mean? ++ptr is perfectly equivalent to ptr += 1, for any pointer type.
int *a, **b, **c, **d;
d = c = b = &a;If you are writing code that is too complex to be linted, then you are also are at a level of expertise where those mistakes are absent or very uncommon.
This doesn't work on all architectures which is why its undefined behavior.
Dereferencing NULL is not guaranteed to crash. It's undefined behavior, generally. SO that can lead to tons of other problems....
> There is no alternative for performance critical code
On many platforms, Rust exists today. C is still the king of that, though, especially for obscure embedded stuff.
I agree it's not perfect solution, just something to help you catching errors in your logic (not checking for NULL when something could be NULL is a bug in program logic).
There is something missing from these discussions.
The reasons for the gap between the condition of most C code and what you describe. Plus how to close that gap with lower effort than just getting them to use Ada, Component Pascal, Go, Rust, etc.
I am keeping some notes on these discussions. Remembering you and TickleSteve in mind for the eventual roundtable discussion on Doing C Right With Ease (TM). pjmpl's list of safety tricks in the reply to you looks interesting, too. Once a basic strategy forms, then tooling can be designed to analyze conformance to it with helpful error messages and justifications. Built into IDE running in background as developer programs.
But frankly, the timbre of these discussions leads me to believe I'd simply be defending that for hours on end and I just don't wanna fool with it. The sheer rush to judgement is enough to make me think I'm sort of out of my depth in the ability to actually debate this properly. Part of my desire to say anything is trying to get the color and shape of that.
Plus, I'd rather not break anonymity, and I (amusingly) don't own any code of consequence that could demonstrate the principles - it's all owned by my employers.
Something like that - if I can't visualize the end-game, I probably have no business starting in it. Plus, I feel somewhat strongly that the languages designed to replace C should probably be given a chance. Throw in that the compiler people, in chasing absolutely performant executables, have left old code to rot... I'm also a bit feeling out of my depth.
I can more do it than talk about it.
Two methods I religiously use in my code: Set to NULL after freeing. free(NULL) is a no op, so freeing twice is never an issue anymore. And since you probably won't use the variable after that anyway, setting it to NULL eventually gets optimized out anyway, probably. And no if without brackets in my codebase.
Finally, C is slow. Yeah, you read that right. Its main problem is that it has no non-wizardry high level semantics for parallelism.
Do you know about openMP? Which works in most mainstream c compilers (clang, gcc, icc, msvs, etc). Ok, its not part of the standard, but IMHO thats probably a good thing. C doesn't dictate a paradigm which allows it to support a wide range of competing standards. Pick the one best for your project, or embed something like openCL or lua right into your C project. Of course the opposite can be said as well, write your code in python/whatever and then write a C plugin for the critical portions. Because, outside of fortran or C++ pretty much nothing comes close to the capability of C to solve a problem quickly.Which if no one noticed is even more important these days than it was two decades ago, when companies used crappy languages and shipped products that wasted 99% of the cycles of the users computer. Today doubling the performance of your product can literally 1/2 the computing costs at your friendly cloud provider/datacenter.
Not one thing said in that sentence fragment can be shown categorically to be true. Yo, software has defects. Some get past everybody. To wit:
1) It's 2016. There's a lot of old code out there. Old code tends to be C.
2) UB and implementation-defined behavior are just a fact in the language. This being said, they're not THAT hard to avoid.
I think the thing is whether or not people are habituated to thinking in constraints. People who aren't will be more dangerous with a C compiler than those who are.
FWIW, I've heard the phrase "knuckledragger C programmer" more than once. As a C programmer, it might even be apt. :)As in "monads? We don't need no steenking monads!" :) ( this being said, I use something akin to a monad pattern quite frequently and in C )
p == NULL
p
*p
is a monad. :)and for the editor auto indenter too.
I don't like doing the job of the compiler but this is an exception.
I already wasted more time in a few months of Python by hand aligning and debugging code after I moved it around than it saved time for me by not having to type } or end at the end of the block. Note that Python has a { in most cases: it's a mandatory :
And how about the time lost because of "IndentationError: unexpected indent" when copy-pasting code from the editor to the command line interpreter?
To the point of bashing C, the post is an answer to a very few points of https://eev.ee/blog/2016/12/01/lets-stop-copying-c/ It's mostly about syntax and it's an interesting read. I particularly like "No hyphens in identifiers"
Python has an "invisible close brace" character, the newline, which closes a varying number of braces. When you think of it like that, it makes much less sense.
The idea that the end of a block is invisible isn't really right, though. The newline doesn't end the block. The block ends at the next line containing non-whitespace text at a lower indentation level. In other words, it only ends when it becomes visible that it has ended.
In an Algol brace-blocked language (or Pascal, Verilog etc which use begin/end), you can just put stuff in the middle of a block, check that what you've just inserted is balanced, let the editor reformat it, and you're fine. Whereas if you've lost the indentation from
if a:
print b
a = a + 1
then you have to work out all over again whether the second line is part of the block or not.So maybe what C's missing is a standardized, popular, universal, automatic fmt-on-save. This eliminates its' issue problem with improperly indented code.
if(foo)
bar; baz;
Which, when you throw in a preprocessor has probably caused more bugs than anything other than out-of-bounds pointer errors.Of course the fact that everything, including a block, is a single statement is C is one of the things that gave me my first big "whoa" moment when learning to program, and makes me glad that C was the first "real" language I learned (after Basic). K&R C is far more elegant than people give it credit for, probably because modern, real-world C is full of all sorts of weird stuff (and often egregious preprocessor abuse - yes, I'm looking at you PHP), or people learn C++ first and assume it's all the same.
if (foo)
bar; baz;
Is the above code example, though legal, considered bad practice?Yes, misleading indentation like this is bad practice for anyone who believes that there is such a thing as "bad practice" exists. For clarity to those who do not know C, the problem is that "baz" is executed unconditionally, even though the formatting misleadingly implies that it depends on "foo".
Unfortunately (in my opinion) there is not consensus on whether the following is bad practice:
if (foo)
bar;
My belief is that this formatting should be avoided, to avoid the case where someone not familiar with the rules of C (or not thinking about them) edits it to add a manually indented "baz" on the next line: if (foo)
bar;
baz;
Personally, I'm fine with a single line without braces: if (foo) bar;
But I believe that as soon as the body is moved to another line, braces should be required: if (foo) {
bar;
}
This belief is common, but not universally shared. Some go farther and say that braces should always be required (there is a good argument for this). Others say that a two line if statement without braces is just fine (I think they are wrong).I don't think I ever had a bug due to wrong indentation. I admit that I seldom copy-and-paste code from web pages, but even then I don't remember ever having troubles with it. By no means do I intend to invalidate your experience, just saying that I/some/a lot of people have no problems with this. When discussing Python there are some issues that regularly come up (package boilerplate, complex data model, asyncio), but the indentation isn't mentioned in my experience. I'm doing Python for 4+ years and mostly used (and still do use) C and C++ before that, and some Basics and php before that.
What I feel is very important when editing source (not just Python, any source) is to have an editor that isn't entirely bad about handling indentation. Very simple editors just delete/replace a selection when hitting tab. Slightly smarter editors prepend a tab to each line, or indent one level. Emacs knows enough about indenting things that it cycles between possible indentation levels. An editor supporting shift-tab (reverse indenting) is rather helpful.
Personally I find it easier to restructure Python code than eg. C code, because I don't have to adjust braces manually. In Python I can just move a block around and, if necessary, move it horizontally to the correct level, whereas in bracelang I often have to adjust braces as well, which are outside the moved section and need extra cursor navigation.
Pycharm, by way of it's IntelliJ heritage is pretty good here; when moving code it automatically adjusts the indentation if it is distinct (eg. move a few statements from an `if` to a upper level and it adjusts the indentation automatically to match the surrounding block, since the statements can't possibly be indented due to the lack of a if/for/while/def). CLion can do similar things. The IntelliJ IDEs do these things for any supported language, sometimes across languages (paste some Java code into a Kotlin file - IntelliJ will automatically translate it into Kotlin, adjust the code style and sometimes even rename variables by type to match).
For what is worth, for C-like languages, I do not have to indent my code, my editor of choice does it for me according to brace placement.
Could you clarify; I think adding one/two more line to the block being moved makes this a similar operation. For that matter, the editor can unambiguously figure out the block and do auto-indentation when braces are present.
I'm seriously baffled by people complaining about indentation errors. The indentation is so tightly tied to the meaning of the code, isn't it almost always obvious?
Do you use an editor where you have to indent each line individually rather than moving all selected lines with tab/shift-tab?
I'll also google that %paste. Thanks.
Edit: I googled that and installed IPython 5.1.0 (sudo pip install ipython) I can paste lines at any indentation level now and they work, or type spaces at the beginning of the line. %paste is not required.
I would upvote you again if I could. Thanks again!
The fact that you need an editor that parses C and adds in characters for you should send up a red flag.
DRY. I shouldn be working with human-generated code, not redundant machine-generated code. The only program I want writing code for me is my compiler.
1. It makes the case that just because you don't like something, it doesn't mean that there's not a good reason behind it.
2. Just because you like something, it doesn't mean that it is good (or bad).
3. The things that the naysayers constantly bring up just aren't really that bad to begin with.
C is amazingly powerful, and I love it. I love JavaScript, too (especially since ES6!), but I don't confuse where I should use one rather than the other. I tolerate Python. ;)
Languages are just tools, and you should use the one most appropriate for the job at hand.
In fact, if you look at this post, some of the so-called defenses seem more like indictments to me. For example, in the increment/decrement section, there is this:
But look, there’s an even more direct argument: the ++ and -- operators are not even “one” operator. There is a prefix and a postfix variation of them that do slightly different things. The usual semantics is that the prefix ones evaulate to the already-modified expression, while postfix ones yield the original value. This can be very convenient, as in different contexts, code might require the value of one state or another, so having both versions can lead to more concise code and less off-by-one errors. Therefore, one can’t simply say that “++ is equivalent to += 1“, as it is simply not true: it’s not equivalent to at least one of them. In particular, in C, it’s not equivalent with the postfix increment operator.
This illustrates exactly the point that Evee was trying to make. It's hard enough to justify having increment and decrement as their own operators (as opposed to a performance optimization implemented by the compiler). Having two variants, which work exactly the same in the vast majority of instances, but do subtly different things in e.g. if-statements and for-loops is complete and utter madness.
For example, iterating and adding stuff to arrays:
array[index++] = object;
instead of
array[index] = object; index += 1;
and the other way around:
array[++index] = object;
instead of:
index += 1; array[index] = object;
There is no way you will miss incrementing your index here, or do it in the wrong place. This is so common, that as a C-programmer you WILL recognize this pattern.
It's even better when iterating pointers instead of integers. May I ask what the following means to you?
float *float_ptr; float_ptr += 1;
I think writing ++float_ptr here is just much more clear. Incrementing pointers by integers just feels wrong when what you really are doing is incrementing it with sizeof(float_ptr).
float *float_ptr; float_ptr += 1;
is no more confusing than
float float_ptr[...]; float_ptr[1];
If somebody had set out to create a language prone to error and exploitation, bash would result. (It's fine for interactive use BTW, just not as a language in which to write anything complex or long-lived.) While it might not have C's pointer/memory problems, its lack of real types or scope rules and its other idiosyncrasies (e.g. making it nearly impossible to distinguish "" from an undefined variable or the whole $*/$@ mess) make it even more of a nightmare. I've actually gotten pretty handy with it because I had to, but it's a far worse crime against developer sanity than C ever was.
(Downsides: Haskell is weird (but good), you have to have Stack installed, and the first time you run the script it will take a while to download dependencies)
More resources: https://github.com/Gabriel439/post-rfc/blob/master/sotu.md#s...
Interesting enough, a Scheme or even BASIC-like language with macros (eg 4GL) couldve handled about all that just as readably while building on a simpler, consistent core. Bash wasn't designed like that, though. So, I ditched it altogether except as compilation target.
"Let's Stop Copying C" isn't a criticism of C, so much as a criticism of other languages that have applied a kind of conceptual copy-paste of C-like characteristics.
Demonstrating that there are good reasons for those characteristics to exist in C talks entirely past the question of whether the sharp edges of some of those characteristics are still warranted in other languages with other design goals.
For sure, the author takes the most controversy points of Eevee's points, that AFAIK are mostly (her) personal preferences. However it does not enter in the interesting parts of her argument, like weak typing, C-style loops* or textual inclusion.
This post was much less interesting read than Eevee's post, that makes me wonder if the author did a click-baiting title just to try to get some clicks on his blog following Eevee's post...
*: really, this is the most retarded thing of the whole C-family languages; it kinda makes sense in C, however if you're writing a modern language that only supports this kinda of loop (pre-ES6 Javascript comes in mind), you're retarded.
Point by point:
- textual inclusion and macros: yeah, this technology was obviously chosen for ease of implementation on small systems and makes little sense. Now obsolete and a handicap. Especially in C++.
- optional block delimiters: responsible for gotofail and similar errors.
- modulo: symptom of C not having standardised maths semantics. Standardise your semantics, language designers! Don't just say "whatever the CPU gives us, I'm sure it'll be fine".
- octal: Nobody uses goddam octal and it's a trap for people used to leading zeroes in decimal.
If we're changing this then I'd like to appeal for some features from Verilog e.g. 8'b1101_1101 : leading width specifier, and the internal underscore which is ignored but provides visual clarity.
- power operator: meh.
- switch: actual operation is kind of bananas, see Duff's device. Should be block-based.
- integer division: Careful. Huge argument here about maths.
(tbc)
0b..., 0x..., 0o... the latter is still a bit subtle (0Oo), but far more distinct and far less likely to happen by accident.
> - switch: actual operation is kind of bananas, see Duff's device. Should be block-based.
the goto-table semantics of switch are kinda useful in many instances, OTOH a compiler should be smart enough today to achieve the same degree of rather simple optimization.
... some of which were extensions provided by Metaware's High C/C++ compiler back in the 1990s: 0x2x1101_1011 0x10x_bad_feed_face 0_777
PS: it's not as easy as you might imagine.
And Rust has been consistently delivering software with performance on par with or faster than C counter parts. http://blog.burntsushi.net/ripgrep/ is a recent example. Servo is a more long term one.
Integer division is misleading. It uses the mathematical division operator for something that doesn't behave in that way. Having a separate 'integer division' operator (e.g. //) is much clearer.
Nobody could say that the pre/post increment aren't confusing. They feature heavily in 'trick question' C++ tests (along with undefined behaviour, type promotion and so on).
For dynamic languages I kind of agree, but with static languages I think integer division is the right thing. int+int, int*int, int%int and int-int all return an int, so having int/int returning a not_int (what type should it return? should 4/2 return a different type than 2/4?) would be equally confusing.
I would suggest that it does not compile, and a different operator is required that makes it clear that it isn't 'normal' division. `//` is a good choice, except that is a comment in many languages. Maybe `_/` to indicate that it is a sort of 'floor division'.
/ = normal division
_/ = division then round down
Then the compiler enforces all the hard-to-remember rules, i.e. that you can't to normal division on integers. Floats would support both.This also frees up those symbols for use in more frequently used constructs. How often do you actually use those bit fiddling operators nowadays?
My point is that separating "real" division from integer division is... a bit artificial? Not totally mistaken - there are differences - and yet calling one "real" is going a bit too far. All of these models of arithmetic suffer from the limitations of their representation. In all cases, you'd better be aware of what those limitations are...
Floating point does not behave in a mathematical way, either.[1][2] Unless your language uses fractional representation using big integers for real numbers.
> that the pre/post increment aren't confusing
The operator and retrieval of the variable are two separate operations that occur in the order that you read them. None less trivial than "I" before "E" except after "C".
[1]: https://en.wikipedia.org/wiki/Single-precision_floating-poin... [2]: https://en.wikipedia.org/wiki/Arithmetic_underflow
It approximately does. The only common way that newbies would get tripped up is something like (3 * 1/3) != 3. Maybe that is a case for not allowing == operator to operate on floats (use a function/keyword instead), but I think that may be a step too far.
On the other hand, that is a property of floating point numbers, regardless of what language one uses.
That's true, but, OTOH, having floating point rather than an exact representation be the default representation for values represented as decimal literals, and the only non-integer type supported by convenient operators is a language-specific "feature" of C that many other languages don't share. (Though, to be fair, there are also many newer and popular languages that do share both, and more just the first, of those features.)
EDIT: Also I like my numbers reflexive.
I will - its one of the simplest concepts and far from any of C's real problems.
It's C++ that is more like a chainsaw: a noisy, smelly, clattering but effective tool, dangerous without modern protective gear, and an equally poor choice for tightening screws.
One of the things that I like about the chef's knife is that if you hold it correctly (http://www.seriouseats.com/2010/05/knife-skills-how-to-hold-...) the deep blade gives you both control and increased safety. People often think the bigger the knife the more dangerous, but often the opposite is true.
Chainsaws on the other hand scare me despite their utility, possibly because my grandfather (a very experienced chainsaw user) had a horrendous scar across his neck and face from being stitched back together after an unexpected kickback.
Some people say Golang is that language, but the tooling is actually not that great and then theres the garbage collector...
Edit: forgot to add "sound type system", to me C feels like a hybrid between a properly strongly typed language and something scripty.
Make, while I quite liked it in its day, has some real limitations. cmake, while better on some fronts, doesn't actually solve the right set of problems (or, it solves the set of problems as they were perceived ~15 years ago).
I wish there were a strict subset of C (compliant with standards, but that prohibits the trickiest bits during build), with a good package/build system, a huge/modern library of high level functionality. I always enjoyed poking at C, but I just can't spend enough time coding in C to ever be really good at it. I can go years between working on C code, so I forget all the gotchas by the time I look at it again. It's not forgiving of casual users the way Ruby, Perl, Python, Go, and even (some) Java can be.
Then again, I guess Go is kinda that language for some classes of problem (not the very lowest level systems stuff, but I rarely do any of that kind of coding, anyway; I haven't touched a kernel module in a decade).
In most cases I don't even need a full build system. I do most of my hacking in Python and I often create small "throwaway libraries" to create a nicer structure of my project. In Python you can do this by just creating a sub-directory and a __init__.py file. This takes 45 seconds and then you can easily "import lib_xyz" in your project. And if you need this library in another project you can just copy/paste the. (I know, this is not an ideal solution but when I am hacking on some stuff it is a good way to do things because it takes no effort and brainpower to do it)
It's not so much tools like debuggers or linters, but more about the build system and the compilation process. There is just do much crust and dangling bits and pieces of the last few decades that the whole process is the complete opposite of streamlined and user friendly.
I do most of my programming in Python and for most projects it is enough to create a program.py file, import some stuff and then do 'python program.py'. If your Python program is not overly complex and only uses one of the common libraries then there is almost no visible build system which gets in your way.
If i haven't used Python in half a year it takes about 30 seconds to start programming again, but god forbid I haven't looked at make or cmake for half a year...
C is going to look horrible if you've only ever used python. That's because you don't understand at all about real computers and how to program them.
But no, trust me there are C programmers who use malloc and friends without knowing about the heap and stack. For those programmers it's just how you "get memory" in C. It's just part of the language for them (as opposed to part of the operating system).
A personal anecdote that took place sometime between 1999 and 2003.
Nor sayng it's perfect and universal, but it is pretty widespread even outside "The West"
IME, Indians and Germans who use English are generally extremely proficient in it.
What proportion of developers can write C at an analogous level to first, second or foreign language? 20% seems like quite a good ball park estimate, from my (anecdotal) experience.
you can't tell machines how many bits to use.
Think about that for a moment in the light of Python 2 - Python 3 or K&R C - C99 or original C++ - C++14
Yikes! How about "old enough to have a crapton of experience I'd really like to learn from/integrate with my company"? I know there is ageism in this industry (towards both people and tools), but sometimes the cavalierness with which people throw out the idea that "old == bad" and "good == new" comes as a shock.
And besides that, shouldn't we be proud of half a century of stability and compatability? Isn't "well-designed systems that stand the test of time" a holy grail to reach for, rather than poke fun at?
To make an analogy, I can perfectly understand null and what it does. My objection to null as a language feature is not based on not understanding it and what it does.
Firstly there is simply the `i++`, which is redundant with `i+=1`, but it's also very easy to understand syntactic sugar. Keep it or leave it, no big deal.
Then there is the more advanced and very subtle use of the return value:
int i = 3;
while (i--) {
printf("%d, ", i); // What does this print?
}
This one is very controvertial. On one side, it simplifies and shortens code by a lot, but it also makes the code say too much at the same time. Can you tell by glance what this code would do? Personally, I prefer longer code and more expressive code, but I can also cut corners with the short version from time to time, when quickly testing something out.EDIT: Fixed code formatting
2, 1, 0
Your code is just syntax sugar for assembler code. If you think in assembler instructions, you will have no problem with it. 2, 1, 0, %
where % is... I'm not sure what. What is that exactly?I read the instruction as: "Set i to 3. Subtract 1 then print until false."
i = 3
3 - 1 = 2, print
2 - 1 = 1, print
1 - 1 = 0, print
0 = false, stop
I see a lot of people complain about using `--` like that but I fail to see what is unintuitive about it if you actually read the code and see what i is initialized as. =\If you're using zsh:
> When a partial line is preserved, by default you will see an inverse+bold character at the end of the partial line: a ‘%’ for a normal user or a ‘#’ for root. If set, the shell parameter PROMPT_EOL_MARK can be used to customize how the end of partial lines are shown.
See http://zsh.sourceforge.net/Doc/Release/Options.html#Promptin...
I agree that you can write confusing code with ++, but that doesn't mean it should be banned! (Like they did with Swift). The ++ operators still are very useful. Also there are arguments that they're more correct than +=1 operations. I see ++ as meaning, "go to the next thing."
For example, the following operation sort of doesn't make sense:
char c = f();
c += 1;
Why are we adding an integer to a char? If you really wanted to be type safe, it should be c += '\001';
So you're adding a char to a char.Same goes with pointers. They're memory addresses... but what happens of you add 1 to a memory address? Should you get the address plus one, or should you step over to the next object?
It doesn't compile in C++. Conditions have to be booleans. i-- is an integer :D
In C, 'true' is defined as anything else than '0', there is no proper boolean types, i-- is an integer which is perfectly okay for conditions, the condition is equivalent "while (i != 0)".
There might be some variations, errors and warnings depending on the compilers, the strictness level and the revision of the language chosen.
I am definitely talking about C++
int x = 1;
bool y = x;
# cl.exe /W3 main.c
This code gives a warning on VS2012. But it doesn't give one when the cast is in a loop condition. That is weird.http://stackoverflow.com/a/31552168/5994461
This stackoverflow message talks about the specs for C11, and the first comment adds information on the C++03 spec. It seems that implicit cast from integer to boolean is allowed... under all circumstances... depending on what specification the compiler is following :D
For future references, I'll just summarize this as "C and C++ are minefields". We'll just add that to the list of WTF behaviors.
By the way, if you think that "C has had bool for 19 years" [the C99 spec specifically]. You clearly didn't work in C for long enough with a large variety of tools. The world is bigger than just GCC.
I believe that the justification for that is that you'll often want to do e.g.
while (node) {
node.val += 3;
node = node->next;
}
Implicit conversion of a type into a bool is pretty useful here, or for e.g. while (std::cin >> x >> y) { ... } int i = 3;
while (i--) {
printf("%d, ", i); // What does this print?
}
printf("%d, ", i);"++ is also used for iterators" Python has "next". I understand there's "*p++", but the semantics of ++'ing an integer or ++'ing an iterator are very different! Why should they share the same mechanism? (There's an argument that ++ on integers is iterating over N. Pedantically true)
"Integer math is useful!" Floating point semantics are different, so why not use different operators for such? Caml has + for its, +. for floats. Impossible to mix different semantics together implicitly.
--
I find saying "whitespace sensitivity makes auto-indentation impossible" to be a bit silly. The equivalent of auto-indentation in Python to C is auto-brace insertion! Just as impossible.
There are definitely parsing difficulties with whitespace sensitive language (technocally, lexing difficulties). If you're willing to go for parenthetic function calls like in Python, it's not too hard. But go for Haskell-like function application and you're in for a treat! Would not want to have to parse Scala.
I can see the argument to braces being better, though. It's such a subject of taste that if you don't want to be opinionated, going for explicit blocks is really your only choice.
This sort of misses the point there. What eevee was saying was that "Usually you use ++ to mean +=1". It is equivalent to +=1 in those cases.
Eevee then goes on to say "The only difference is that people can do stupid unreadable tricks with ++.". She is talking about postfix/prefix there. She never said that ++ and += are exactly the same, she's saying that they're mostly the same, except for some cases which often read to more unreadable code. Using i++ or ++i as an expression within a larger expression often leads to unreadable code.
The author here then goes on to talk of off by one errors, but ++-as-expression is what causes a lot of off by one errors.
https://news.ycombinator.com/item?id=13089663 explains this a bit too.
---
The other two points also miss the point a bit. The post is not "why C is bad". The post is "Let's stop copying C". There are good reasons behind C having many of these features. These reasons do not necessarily port to other languages; yet they copy the feature.
But near the end of college I had to write code using pthreads. It was awful. It was exceedingly painful. Banging on the keyboard cussing continuously painful.
Maybe it was just pthreads (I'm sure there are nicer libraries) or my stupidity but that exercise killed my mild liking of C.
Languages I like are heavily expression based but don't require braces (algol like languages) nor parenthesis (lisp like). As much I dislike braces and parenthesis (for blocks) I despise statement heavy languages more (sadly python). I wish the article of the "Stop Copying C" mentioned that (or maybe I only share that opinion).
For what it's worth, I don't think it's C's pthread library that is painful, but POSIX threading in general. Languages and libraries try to "simplify" threading by re-inventing pthreads. Eg: python's 'threading' module, erlang in general, java 'runnable' interface, golang's 'coroutine' thread manager...etc.
The difference between all those languages specific threading implementation and POSIX threads, are that POSIX threads work across every language, and all those implementations are only relevant to the language itself. Working with "one size fits all"(pthread) tools is inherently more complex than a simplified tool specific to one language.
Summary: once you learn POSIX threading [well] in C, using other pthread abstractions in other languages becomes much easier.
I agree on the reimplementing POSIX threading and despite my rough experience pthreads was worthwhile learning about.
Having taught as well, Python is not a language for beginners. They are punished for getting their spacing wrong, often confuse types, oo seems to have been an after thought and versions 2 & 3 are completely different languages.
C can also be confusing for beginners, but not for the reasons mentioned. Teaching people about pointers spins heads for the first time.
Java for me seems to be the middle ground. Good understanding of oo, portable code, solid types, well formed errors, beautiful garbage collection and well thought out (libraries, types, access, concurrency, etc).
C is obviously an advanced language, but like PHP there is a good reason why it's not ready to be buried yet.
The limitation of programming (from a bug-minimization perspective) is the programmer's ability to understand all the possible states his code and data can get into which means that the key to bug and security hole elimination is any pattern which reduces cognitive load
I mean, getting mutable access is trivial:
let mut x = ...
fn frobnicate(x: &mut...)
The performance "problems" in Rust requiring "hoops" are in very narrow applications (e.g. certain applications of high performance numerical computing). I'm not a fan of the "unsafe-offset" idiom required so that every time access into a vector occurs doesn't waste cycles on a pointless boundary check (for example).But I must say for a given chunk of Rust code the equivalent C++ code is generally more bloated, because unless you're writing code begging for abuse you're going to have "const" and move operations everywhere, which is sort of inverted from Rust's model.
The only cognitive load is generating test vectors, and I use scripting languages for that.
(from the OP's comments to another reader)
Allow me to translate: Ok, let's stop bashing C. Let's start bashing people who have legit complaints about C.
I don't think the above comment was warranted. Someone took the time to read your arbitrary article and attempt to offer a meaningful point and your response was to demean the fellow.
Edit: a nice write up by Chris Lattner: https://github.com/apple/swift-evolution/blob/master/proposa...
Given that this reply also starts with "To begin with, I agree with most of the things Eevee wrote", I'm not sure "Let's stop X" is the right title to use, even if it matches the theme of the original article.
The reply itself is... a decent alternative viewpoint, to a few of the points made, but I don't think it quite matches the title. And now some counter-counter points:
> What’s Wrong with Integer Division?
This is the wrong question. The right question is: What's wrong with integer division if and only if both arguments are integers, and ambiguously conflated with floating point division in syntax? I'm sufficiently used to it to not find it a big deal, but that's a poor argument for it's merits in new languages.
Nobody thinks integer division shouldn't be an option.
> However, the behavior of integer division is very useful. I would argue that in most cases, one expects it to behave just as it does.
I've seen even professionals trip up over the semantics of this just regularly enough to be annoying - you read float a = x/y; and assume terrible things about x and y, things that were perhaps even once true. Even if you really did intend to round, you probably assumed unsigned 'round down towards zero' semantics and will have a bug to contend with when x<0 and end up rounding up towards zero.
Personally, I haven't found it that difficult to adjust to languages that have a separate integer division operator, or languages requiring an explicit rounding operation, despite a long history of only using languages with C's semantics before them.
> Unfortunately, Eevee seems to fall for the extremely common “++ is equivalent with += 1” fallacy. You can see it’s a fallacious statement when, in the end, even the author herself admits that there are things that can’t be implemented in terms of “+= 1“; for instance, incrementing non-random-access iterators.
That Eevee herself points out that "++" is not always "+= 1" makes it quite clear that she hasn't "fallen" for any such fallacy. The point is that even including other unmentioned (ab)uses of overloading, there is always a way to rewrite things in an equivalent manner in terms of "+=", "-=", or custom functions - combined with either the abuse of the comma operator, or multiple statements. For the mentioned case of iteration - plenty of other languages have a separate, named function for iteration, even if they allow "++" and "--" to be overloaded.
The only real point of debate here is how often the "++" and "--" operators produce concise code, versus how often they produce write-only code.
Helping the inertia by self-sabotaging with over idealistic , non backwards compatible languages, the software architects and computer scientists lead the procession.
Followed they are by self-flagellating programmers, trying to figure out who beat them to it again and again.
Followed they are by the silent businessman parade, who search for the perfect compromise between throw-away code and reuse ability on hardware where the solder-lead is not stable yet.
All hail the holy C, all hail the mighty procession of pain.