Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

As a long-time C programmer (20+ years), this quiz -- despite its flaws -- are a good example of how difficult it really is to write 100% portable C code.

I often hear developers talking about how C is a simple language, but it may or may not be, depending on how you define "simple". For example, a written alphabet with only two letters -- A and B -- is "simple" to grasp (there's only two letters to learn!), but it would be very difficult, in the real world, to read and write such a language.

I think C is similar: "simple" in a sense, but very, very complex in another sense. C could use some massive clean-up, in my opinion. Much of its design was so that it could be ported to CPUs which haven't really existed in the wild in a long time in great enough numbers to warrant all the undefined and implementation dependent behavior.

The language could be hugely simplified, in the usage sense, if much of the cruft were jettisoned.



That's a huge problem with programming languages: Designers can always add things, but removing stuff is hard. JavaScript is a great example of this. It still has automatic semicolon insertion, variable hoisting, and a broken comparator (==). Practically everyone agrees these are bad things, but they can't be changed. Doing so would break backwards compatibility, and there's a huge ecosystem of JavaScript programs that would need to be updated.

Also, language users often rebel when language designers make breaking changes. Python 3 tried to remove cruft, and look how slowly it's been adopted. Instead of adopting 3.x, people backported the features they wanted to 2.x.

I would love it if C had less cruft, but when I say that I mean, "I want C with less cruft, but with the same huge ecosystem of documentation and libraries and tools and debuggers and profilers that crufty-C has."


That's why I think the language Go is such a great development. The authors seem to have added only the minimum set of features that make the language workable for their initial needs.

As a result, these features are largely orthogonal, and the rules are simple to state and learn -- even if they at first seem a bit odd (you have to cast all numeric types to each-other before they can interact).

In contrast to Scala, another modern language I investigated recently, which seemed like quite a thicket of features.. some of which seemed just to be there to mediate the interaction of other features.


Even languages that are explicitly designed to be minimal can be anything but. For example, Scheme. The call/cc function is a lot more complicated than it sounds, and it sounds complicated. People are talking about removing it from the "minimal" Scheme. (There are two versions of R7RS, the big version and the small version. If you think that call/cc is actually simple, then you haven't tried to write higher order library functions such as map.)

And Scheme's syntax for numbers was invented by Cthulhu himself.

(Python 3 was supposed to be adopted this slowly, last time I checked.)


Much of its design was so that it could be ported to CPUs which haven't really existed in the wild in a long time in great enough numbers to warrant all the undefined and implementation dependent behavior.

A lot of the implementation dependent behavior is now expected. Not too long ago, I was taking part here in a brief debate in the comments for a Go language related post, and the programmer was up in arms that the Go compiler didn't transparently cast things for him. I pointed out that this would result in implementation dependent behavior, and maybe the design goal in Go was to eliminate that.

His reply: That's what people should expect. >Of course< you should expect all these arcane things to happen when you port from one platform to another.


I was taking part here in a brief debate in the comments for a Go language related post, and the programmer was up in arms that the Go compiler didn't transparently cast things for him

I was that programmer, and I've now changed my mind.

The whole 'less design is better' is always more palatable when it doesn't touch the things one is used to taking for granted (in my case, having a nice numeric tower).

But the union of all the common such features gives you something like Scala, which is to me obviously over-designed. Go is more like the intersection.


I agree, but I also think that C isn't as complicated as these kinds of quizzes suggest. I've been using C for over 20 years as well, and I do know most of these arcane rules, but I don't rely on that knowledge much.

I decided a long time ago that it makes no sense to trust myself and everyone who might have to maintain my code to always remember all these rules. It's a lot simpler and safer to remember a much smaller set of rules like don't mix signed and unsigned and just enable all the warnings.

One thing I do that may be controversial is to prefer fixed size types most of the time. In my opinion, the decision to use a 16, 32 or 64 bit int type is not primarily a portability or performance issue. It's a decision driven by application requirements, and I want to express these requirements as explicitly as possible in my code.


I think I don't understand... On one side, you say that C isn't that complicated but on the other side you say you just use a small subset because you don't trust yourself and your colleagues to understand all the C quirks...

I think C is complicated with all the 191 undefined behaviors and 52 unspecified behaviors (source for these number is the paper that the author of the quiz wrote for specifying CSmith). One must master C before having absolutely knowledge that the code that he wrote doesn't fall into one of the two black-holes (undefined and unspecified behaviors). For instance, almost everyone that I ask, they tell me that dereferencing a NULL pointer yields 'Segmentation fault' but it's just not true. It's in fact undefined behavior.

The other thing is the rules for strict aliasing... It's pretty much impossible to convert one pointer to another without breaking some of the C rules. The experts say type punning is the way to go but type punning relies in another not defined behavior. Reading an element from an union that wasn't the last element written is undefined behavior.


I didn't say anything about a small subset. The rules I use are not a subset of all those special cases, they are broader rules that cover lots of potential pitfalls and are easier to remember. But C is definitely more complicated than it should be, no doubt about it.


Wait wait, all questions in this quiz are not really about C but about the form in which numbers are stored in computer and consequences of that -- C just expresses them in raw way, but other languages also suffer from this quirks, either directly or via performance/accuracy jumps.

EDIT: Ok, Ada has this done right.




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

Search: