Hacking on the C code of giflib after an absence of nearly two decades has been an interesting experience, a little like doing an archeological dig. And not one that could readily be had elsewhere; nowhere other than under Unix is code that old still genuinely useful under any but carefully sandboxed conditions. Our reward for getting enough things about API design right the first time, one might rightly say. But what’s most interesting is what has changed, and giflib provides almost ideal conditions for noticing the changes in practice that have become second nature to me while that code has stood still.
I mean, this is code so ancient and unchanged that it still had a macro to hide the difference between C compilers that have void pointers and those that don’t – it wasn’t assuming even C89, let alone C99. I only resolved the uses of that to “void *” last night, and I’ve left the definition in the header just in case.
But the largest single executable-code change I found myself making was introducing C99 bools where the code had simulated them with old-style TRUE and FALSE macros. Only internally, I might add – there are a couple of pseudo-bools in the externally visible API that I haven’t touched so as not to risk breaking source compatibility. That and counting on stdint.h to supply uint32_t were two of the big three; I’ll get to the third in a bit.
The fact that editing in now-standard typedefs was the largest change in executable code is a testament to the remarkable stability of C, I think. I can barely think of any other language in which bringing code across an eighteen-year gap would be anywhere near that easy, and you would not want to use any of the handful of partial exceptions for systems programming on a modern machine.
Something else I noticed is that there was no point at which I looked at my old code (or code from Gershon Elber, the original author of the DOS version) and had a “What the fsck were you thinking?” moment. Which is good: I guess I was already a pretty good programmer 20 years ago, and the proof is that this code ended up deployed everywhere that displays pixels, including the phone on your hip, without my ever hearing barely a bug report. On the other hand, it’s a little disconcerting to think that I might not have learned anything about practical C in two decades…
There were some moments of pure amusement in the quest as well, such as my rediscovery of gif2epsn – a utility for dumping GIFs to Epson dot-matrix printers, actually using the individual print head wires as scanning pixels. Just what the world needs in 2012. I removed it; it’s still in the repo history in the unlikely event that anyone ever cares about that code again.
In general a lot of what I’m going to be doing for the upcoming 4.2 release is removing stuff. Back then, it made sense for giflib to carry a fairly elaborate set of image viewing, image capturing, and image format conversion tools, because relatively few other codebases spoke GIF. Of course that’s all changed now, with multiformat viewers and editors the norm. I’m probably going to throw out several more utilities because it doesn’t make any sense to compete with the likes of ImageMagick and the GIMP in that space. In a sense, giflib is a victim of its own success at making its format ubiquitous.
I’ve saved what I think is the most interesting change for last. When I’m not looking at code this old I tend to forget what a thicket of #ifdefs we had to manage back in the day. Cross-platform portability was hard, and it made our code ugly. Just the huge numbers of conditional includes of header files was bad enough; we used to be nibbled to death by a thousand little ducks like the stdargs.h/varargs.h skew. It’s easy to forget how much cleaner and lighter C code is in the era of full POSIX conformance (well, everywhere except the toxic hell-swamp that is Windows, anyway) and C99 required headers – that is, until you go looking at code as ancient as giflib and it all comes flooding back.
I had almost forgotten how liberating it felt four or five years back when I made a policy decision about GPSD. Yes, we’re going to assume full POSIX/C99/SuSv2 and if that chaps your ass you can damn well fix your broken toolchain! The giflib code is much older and more encrusted. Well, it was until about 48 hours ago. I ripped all that gunge out. The code almost doesn’t need autotools now, and if I chisel a little harder and drop the X viewer and Utah Raster Toolkit support it won’t need autotools at all.
That is, in itself, interesting news. The autotools suite began life because the combinatorial explosion of feature tests and #ifdefs back in the day was too difficult to manage by hand. Over the years autoconf and friends got hairier and hairier (to the point where I got utterly fed up with it), but at the same time increasingly good standards conformance in C and Unix implementations attacked the problem from the other end. So, during the last couple of days, I’ve found that the sort of platform #ifdefs that used to be autotools’s raison-d’etre can all be yanked out – what’s left is feature switches, and only two of those.
That whole standardization thing…actually worked. Though because it snuck up on us by stages, old farts like me have half- to three-quarters forgotten the problems it was solving, and younger programmers barely ever saw those problems to begin with. This is called “progress”.
You bring back long forgotten nightmares for me …
How much of that is standards conformance and how much of that is the result of the Linux/x86{,_64} monoculture?
>How much of that is standards conformance and how much of that is the result of the Linux/x86{,_64} monoculture?
It’s mostly standards conformance.
Here’s how I know. I routinely test-build and then run regression tests for GPSD on the Debian porterboxes, which include about a dozen odd architectures – ARM variants, MIPS, SPARC, PowerPC, m68x, even s390 and s390x. I’m pretty sure there are only two architecture-specific #ifdef conditionals in the entire build, one for big-endian/little-endian and one to condition in a memory fencepost instruction. And currently the only port bug appears to be caused by two toolchains not implementing #pragma pack(1).
> There were some moments of pure amusement in the quest as well, such as my rediscovery of gif2epsn
At first I read that as “gif2espn”, and thought “Yes, that is amusing. What does ESPN have to do with GIFs?”.
Yet more proof that the Golden Age of hacking is now.
I don’t let my fond memories get in the way of enjoying our present technologies and culture. I wouldn’t want to go back.
My favorite example of creaky, extremely compatibility-laden code is the stuff generated by flex and bison, GNU’s clones of lex and yacc. If the right #defines are set or not set, flex will define its own implementations of strlen() and strncpy(). Every function definition in bison’s output has an ANSI-style definition and a K&R-style definition, surrounded by a thicket of #ifdefs for compiler detection. And then, of course, there are the endless lines of compatibility stuff designed to let people change the way the generated code compiles. Want to change the type of a uint8_t? No problem, just pass a -DYYTYPE_INT8 flag to the compiler! And so on.
I’m kind of awed that it works, and it’s comforting to know that I’ll still be able to generate parsers if I’m ever suddenly cornered in a dark alley by an old Xenix workstation, but still, it feels like a morality play about the dangers of trying to follow all the rules at once.
Eric, as a longstanding C hacker I was wondering if you still stick to a style that is IMHO a definite indicator of one, that is explicitly writing NULL for a null pointer. Although my C programming history does not go back so far I still prefer to use NULL, but the prevailing style in most of the projects I work with today seems to be to use 0 (or even an unnecessary embellishment such as 0L, why?). For the reason that ‘if (p!=NULL)’ surely shows what is intended more clearly than ‘if (p!=0)’ or even ‘if (p)’.
Admittedly, trying to define a NULL that works well with both C and C++ has problems, but the system header files should sort that out. Maybe one day we will all be using ‘nullptr’ anyway…
>Eric, as a longstanding C hacker I was wondering if you still stick to a style that is IMHO a definite indicator of one, that is explicitly writing NULL for a null pointer.
I am disconcerted to learn there are people who do anything else.
As you say, ‘if (p!=NULL)’ surely shows what is intended more clearly than ‘if (p!=0)’ or even ‘if (p)’. The use of this macro carries semantic information. I will continue doing so until I drop.
Hmm… I would say that ‘if (!p)’ is more clear than ‘if (p == NULL)’; the latter contains unnecessary information about implementation details, don’t you think?
OTOH ‘if (!strcmp(foo, “bar”))’ is not yet learned idiom for me, and I find ‘if (strcmp(foo, “bar”) == 0)’ more readable…
>OTOH ‘if (!strcmp(foo, “bar”))’ is not yet learned idiom for me, and I find ‘if (strcmp(foo, “bar”) == 0)’ more readable…
So do I. I prefer to only use ! on bool-valued expressions.
“It’s easy to forget how much cleaner and lighter C code is in the era of full POSIX conformance”.?Not everyone’s there yet, e.g. nmh, the RAND MH descendant, recently got caught out by OpenBSD’s lack of utmpx.?http://lists.nongnu.org/archive/html/nmh-workers/2012-04/msg00037.html
Speaking of old farts and monocultures… I wonder whatever happened to the culture that stabilized the Jargon File. (And the mailing list, for that matter.) Did this “generation” simply disperse behind weblogs and SIGs and comfortable homes in the country?
>I wonder whatever happened to the culture that stabilized the Jargon File
If you mean the PDP-10 hackers from the “generation” before me…some of them are still around, I know several. But there were never very many of them compared to the teeming throngs of today’s hacker culture, and they are as you say dispersed in it.
I’d probably rather see ‘if (!allocated(p))’ (given an implementation of allocated that compiles to (p != NULL) or similar). If you want to play the encapsulation card, you have to define why exactly the negation of a pointer has any meaning whatsoever. In the context of C, I’d argue that the latter involves fewer implementation details as NULL could be 0xFF..FF or any sentry value of the implementor’s choice, the former would only work where NULL == 0.
Good grief. I have never understood using opaque idioms like (!p) instead of (p==NULL) or (!strcmp(foo,bar)) instead of (strcmp(foo,bar)==0). Those pairs of expressions should produce identical code out of the compiler. assuming that they are, in fact, identical, and even if not, program maintainability beats code efficiency every time. Even if the original coder is a Mel, some other poor slob is going to have to come after and figure out why the program cheats every time in its own favor.
If you haven’t come back to your own code five years later and asked “why the fsck did I do that?!”, then you haven’t programmed enough.
If you haven’t come back to your own code five years later and asked “why the fsck did I do that?!”, then you haven’t programmed enough.
I’ve come back five weeks later and asked that question. So I try to put in comments that explain anything I think Future Me will have to know about why I did it that way.
(It seems this comment system doesn’t like U+2002; it isn’t treated it as a space. http://en.wikipedia.org/wiki/Space_character )
If one thinks found = search(); if (!found) is “opaque” unless written if (found == NULL) then one hasn’t written enough C but instead some crippled subset. :-) The former even reads more naturally; “if not found”.
And IIRC it doesn’t matter if the platform has a bit-representation for a null pointer that isn’t all zeroes, the compiler knows found is a pointer and what it means to take its *logical* complement; direct comparison with the NULL macro isn’t required and is just more wordiness for the reader to grok.
“If you haven’t come back to your own code five years later and asked “why the fsck did I do that?!”, then you haven’t programmed enough.”
I think that’s might be a bit unfair to ESR. Some of us, such as me, learn programming techniques across a long expanse of time. Others, the brilliant ones like ESR, learn far faster and in far more depth. I’ve only personally known a couple of people like ESR. I don’t doubt for one second that ESR had pretty much sewn up the entire volume of nuance and esoterica of C within a few months of his having first been exposed to C. ESR is a savant without the brain damage.
>I don’t doubt for one second that ESR had pretty much sewn up the entire volume of nuance and esoterica of C within a few months of his having first been exposed to C.
That’s actually an interesting (implied) question. Hm. Did I become fluent in C that fast?
I wrote my first C program early in 1982, I think. We can take giflib as evidence that I had mastered the language by 1990, but that’s eight years already so it doesn’t resolve the doubts you didn’t have :-). I do remember that I felt comfortable with it very quickly, like within weeks, but that’s not the same thing.
Probably the best evidence that I learned C damn fast is that I picked it up on the job at a startup company – not conditions where there’s a lot of tolerance for error or slowness. It didn’t seem like a big deal at the time, because I went in with fluency at Pascal. I can still recall how C felt by comparison; there was this amazing lightness about it, the ability to do much with little.
Ralph, the problem is that examples such as yours are much less common than other reasons to compare pointers to NULL.
Wordiness for the readers to grok is a good thing, because it fosters understanding by someone who hasn’t touched the code in a while, be it the original author or another.
Clarity of code and understandability to those not familiar with it are overriding considerations in any program that people will need to maintain. Attitudes that say “look how much of an expert I am because of this obscure code I write that’s not readily understandable to newbies” only make the maintainer’s job harder, and lead to bugs later in the program’s life.
patrioticduo, I wasn’t intending that as a reply to Eric’s thought, but as a general insight into programming. Indeed, the right defense to that question is to adopt programming practices that tell your five-years-older self just why the fsck you did do it that way, and avoid the need for the question in the first place. It seems giflib was done in just that way.
Even in the late ’70s programmer time was more expensive than computer time for most applications. Even on early compilers
if ( NULL != ptr )
compiled to the same object code as
if ( !ptr )
Oops
Those expressions are opposites.
@Jay Maynard:
“Attitudes that say “look how much of an expert I am because of this obscure code I write that’s not readily understandable to newbies” only make the maintainer’s job harder, and lead to bugs later in the program’s life.”
Agreed. Save that stuff for the Obfuscated C Contest.
Ugly wart on Python: Python is designed to encourage this sort of thing. 0, None, the empty list, the empty string, the empty tuple, and the empty dictionary are all considered false in conditionals, leading to this sort of idiom becoming commonplace:
files = collect_files()
if not files:
print "no files found"
What about Ada? It’s hella portable, and designed for long-term maintainability, and these days with the GNAT toolkit hacking in Ada can be a pleasant experience (though its much more strict type system takes some getting used to). A lot of its bad rap came from crufty compilers which cost way too much money, the vendors only getting away with it because of the DoD mandate. The decline of that market is yet another open source disruption success story.
I’m not saying I’d prefer starting a project in Ada to starting one in C, but Ada is infinitely preferable to C++ for new code.
Eric, this is thoroughly OT for this thread, but have you seen James Lovelock’s admission? It’s hardly a comprehensive mea culpa, but even a lesser acknowledgment would have been major news.
>Eric, this is thoroughly OT for this thread, but have you seen James Lovelock’s admission
I have, and if I weren’t heading off on a road trip I might have blogged on it already. As you say, not a comprehenisve mea culpa but pretty crushing stuff for the true believers nevertheless. I wonder who the next major climate alarmist to collide with reality will be?
‘if (p!=0)’ or even ‘if (p)’
You can blame Bjarne for this. He explains that, due to C++’s tighter type-checking, the use of ‘0’ leads to fewer problems (in C++). Unfortunately, that usage has now infected C programmers.
I would hazard a guess that those who prefer ‘if(p)’ fall into two categories:
1. Those who believe that C programming is a game that rewards the fewest keystrokes.
2. Those who get tired of chasing bugs caused by typing ‘if (p=NULL)’ and don’t want to use the Yoda form.
Personally, I think Bjarne Stroustrup should be shot, drawn, quartered, tied to four horses and dismembered, soaked in molar hydrochloric acid, and then the leftover bits jumped up and down on by the Houston Texans in full uniform for inflicting C++ on the world.
Tut tut, Jay. Molar hydrochloric acid? Excessive. Mere excruciation on the rack would suffice.
The Texans? Feh. More proper to get the UT team to truck on over to College Station, I think.
No doubt I am about to open myself up to ridicule, but…
Perhaps somebody would be good enough to explain why this sort of pattern is a Bad Thing? It could be because I learned python as my first language, but it seems perfectly natural to me, and extremely readable.
Tom: Because it is an implied comparison, rather than an explicit one. This matters when someone who’s not the l33t h4x0r who wrote the code has to come along and maintain it; he may not be as intimately familiar with the guts of the language, and may interpret the code incorrectly when trying to fix an unrelated bug.
jsk: I doubt the Aggies would let Kyle Field be used for the ritual, though. They have standards.
Seems that C++ (and its creator) are the Marmite of programming languages – either love or hate with nothing in between.
Personally I’m more inclined towards the former, although I will admit it is a long way from perfection. But it does have just about the right amount of rigour and type checking: enough to catch common mistakes and to be able to implement safe but versatile library code, but not enough to make it into a bondage-and-discipline language.
Disclaimer: it’s a long time since I programmed in Java.
I might have agreed with you a few months ago. That was before I ran a KDE build and saw the resident memory for a g++ process north of 1.2G.
James M: My introduction to C++ has been an 800KLOC crawling horror, and a 950KLOC follow-on to it, where a crash rate of 10% of executions is considered outstanding and every nasty opaque programming trick you can conceive has been used – and then Boost layered on top of that, with its own special hell. (I consider Boost an indictment of C++ as I consider CPAN an indictment of Perl.) I once spent three days chasing through a twisty little maze of templates, all different, to find a bug that was taking a 32-bit signed value and turning it into a 32-bit floating point value, then back again, losing you guessed it, nine bits of precision. I finally gave up in disgust.
I can handle a bondage and discipline language. I can handle C. The problem with C++ isn’t it’s lack of/possession of the B&D nature; it’s that it’s a nice compact language that’s had a railcar load of horse exhaust troweled on top of it in the name of the software engineering fad of the day, for the last 20 years.
@James M “Admittedly, trying to define a NULL that works well with both C and C++ has problems”
Define “works well”. 0 works fine except in situations that you really ought to be explicitly casting anyway (namely, varargs situations like the final argument to execl). There’s not really much that using a void * or even 0L gets you if you’re using it in a standard-conforming way [namely since none of the things the other definitions get you are, in fact, guaranteed by the standards.]
NULL is therefore mainly useful a matter of self-documentation, same as still using true and false even though even C99 _Bool has values of 0 and 1. Speaking of boolean types, I do think it’s an unfortunate artifact of C’s evolution that comparison operators still return int rather than _Bool – this is an aspect of backwards compatibility that C++ broke, and maybe we can hope C will eventually also break it. (and maybe one day C++ bool will be the same type as C _Bool – I’m not sure if that’s allowed by current standards – maybe C++11 fixes it)
@Ralph Corderoy
>If one thinks found = search(); if (!found) is “opaque” unless written if (found == NULL) then one hasn’t written enough C but instead some crippled subset. :-) The former even reads more naturally; “if not found”.
What’s unnatural is calling the pointer “found” instead of something like “result” in the first place.
This conversation is bring back memories of trying to learn to program the Amiga when I had barely learned C.
Code like:
if ((winptr = (struct Window *)WindowFunction(arg1tag, arg1value, arg2tag, arg3value)) != NULL) {
// do stuff with winptr
}
was common even in the manuals.
Tut tut, Jay. Molar hydrochloric acid? Excessive. Mere excruciation on the rack would suffice.
Depends on the molarity, surely?
http://artlung.com/smorgasborg/Invention_of_Cplusplus.shtml
I have always loved that parody Bjarne interview. My opinions of C++ have closely mirrored Linus’s (seen in repro here: http://harmful.cat-v.org/software/c++/linus )
If C++ only had a very minor subset of what it actually has (single inheritance, operator overloading, simple classes and other basics) I’d be tempted to use it, or at least not loathe it so.
As standards and compilers evolve, C++ becomes stricter about its types. But it has a tendency to be penny-wise and pound-foolish; for example the meaning of the word “const” is not clear and varies widely depending on where it is used, but the compiler is very strict about enforcing const-ness, in all its eighty-seven shades of meaning. (And by “not clear” I mean I saw a guy with a Ph.D. who’s been programming C++ for years get const-ness wrong.) So a C++ compiler will bitch at you for mumbling “const” with the improper intonation, or for not doing template specializations in the new way that was approved by the standards committee just this past week (didn’t you get that memo?) and vomit the cascading ramifications of your mistake all over your terminal, but might not tell you what really needs fixing.
Whereas modern Ada tools are schoolmarmish about type-correctness but a) the type system has few, if any, hidden surprises and b) the compiler actively tries to help you bring your code into conformance. So in cases where C++ may merely say “undefined symbol”, GNAT might say “Couldn’t find a procedure Frob() in this namespace, but it looks like you were trying to use the one from Com.Yoyodyne.Widgets, so if you just USE that package you’ll be all set.” Or, “I didn’t find a variable called Teh_Awesome but it looks like you misspelled The_Awesome, declared on line 73.” Not in so many words, of course. It’s almost like as if Clippy the paperclip were unobtrusive and useful.
Which is one of quite a few reasons why I suggest considering Ada before reaching for C++.
Dan: Your last name wouldn’t be Rather, would it? That interview is how you do “fake but accurate”.
It would be funny if the result of all this progress is that it could now tansported on a 51/2 floppy disk.
http://en.wikipedia.org/wiki/Floppy_disk#5+1.E2.81.844-inch_floppy_disk
Timothy, you don’t need to explain 5-1/2 inch floppies to this crowd…or 8-inch floppies, for that matter. Some of us even know how to figure the capacity of an 800 BPI magnetic tape given the physical block size.
I must admit, I’ve never been swayed by the endless pissing contests over which language is teh aw3s0m3 l33t h4x0r c0d3z. They’re just tools in the toolbox. A skilled craftsman knows how to best decide which tool[s] are right for the job.
The least dusty set of tools I use are (in no particular order) – Ada, Java, C/C++, Python, Bash & Perl.
Love ’em all.
Dan: “A skilled craftsman knows how to best decide which tool[s] are right for the job.”
Yes, and then often gets stuck dealing with other folks’ bad choices.
My weapons of choice these days are Python and C, with bash, Perl, Applescript, and LSL (the Second Life scripting language) filling niches.
Jay said: If you haven’t come back to your own code five years later and asked “why the fsck did I do that?!”, then you haven’t programmed enough.
Even better, I’ve gone to code I wrote – to fix a bug – and asked myself “how did that ever work?” and then rewrote it in a sane, sensible way.
(That said, I’m going to be contrarian and say that I haven’t touched C since my CS classes and would rather chew on glass than go back to un-managed code, let alone non-OO code.
But then I don’t do device drivers and kernel code.
[I mean, seriously. Pointers and memory allocation? That’s the computer’s problem. It’s good at that. The monkey shouldn’t have to worry about it.
It’s 2012, for God’s sake. Even my phone has cycles to spare to handle that for me, and the stability and security wins are enormous.])
Yes, and then often gets stuck dealing with other folks’ bad choices.
A-frickin-men, brother….amen.
When the monkey stops worrying is precisely when the monkey should be worried ;)
>On the other hand, it’s a little disconcerting to think that I might not have learned anything about practical C in two decades.
Isn’t that the point about C? That there really isn’t that much to learn about it.
> A skilled craftsman knows how to best decide which tool[s] are right for the job.
All software sucks; some suck more than others. I have trouble imagining a case where C++ can’t be adequately replaced with C and Lua, or where Python alone isn’t more than good enough.
You can learn all its ways in a month, and yet after a hundred years it will still surprise you.
Oh sure, they say there isn’t much to learn about C, but they’re not thinking about the fact that there needed to be POSIX C, then ANSI C, then… C is like English. Sure, the basics are graspable in a day, and with a course and some diligence and smarts, you can understand pointers (which isn’t really C’s fault), and then you’re off and working with it – and a decade later, you’ll *still* run into an odd idiom some clever girl decided to use here or there that you don’t understand.
After all, it’s not for nothing that Obfuscated C is a thing which exists. …Well, to be fair, that’s intentional, and it’s poking fun at the sins, and everyone understands that if one writes C that way, that the punishment has fit the crime… but that still illustrates that the sins exist.
There’s wisdom (and love, heaven help us) in the old saying: “C combines the flexibility and power of assembly language with the user-friendliness of assembly language.”
> and with a course and some diligence and smarts, you can understand
> pointers (which isn’t really C’s fault)
Nonsense. Pointers are simplicity itself. It’s the vagaries of some of C’s pointer syntax that requires study.
Remember this old chestnut?
http://www.savageresearch.com/humor/selLanguage.html
(I was writing large C programs on Unix a couple years before esr discovered Unix.)
> Pointers are simplicity itself. It’s the vagaries of some of C’s pointer syntax that requires study.
Pointers are a tool. A very sharp tool. The tool is properly termed “indirection”, btw.
Pointers can cause a lot of trouble for large software projects, especially large software projects where the team has many less-than-expert programmers.
Which is why Java took them out.
The mere fact that large parts (e.g. GTK) of a modern Linux system are written in C++ should be enough to cause many here significant pause. Open Source was supposed to weed-out the mediocre code, (enabling “This sucks, let’s re-write it.”), and in at least this specific instance, has failed to do so.
@Jay Maynard
Jay, thanks for that. I agree, of course, that we want to make sure that code is as clear and easy to understand as possible. I am definitely not one of those people who likes to show off by making code hard to read.
The thing is that—to me—something like:
seems at least as clear as:
and has the added benefit of having quasi-English semantic content inherent in the way it sounds if read out.
Maybe I am just weird. Maybe my brain is wired up wrong. I don’t know.
When I am designing a program, I like to have the code be as close to the English spec for that program as possible. I choose names for my variables and functions in such a way as to make them almost (where possible) spell out English sentences in code that represent what they do.
I don’t regard that as obfuscation. In fact I think it adds clarity.
To me, “if (! found)” is the height of clarity, because it has a one-to-one mapping to its English equivalent.
@Jay Maynard
Python and C are my first choices too. Python if possible, and C where either speed or low-level control is paramount.
Are you talking about the online virtual world Second Life? I dabbled with that briefly years ago, and to be honest it seemed like a extremely strange affair. I didn’t get it at all. Has it developed into something interesting?
You know, just because you didn’t find anything to improve in how the functional code gets its job done doesn’t mean that you “haven’t learned anything new about practical C in 20 years.” All it means is that you wrote it right in the first place. John M. Browning’s M1911 turned a hundred years old last year, and while a few details here and there have changed (we’ve added passive firing pin blocks, and my Para-Ordnance has a widened grip frame to accopmodate a double stack magazine, a,d I installed a full-length guide rod and an ambi safety), there’s not a single part in it that John would be puzzled by if you detail-stripped a modern 1911A1 clone and laid the parts out on a tray. (Oh, he might not recognize the firing pin block if you handed it to him in isolation, but he’d have it all figured out thirty seconds after he picked up the slide.)
Good design never stops being good. Newer designs may come along that do different things that weren’t thought of back then, but the underlying requirements of an absolutely reliable service pistol haven’t changed in a hundred years, and the algorithms to encode and decode a GIF image haven’t changed in twenty.
The initial design was good, the format hasn’t changed, and nobody’s come up with a completely radical new way to approach the problem, so the design’s still as good as it ever was. It’s that simple.
“Remember this old chestnut?”
http://www.thealmightyguru.com/Humor/Docs/ShootYourselfInTheFoot.html
But then, I remember another version of How-to-shoot-yourself-in-the-foot-in-C that went:
“You write and select your header files, then write your source files, then you write the makefile. Then you debug your makefile and fix up the compiler options you need. Finally, you compile and link your program. Then you run your program, and discover that you *have* shot yourself, but you can’t tell where….
Tom: Now imagine you’re a programmer coming up on that code fresh, never having seen it before, and trying to understand WTF’s happening. You look at
found=search()
, and then dive intosearch()
to see what it’s doing. You see thatsearch()
returns NULL on a not found condition. Then you go back to the calling statement, and seeif (!found)
…and sproing a boolean negation on a pointer? Huh?!At the very least, you’ve derailed the thought process. Perhaps you’ve educated the programmer, but more likely, you’ve confused him, at least temporarily, and at worst you’ve sent him off on a wild goose chase, thinking “this crap can’t have ever worked!”.
That’s why I say it’s not readable.
@Jay
Well, I respect your opinion. And I’m very interested to learn that for some people this pattern is obscure. To me, though, “if (!found)” immediately tells me that search() returns NULL on the not found condition. I just see that without even thinking. The person who designed this set it up in such a way that he could write “if (!found)” and have it make sense. For me, it means that the code is self-documenting. I am telling the reader what search() returns when it doesn’t find something without their even having to look inside the function!
I guess different people have different ideas of clarity. We should hardly be surprised.
CURLY BRACE ALIGNMENT FLAME-WAR FTW!!!
Tom: “For me, it means that the code is self-documenting.”
You have much more faith in programs and programmers than I do. Never, ever believe what you think the code does. The only authority as to what the code does is the code itself. I can’t begin to count the number of bugs I’ve found by going back to first principles and checking everything.
Strangely enough, I seem to recall seeing an insight about this somewhere…
@Jay
Okay, but in either case (“if (found == NULL)” and “if (!found)”) you’re going to have to look into the search() to understand fully what is going on. On that score they are no different. But at least with the “not found” version I get an immediate clue as to the intention of the programmer. I’m being told “this if block is going to deal with the case when we didn’t find anything”.
Tom: Yes, and that’s a valuable insight – but that’s all it is. To me, the conscious shift you have to make to go from an explicit NULL return in
search()
to a boolean test in the calling routine is enough to more than offset that.It’s better, IMAO, to explicitly document everything, and then be consistent with that even in little details. You did comment that
search()
returns NULL on not found, right? Then explicitly testing for the documented return value has a clarity all its own.And yes, this is definitely a difference in style, not really substance. I prefer never to assume anything, because I’ve been bitten on the ass far too many times by programmers who thought they were fast and turned out to be only half right.
“To me, though, “if (!found)” immediately tells me that search() returns NULL on the not found condition.”
To me, “found = search()” tells me that search() returns true on the found condition.
I only use the NOT operator (!) with boolean variables or expressions. I never use it with pointers. OTOH I would never call a pointer variable ‘found” either. If it’s a pointer to some object or structure or string then it would have a prefix of ‘p’ (I like variable prefixes) and a more meaningful name.
Personally, I think Bjarne Stroustrup should be shot, drawn, quartered, tied to four horses and dismembered, soaked in molar hydrochloric acid, and then the leftover bits jumped up and down on by the Houston Texans in full uniform for inflicting C++ on the world.
Oh, c’mon Jay. Don’t mince words or hold back. We’re all adults here.
Tell us how you really feel. I’m sure that somewhere in there, having live rats put into his intestines to gnaw their way out while he’s still alive is part of your manifesto…preferably as an object lesson for programmer reliance on excessive abstraction.
Naw, Ken. I wouldn’t do that to Kyle Field (or else the Corps of Cadets would haunt me through a thousand lifetimes), and I wouldn’t do that to poor innocent rats.
> To me, “found = search()” tells me that search() returns true on the found condition.
To me, “found = search()” tells me that found could be a boolean, or a pointer, or a count of matches. “if ( ! found )” does NOT help me determine which is the case, because it adds ambiguity. Could be FALSE, could be 0 (matches), could be a NULL pointer. If search() really returns a pointer, then check for the damn pointer, not for 0.
> To me, “found = search()” tells me that found could be a boolean, or a pointer, or a count of matches.
Or it could be a coded bitfield status structure (a la microsoft’s HRESULT). But calling it “found” implies that out of all those things it should be a boolean – the other things should have different names.
> Or it could be a coded bitfield status structure (a la microsoft’s HRESULT). But calling it
> “found” implies that out of all those things it should be a boolean – the other things should
> have different names.
In that case (noted of course that this IS an isolated phrase of code so we can’t make too many assumptions) why return to a var at all and not just do “if( ! search() )” ? That removes almost all ambiguity, and then if it’s not a boolean, you’d want to check equality instead. I think we’re in agreement, though. a.) don’t be confusing with var names b.) check what you’re actually returning, not what you assume the system will represent it as
“At first I read that as ‘gif2espn’, and thought ‘Yes, that is amusing. What does ESPN have to do with GIFs?’.”
What in the Chyron are you talking about??
#include hamlet.h
question = 2b OR !2b;
if(suffer(slings,arrows) > die(sleep,dream)){nobler}
endif
“Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.”
—
Brian Kernighan
Hi,
Jay Maynard wrote:
> the problem is that examples such as yours are much less common than
> other reasons to compare pointers to NULL.
No they’re not. The common reason to test a pointer for truthness is to check it’s valid; something has been found, allocated, etc. Show me these more common reasons of yours.
> Wordiness for the readers to grok is a good thing, because it fosters
> understanding by someone who hasn’t touched the code in a while, be it
> the original author or another.
Wordiness is not a good thing when it is noise not signal. Far more important is good choice of names, including length proportionate to scope, and overall structure. (p == NULL) is akin to writing (b == TRUE) or &a[0]. A C programmer reading it would think it was written by an amateur. And don’t bring the strawman `obfuscation’ into this. Many current-day open source C code written on projects scoured over for quality do not shun (!p) when p is a pointer.
Jeff Read wrote:
> Ugly wart on Python: Python is designed to encourage this sort of
> thing. 0, None, the empty list, the empty string, the empty tuple, and
> the empty dictionary are all considered false in conditionals, leading
> to this sort of idiom becoming commonplace
Python does this by design. It’s not a wart but recognition of one of C’s many beauties leading to clearer more readable code. It’s much lauded and copied. Can the majority of the world be wrong?
LS wrote:
> I would hazard a guess that those who prefer ‘if(p)’ fall into two categories:
> 1. Those who believe that C programming is a game that rewards the fewest keystrokes.
> 2. Those who get tired of chasing bugs caused by typing ‘if (p=NULL)’ and don’t want to use the Yoda form.
I don’t believe fewer keystrokes matter. Yoda is repugnant. I think the reader is most important, that’s why (!p) is to be preferred. It often is the unusual `error’ condition, we couldn’t open that file, etc., and comes immediately after the assignment to p. The reader who can put C on his CV and pass an interview has to be immediately familiar with its meaning.
Jay Maynard wrote:
> Tom: Because it is an implied comparison, rather than an explicit one.
The comparison isn’t implied. You fail to see that C has 0 be false and everything else true in a Boolean context. This reminds me of the well-regarded C FAQ that if one writes (b != FALSE) why does one stop there and not write (((b != FALSE) != FALSE) …). http://c-faq.com/bool/bool2.html
> This matters when someone who’s not the l33t h4x0r who wrote the code
> has to come along and maintain it; he may not be as intimately
> familiar with the guts of the language, and may interpret the code
> incorrectly when trying to fix an unrelated bug.
“intimately familiar with the guts of the language”. A nodding acquaintance with C would have the reader grok (!p).
Random832 wrote:
> What’s unnatural is calling the pointer “found” instead of something
> like “result” in the first place.
Very wrong. Every non-void function returns a “result”; the name means nothing. Whenever I see that I think the coder has been too lazy to think up a better name.
Tom wrote:
> Maybe I am just weird. Maybe my brain is wired up wrong. I don’t know.
No Tom, it’s not you, it’s them. :-)
Jay Maynard wrote:
> Tom: Now imagine you’re a programmer coming up on that code fresh,
> never having seen it before, and trying to understand WTF’s happening.
> You look at found=search(), and then dive into search() to see what
> it’s doing. You see that search() returns NULL on a not found
> condition. Then you go back to the calling statement, and see if
> (!found)…and sproing a boolean negation on a pointer? Huh?!
You go back to the calling statement and think “if not found”. I think you ranted early on here and are struggling to defend your position. What C programmer is going to struggle over testing a pointer for validity?
> At the very least, you’ve derailed the thought process. Perhaps you’ve
> educated the programmer, but more likely, you’ve confused him, at
> least temporarily, and at worst you’ve sent him off on a wild goose
> chase, thinking “this crap can’t have ever worked!”.
The reader that thinks that should not be let loose nearly any code in any language; they’re incompetent.
Tom wrote:
> To me, though, “if (!found)” immediately tells me that search()
> returns NULL on the not found condition. I just see that without even
> thinking.
Quite.
> The person who designed this set it up in such a way that he could
> write “if (!found)” and have it make sense.
That was me, and I did. A style I learnt from the greats, including many at Bell Labs.
> For me, it means that the code is self-documenting. I am telling the
> reader what search() returns when it doesn’t find something without
> their even having to look inside the function!
Right. After the “if (!found)” has recovered or returned the code continues to do something with the non-NULL `found’ element. Not the `result’, or `pEntry’, so named because it’s a pointer.
I can only assume those that disagree first learnt a language where if, while, etc., mandated a binary Boolean comparison operator be used and have never managed to free their thinking from those shackles.
I won’t be replying further; I should have learnt by lesson after all those years on Usenet. :-)
> 0, None, the empty list, the empty string, the empty tuple, and
> the empty dictionary are all considered false in conditionals, leading
> to this sort of idiom becoming commonplace
That is indeed the beauty of the original work of Boole:
Realizing that logical FALSE behaves like numerical 0 when AND == * and OR equals +.
Set theory added that the empty set behaves both like 0/1 (depending on the operation, eg, 0! = 1) and FALSE.
Nice story, Eric!
You brought this crusty old-time C-coder to take his own architectural source journey, after which a return to my language love-of-the-day (lua: “if val or init_val”) positively tickles.
My pet peeve in the language wars, is the fact of existence of the git “global.autocrlf” preference. wtf, 21st century, can we please do away with this problem by not spreading it all over the place .. :)
@Ralph Corderoy:
The reason I consider
p == NULL
preferable to!p
is that I think type-punning is evil. I expect ! to operate on booleans, and a boolean is a logically different thing from a pointer; in a well-designed type-safe language, it wouldn’t be possible to confuse the two. Let’s consider your other two examples through that lens.b == TRUE
: This really is just pointless verbosity, because I expect theif
condition to be a boolean, andb
is already a boolean. It’s also a potential bug, if it’s possible forb
to take values other than 0 or 1.&a[0]
: I don’t think I’ve ever written this, but I can imagine myself doing so. Ifa
is an array ofint
s, and I’m passing it to something that’s expecting a pointer to a scalarint
, then writing&a[0]
rather than justa
is a clearer way of communicating my understanding of that expectation.>The reason I consider p == NULL preferable to !p is that I think type-punning is evil.
I agree 100% with this and the rest of Daniel’s comment. And this bears directly on why I was able in 1989-1990 to write code that would remain rock-stable and the nearest thing to bugless for 22 years.
One of the things I figured out very early in my life as a C programmer (around 1983-1984) is that even though C is emphatically not a type-safe language, it is best to write C code as if you were obeying the restrictions of a type-safe compiler. This sort of hygiene produces significant gains in portability and long-term maintainability.
I wouldn’t write “if (!p)” for p a pointer because ! properly operates on boolean-valued expressions and p is not a boolean; “if (p == NULL)” is preferable because the NULL macro is conventionally a pointer value (even though the type system doesn’t enforce this) and that expression is a reminder that p is in fact a pointer.
One of the reasons the giflib code looks surprisingly clean and modern for its age is that my practice at the time anticipated the evolutionary direction of later C programming practice and the language itself – that is, in the direction of greater type-safety and more type-safety enforcement by the compiler.
I was well ahead of the curve on this. Which is why, when I dusted off the giflib code, the largest single change was retrofitting it to use C99 bools. If I had had real booleans in 1989 I would absolutely have used them, and I would have used them as though they were a distinct type in a type-safe language.
I say this even though I’m not a fan of languages with “strong” restrictive typing; I consider involuntary type safety a straitjacket, but voluntary type safety a valuable discipline which I tend to exercise even in fully polymorphic languages like Python.
I was not, in the 1980s, a well-known enough programmer to publicly lead the move away from type punning. But I was paying careful attention to the people who were advocating it – Henry Spencer for one – and I absolutely agreed with both the detail and the philosophy of their arguments. It’s not just that type hygiene is good for portability in C, it also functions as implicit documentation of the program’s invariants and improves long-term readability and maintainability.
This is why I said in my first post in this subthread that I was disconcerted to learn of people doing anything else than writing “if (p == NULL)”. Because when I see “if (!p)” that tells me that the person who wrote it has not grokked the importance of voluntary type hygiene in C. That’s a lesson we should have all learned long since, and I’m not inclined to trust the code of anyone who hasn’t gotten the message.
Great. But isomorphisms between types don’t mean things of different types should be indistinguishable. And also, lists and strings aren’t sets.
Python’s way of doing it doesn’t let you use the old shortcut of “if a” to mean “if a has a meaningful value”. There are plenty of instances where the empty list or string might be meaningful. By contrast with Python (and Common Lisp), in Scheme the value #f is distinct from all these things, and is the only thing that counts as false in a conditional. This makes #f a handy little sentinel value to use, for instance, to distinguish an invalid result from a valid result that happens to be zero or empty. (The Gambit C FFI lets you pass #f into a C function that takes a parameter of pointer type to represent NULL.)
But even the Scheme way is a hack, and encourages a bad way of doing things. Good programming practice would treat booleans as distinct from other types, and the only things allowable in a conditional. What’s desirable are proper option types, and a compiler that forces you to handle both the null case and the case where there’s something there.
And don’t get me started about the Perl way. “0 but true”?
“Very wrong. Every non-void function returns a “result”; the name means nothing. Whenever I see that I think the coder has been too lazy to think up a better name.”
Except, the items returned by a search are called…search results. They don’t have a different name. They aren’t called “founds”. found is an adjective, which is why it should be a boolean. Something “is found” or “isn’t found”, it’s not “a found”. Maybe a better name could be chosen based on what kind of object is being searched for, but we don’t have that information.
I think anyone that wrote a couple of thousand lines of assembler was immediately impressed and overjoyed to be presented with C. Its syntactical brevity, structure, and flexibility made it the success it has become. And anyone that has had to pull old assembler out as well as pull out of C code for reuse realizes just how much those features of C have made the programmers and hackers world a far, far better place. I actually recently took old z80 assembler from twenty five years ago and retooled it for a new project. The task was onerous, arduous and painful. The same thing happened with C code also of similar vintage, the rework was simple, elegant and immensely fun. In my mind, that is the greatest testament to C.
@random
I like ‘found’ because it contains the things that have been found! If we didn’t find anything then we might set found to be 0, or NULL, or an empty list. In that case we can use ‘if not found:’. It makes perfect sense to me.
If it’s clarity and elegance you’re after, I can’t recommend VB.net highly enough.
Decades ago, I had a fairly skilled programmer working for me who was fascinated by the Obfuscated C contest and would embed such gems in his work product despite my repeated use of a cluebat. I think I finally got through when he had to spend a week fixing a bug he couldn’t find in his own crap.
Jay, why are you wimping out with hydrochloric acid when you could use the Happy Fun Liquid that is Chlorine Triflouride
Ralph; A better question would be: When hasn’t the majority of the world been wrong. (But I do prefer this way)
jsk said: To me, “found = search()” tells me that found could be a boolean, or a pointer, or a count of matches. “if ( ! found )” does NOT help me determine which is the case, because it adds ambiguity. Could be FALSE, could be 0 (matches), could be a NULL pointer. If search() really returns a pointer, then check for the damn pointer, not for 0.
Amen.
Of course, my heathenish but effective solution to this is use a strongly typed language, which will either not allow that ambiguity or require you to remove it with casts or proper type declaration in the first place*.
And ideally one I can step through in an interpreter in realtime for debugging.
(*Yes, this does require that the language restrict the ! operator or equivalent to only working on booleans. And that’s fine. It should, since what the hell does “not integer” or “not pointer”** mean.
** Then again, I don’t want to ever have to see or manipulate or check a pointer myself anyway. Again, heathenish.)
“I think I finally got through when he had to spend a week fixing a bug he couldn’t find in his own crap.”
…and then you assigned him to the APL project….
“Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.”
–
Brian Kernighan (via Winter)
I laughed for a while at this… but what if it doesn’t mean I’m not smart enough to debug it, but just too lazy (or scared of bug poison) to ignore preventative measures? (He could be right, since it took me two whole days to find the error in this line: “for(i = 8; i => 8; i++);”)
This is something I agree with Jessica Boxer on: Strong, static typing systems are an essential component of a modern programming language used to build serious large systems. A static compile-time type checker performs a series of baseline sanity checks that will catch out many bugs before the program is even run for the first time. It’s a basic principle of software engineering that the earlier bugs are detected, the cheaper they are to fix (in terms of money and total programmer time). Therefore, from a software engineering standpoint, static typing is the sensible choice.
I won’t say that Python, Ruby, or Lisp aren’t fun to program in, or that programs written in them are doomed to be buggy. But the designers of large systems in these languages probably spend more on testing than they would have for a strongly typed language like C#, Java, or Ada, just to catch out cases like trying to take the absolute value of a string or somesuch.
Lack of strong type-safety has disadvantages even for C: numerical and scientific code is written in Fortran because that language doesn’t have the type-punning and aliasing issues that C has. Accordingly, a Fortran compiler can optimize the hell out of numerical code in a way that no C compiler can match.
One of the things I like about Ada’s type system is that it is very strict, but is not crippled and circumscribed like those in early versions of Pascal[0]. You can have a lot of fun with it, as it vaguely resembles Standard ML’s, with generic packages of types and derived procedures that you can parameterize over other types and primitive procedures.
[0] When someone says “Lisp” they usually mean Common Lisp these days; similarly when one says “Pascal”, in 2012 they usually mean some descendant of Borland’s Turbo Pascal, whether it be Delphi or the FreePascal compiler. All of these are much more accommodating with their type systems and can be used for real-world applications.
“…and then you assigned him to the APL project….”
I think APL is a great language for a very specific purpose, namely, writing highly-customized, single-usage apps that make heavy use of matrix calclulations.
The ideal APL program is one written to answer a specific question. You write it, run it, make your decision/analysis, and then throw it away. You can probably write that program faster in APL than in a more typical language.
If you think you may need to do similar work on a regular basis, so that you might have to dust off the code in six months and use it again, choose another language. Because in six months, that program you just wrote will be just Greek to you.
I don’t think it’s any accident that it’s an interpreted language; compiled APL would be just ridiculous. It’s like having a workbench with a wide variety of tools scattered around making it easy to build one-off stuff. If you need an assembly line, even for a small-scale pilot plant, choose a more appropriate software development platform.
“Three things a man must do
Before his life is done:
Write two lines in APL
And make the buggers run.” — Stan Kelly-Bootle
Eric and Daniel are dead on target. As I said before, clarity beats efficiency, be it compiler, keystroke, or execution, every time.
And I still consider myself fluent in S/390 assembler, even though I haven’t used it professionally in getting on to a decade.
Tom: Yes, I mean Second Life. It’s interesting to me in that it’s a method of interacting with others over the Internet in real time that allows for more creativity than simpler methods such as IRC or Skype.
Jeff: I’ve written a real-world application in Delphi (one of the hairiest payroll systems you’ll ever meet, with special cases everywhere. Even its relaxed typing rules were still occasionally a pain in the ass.
“I think APL is a great language for a very specific purpose, namely, writing highly-customized, single-usage apps that make heavy use of matrix calclulations.”
@Cathy: Well, there were a lot of reasons that APL died (or, at least, went into a coma) but there may be hope for it in the days of the tablet. A great tablet app might be an APL ‘calculator’ for short programs. (Back in the day, APL one-liners were all the rage.) There would be enough room on the tablet display for one or two lines, with the rest of the space devoted to all those special APL keys. Typing using the tablet screen would not be a problem as APL programs used so few characters.
BTW – have you tried scilab? Free, does a lot of things, including matrix math from the command line. (No need for APL?)
“I wouldn’t write “if (!p)” for p a pointer because ! properly operates on boolean-valued expressions and p is not a boolean; “if (p == NULL)” is preferable because the NULL macro is conventionally a pointer value (even though the type system doesn’t enforce this) and that expression is a reminder that p is in fact a pointer.”
Well, perhaps. But one could argue that there is nothing wrong with positing “(!p)” as a test for equality with NULL (when applied to a pointer value). Even writing “if (p)” (thus coercing the pointer to bool) is not particularly damning IMHO; the null-pointer pattern is so pervasive that this particular coercion makes intuitive sense in this context,
Apart from that, I agree with Jeff Read and Jessica Boxer on “voluntary type-safety”. Type safety has a rather specific meaning in the context of programming languages; it implies that the compiler can _prove_ that the program won’t fail in some specific ways, barring the use of clearly denoted escape hatches (e.g. Obj.magic, unsafeCoerce). It may or may not be worthwhile to sacrifice some programmer convenience for the sake of these guarantees (for instance, consider the above pattern of using ptrs as boolean values), but “voluntary type-safety” seems to be a misnomer. I’d use something like “strict type adherence” for what you’re describing here since proper type safety guarantees are most likely not available in C, regardless of good programming style.
WOW, none of my code with anything approaching that age was as easy to update. So go you!
I love the “Toxic Hell Swamp” comment about Windows. Personally I love Windows as an OS for my daily use. But in terms of the Win32 API or it’s alleged POSIX subsystem I think your term is actually not harsh enough.
> Ada is infinitely preferable to C++ for new code.
I’ve got to disagree with that. C++ has come a long way in the last ten years – STL and Boost provide a lot of modern programming facilities.
Jeff Read wrote: “I won’t say that Python, Ruby, or Lisp aren’t fun to program in, or that programs written in them are doomed to be buggy. But the designers of large systems in these languages probably spend more on testing than they would have for a strongly typed language like C#, Java, or Ada, just to catch out cases like trying to take the absolute value of a string or somesuch.”
The flip side of that for me is that after getting used to e.g. perl’s flexibility, it can be really frustrating to have to deal with scanf and friends in a system like say matlab. Some days I just want to read in a bunch of numbers from a file, and having the language whine at me about what’s a string or an int or a float is just getting in the way of me handling whatever problem I’m trying to solve. “Gah, I just want to while () {blah} for crying out loud!”
Jay Maynard wrote: “As I said before, clarity beats efficiency, be it compiler, keystroke, or execution, every time.”
A sound principle that I agree with. But taking a standard idiom of a given language and calling it unclear for whatever reason strikes me as a bit odd. If I were doing the “search / found” example mentioned above in perl, I’d almost certainly write something like
@found = search( @stuff_being_searched )
if (!@found) { blah }
Doing anything _else_ (at least in a language like this, don’t know python as well as I’d like yet but from the examples above it seems to have the same idiom) would be unclear in my opinion. (I’d likely be “confused, at least temporarily” why the heck they didn’t do it the “standard” way, and worst-case end up on a wild goose chase to find out whether anything else was going on.) Granted “but everyone does it that way” would be a weak argument by itself; I think the test is whether the idiom is unambiguous in terms of the fundamentals of the language itself. One can argue whether an implicit cast from a list to a bool is a bad thing or not, but if the language explicitly defines the cast to be “a list casts to false if emtpy (or undefined), otherwise to true” then it’s at least unambiguous. If it’s both unambiguous _and_ a widely used idiom then that ought to be sufficient for clarity.
Sorry, forgot to try to protect the angle brackets: “Gah, I just want to [sourcecode]while () {blah}[/sourcecode] for crying out loud!”
prolog: I’d stake the perpetrators of Boost out on the field next to Stroustrup while pouring acid over their mangled, bleeding bodies. It’s a dump truck full of goose exhaust troweled on top of the horse exhaust troweled on top of C. It’s next to impossible to build for other than the default compiler options, and its unique build system is even more overengineered and opaque than cmake – itself an example of the worst of German overengineering. I spent two days not long ago trying to build it on OS X 10.6 to work on 10.5, a simple task unless the build chain gets in the way. Then, suddenly, it Just Worked. This should terrify any experienced programmer.
That C++ needs this additional goose exhaust to be considered usable is as damning an indictment of the language as any anyone can make.
Mainly by copying Ada’s features — poorly.
The language semantics were tortured pretty hard to get those features in there. Which means that — yeah, you can program in functional style and with generics — but it looks like ass, is very difficult to read or maintain, and your compile times are likely to blow up.
@darrin
Your perl version of the found=search() phrase isn’t equivalent, because @found tells the reader immediately that search() is expected to return a list. at that point, however you want to check it in the if statement you won’t be risking the same down-the-line ambiguity you’d get in e.g. C.
I know I’m strange. I used to enjoy the pc-lint advertisements in Dr. Dobb’s journal. I understand the difference between
SomeClass::SomeClass ( const SomeClass & rhs )
and
SomeClass::SomeClass ( SomeClass & rhs )
hint: One is a copy constructor. The other is a constructor, but the compiler won’t use it.
C and C++ are very sharp knives. If you have your totin’ chip, you know how to use them safely, and a sharp knife is safer than a dull one.
Most programmers should have their totin’ chips taken away from them and be restricted to plastic utensils and playdoh.
I had a similar discussion with an IT manager just the other day, primarily was discussing the evolution from C to C++ to C#, which all have similar issues of platform dependancy; when I was programming “C”, we had the statement that “C is it’s own virus”. All it took was for someone to update the OS (installing a patch from MS, for example), and the code broke, and sometimes needed to be re-written…all of the C derivitives are far to complex to be considered (IMHO) as a mainstream development language…
C is a sharp knife. C++ is a Rube Goldberg parody of a Swisschamp that’s had so many sharpening stones applied to it over the years by so many different people that the sharp bits are often the bits you need to grab hold of to unfold the parts you want to use.
Is your Tron hat getting a bit too tight, Jay?
Take it off, have a scotch, and let the C++ hatin’ fever subside ;)
The imagery you use to convey your bile is giving me a headache…
Bunch of Newbies!
Go back to a real language, FORTRAN (or for newbies, Fortran), to see the stability of a great language. We still maintain Fortran from the early/mid-70’s and it still does the job it was intended to accomplish!
“A great tablet app might be an APL ‘calculator’ for short programs. (Back in the day, APL one-liners were all the rage.) ”
Well said. APL was always more of a calculator-on-steriods than a real programming language. That’s a pretty small niche, which probably accounts for its comatose state. Certainly I haven’t touched it in 20+ years, nor have I missed it.
C’s past is indeed long, but this comment thread is longer.
…
Well, some folks on the Internet put their faith in C++.
They swear that it’s so powerful, it’s what God used for us.
And maybe it lets mortals dredge their objects from the C.
But I think that explains why only God can make a tree.
…
Julia Eclar, “God Wrote in Lisp”, Roundworm
Lyrics by Bob Kanefsky
God apparently didn’t care about performance or memory constraints…
@Jay Maynard:
At last, something that we agree on – the abomination that is Boost.
@bwana
“Go back to a real language, FORTRAN (or for newbies, Fortran), to see the stability of a great language.”
Yeah, this was my first computer language. I coded in it for 15 years. And I hated it from start to finish.
From the 4 character variable names and punch card code layout long after all punch cards had died to the clever ways you could mess up your program with global commons blocks (shudder) where each function would align their variables differently in the same stretch of global memory.
I have removed FORTRAN experience from my resume after some employer took them seriously.
Nice article..I want to do many language.. I m handling with C++.
so hard..
The only thing Boost actually boosts is compilation times, amirite?
To be fair, it’s evolved a lot, and undergone multiple standards revisions since the days of “4 character variable names” (I thought it was 6?). Fortran today is free-form, modular, and even object-oriented.
@Jeff Read:
“Fortran today is … even object-oriented”
I’ve never looked at Fortran’s OO features myself, but just wondering: is the result of bolting object-orientation onto an ancient language (Fortran) any better or any worse than bolting it onto a just slightly less ancient language (C)?
I don’t like C++ either, but I have to admit that there are some features of the language that are very useful. One example is function templates. They are really powerful, and remove the need for a lot of the hideously obfuscated C macros that you see in even the best C code.
C++ creates the potential for abuse, and it is an unwieldy morass at times, but if used carefully some features can be useful.
@Jeff Read
I remember having used an ancuent version with 4 character names. But I must admit I mostly worked with 6 and up.
And some scars never stop hurting. I stayed away from the newer OO etc versions.
How to spot a C++ fan boy: they use the phrase “it can be used safely”, or some such, with a straight face.
How to spot an iPhone fan boy: they use the phrase “user experience” as the universal argument in favor.
Tom,
Ada generics are safer, less confusing, and more flexible than C++ templates, and they were designed for separate compilation so they don’t blow up your code size or compilation time. Ada supports all instances of a generic package sharing the same code, even, so for any given generic package only one copy of the code exists, no matter how many times you instantiate it. (It can also generate multiple copies if you have inlined code in your package.) The primary drawback of Ada generics is they’re verbose.
C++ templates are macros with some type-checking gravy on top that has a tendency to produce long and baffling error messages when a type check fails in a template. They always generate a new instantiation per permutation of template parameters, per compilation unit. (Some clever compilers and linkers know how to find duplicate template instantiations across compilation units, and remove them; but that’s just the point, you don’t need cleverness in Ada, and you can’t rely on it being there in C++.) You also cannot separate interface of a template class from implementation without complex workarounds like pimples, in which case you’re back to writing verbose code. At least the Ada version would’ve been somewhat easier to read and follow.
Hi all.
I didn’t read all – sorry.
But want to tell something about the discussion “if (p), if (!p), if (p==NULL)”…
In my opinion, all these are simply wrong.
My way in defensive programming is:
if ( NULL == pDetailedDescription ) {
}
The difference to
if ( pDetailedDescription == NULL ) {
}
should be clear.
Consider the million lines of code you write.
If you ever forget one simple “=”, the first if will fail in compile time, the second wont…
Regards.
“if ( NULL == pDetailedDescription ) { }”
That’s known as the ‘Yoda’ form of the conditional. It’s universally rejected as one of those things that works, but is ugly to see and read.
LS,
Nevertheless “Yoda conditionals” are correct because, due to an ugly wart on C and friends, the risk of NOT using them is too great, aesthetics be damned.
Blame ken and dmr, who made assignments value-returning expressions and chose the single ‘=’ for assignment over equality.
“You are old, said the youth, and your programs don’t run,
And there isn’t one language you like;
Yet of useful suggestions for help you have none —
Have you thought about taking a hike?
“Since I never write programs, his father replied,
Every language looks equally bad;
Yet the people keep paying to read all my books
And don’t realize they’ve been had.
@LS
“Universally”
Really?
Speaking of history, Rob Pike posted an awesome and killer old Unix quiz on his G+ page.
https://plus.google.com/101960720994009339267/posts/SnEe2UykcH1
A real boon to students of Unix history, and fun as well. I know more of them than I feel like I should…
As an alternative to the Yoda conditionals: if using GCC, -Wall will warn about an assignment used there. Warnings are useful.
As a definitive fix, though, one of my unlikely-to-be-realised ambitions is to be appointed to an influential position on the C standards committee and then to rule invalid any if/while where the expression is not a boolean value or an operator returning that. Ok, so it will break millions of lines of existing code, but it should never have been allowed in the first place…
> to rule invalid any if/while where the expression is not a boolean value or an
> operator returning that
In C, assignments return values. (i = 1) == 1. You’d have to also have that functionality stripped, which WOULD break things.
@James M
I think most modern compilers will issue that warning. Most programmers ignore warnings.
If you break existing code people will refuse to upgrade to the new version of the compiler. You will kill the compiler vendor’s business. Vendors, including open source, will offer a way to disable this new feature or see their products dropped from use.
Perhaps this is a feature to you, instead of a bug.
“Universally” Really?
@Bob: Well…you know the old saying, “Never make a general statement.”
@jsk: I know 10 of those quiz answers. Probably not bad for someone who wasn’t born when it was written :-).
@Winter
Well, Fortran 90 and onwards isn’t your grandfather’s Fortran 77 (or even FORTRAN IV).
BTW. I much prefer intrinsic module iso_c_binding (available since Fortran 2003, which means that modern GCC supports it) as a way of interfacing with C libraries rather than writing wrappers in C and trying to mangle name the same way Fortran compiler does it (usually by the way of fortran.h header taken from somewhere).
@LS
And because modern compilers warn about assignment in a conditional… which is sometimes deliberate, and can be silenced by writing extra parentheses, or just
while ((p=nextline() != NULL) {
Eric: If you want stable code, look at John McCarthy’s 1959 theorem prover. This Lisp code was running when you and I were in diapers, and it runs again in Scheme with just a few question marks added to the names of standard predicates. This code is so old that false and () are not yet the same thing!
And it still passes its (one and only) test case.
Offtopic: a possible subject for blogging bt mr. Raymond at http://www.phpclasses.org/blog/post/182-Making-the-Web-Faster-with-HTTP-2-Protocol.html
I found the outpouring of support for NULL over zero surprising, but I’ve been a pro C++ programmer since the 90’s.
C++ is awful, but it gets some things right, such as discouraging the use of macros including NULL. In C++ you use zero or, in C++11, nullptr.
Also slightly surprising to me: there are architectures where a null pointer isn’t zero? Why oh why?
I wonder, do people who don’t get multiple inheritance also not get reality? (A dog IS A mammal and also IS A pet and also IS A three-dimensional shape etc. etc.)
http://www2.research.att.com/~bs/bs_faq2.html#null
So if you are coding C++ I say avoid NULL because the convention is to use zero. However, if you are coding C where there is no relevant convention, in addition to all the benefits discussed above for using NULL, if you use NULL you also get to be doing the opposite of what Stroustrup wants you to do.
“I wonder, do people who don’t get multiple inheritance also not get reality? (A dog IS A mammal and also IS A pet and also IS A three-dimensional shape etc. etc.)”
That’s not a good example of multiple inheritance. A dog IS AN animal–>mammal–>dog is an OK inheritance chain, but a dog HAS A shape. A pet probably has properties independent of the animal (like its owner) and should have an animal aggregated inside it. It’s really hard to find an example of multiple inheritance that wouldn’t be better implemented some other way.
Nonsense. For a different example, a graphic Shape class might be a Serializable as well as a Rotatable and even maybe a Colored. Sure some of those (IS A) features might also be implementable as attributes (HAS A), but that’s an arbitrary design choice that has more to do with taste and with the software environment you happen to be working in.