I mean the lesson I'd take from that is "don't use a buggy compiler". If you can't trust your tools then it's very hard to make something you can trust with them. That changing the code means it compiled in an incorrect way that your test case happened to hit doesn't mean the previous version of the code was being compiled correctly, or was correct code.
The lesson I take from this is to steer well clear of historic languages with the many intricacies and interdependencies described in the blog post. I assume that there is a reason why that old buggy compiler version is still in use - probably some legacy library or code that only works with that version of the compiler and that nobody has touched for 20-30 years and nobody wants to touch?
Fortran 66 was the pinnacle as a no-nonsense practical programming language. No semicolon cancer, no fancy brackets, just code that looks like code. Beautiful (in a nerdy kind of way, by today’s standards).
What about assigned goto? hollerith?equivalence? Please look at the following:
C THE CALLEE
SUBROUTINE X(I)
GO TO I
END
C THE CALLER
ASSIGN 10 TO J
CALL X(J)
C THIS IS NEVER REACHED
STOP
10 WRITE(3,100)
100 FORMAT(8H THIS IS)
END
Why would this work? Because recursive calls are not allowed, There should NOT be a stack. So allow rather arbitrary jumped around (setjmp/longjmp) is um... ok?!? And what happens if a normal integer is passed to that code? (and it is separately compiled). Yikes.
My favorite personal Fortran story: I was passing by a WAY SMARTER THAN I COULD EVER HOPE FOR guy who was pouring over a two inch Print-out of some scientific code. He had been trying to troubleshoot a problem. I guess a comma was in the 80th column, which I recall presents an error. In my passing by, I pointed out the error (one second) and I always had his respect after that.
Hah. Years back a person from a forum from my college (this is just barely pre-Facebook) who absolutely hated my guts sheepishly asked for my help to look over some job related code of his that was giving him errors. He came to me because everyone said "I was good with web stuff".
Within about 5 seconds I spotted he had forgotten a semi-colon from the end of an escape character and it was causing him all hell with rendering. I pointed it out right away but still looked over the rest to find errors and didn't find anything. He had a hard time believing it but trusted me enough to try it and alas I was correct.
A far better C programmer than I came by once sure he'd found a compiler bug. He had spent all day wondering why his loop only executed once:
for (i = 0; i < 10; ++i);
func();
In one second I pointed out the extra ;. This does not remotely mean he was a bad programmer and I was a great one. It's just exactly the kind of thing where we see what we expect to see, not what is there. I make misstakes like that, too.
But this experience is why D does not allow a solitary ; to represent an empty line. Modern C compilers spit out a warning for it, but geez, just make it an error already and eliminate the scourge of those bugs. I've seen other variations like:
In high school (1996?) I was teaching myself C to write a small game and I had forgotten to add "break" at the end of each case within a switch statement. I was developing by myself (not quite in my mother's basement, but close enough) before I had access to the internet or stack overflow. It took me months to figure out the error. When I finally figured it out I was so embarrassed.
This seems to be a common pedagogical problem in expositions of C — so few of them point out that no, damn it, `switch` is not a multi-arm `if`, it’s a computed `goto` with lipstick on it (as opposed to an actual computed GO TO like the one in Fortran).
Many compilers allow for emitting the original source interleaved with the assembly output. It is a great way to understand the code, as well as stepping through in a debugger.
With small scale computation (single process, single node), we have control over the entire universe. We just have to realize it and then figure out how to introspect it.
@natnatenathan I am glad you stuck with it, but the cognitive flaw that is very hard to get over is broken partial information. We have to rollback (forget our knowledge) and then learn it again fresh which can be incredibly hard, we skip over things we "think" we already know.
So will objdump (or llvm-objdump) with -dS, for that matter (hint: -Mintel or, on older llvm-objdump versions, --x86-asm-syntax=intel), except the line numbers in both GCC and Clang’s debug info suck in optimized builds. (Not the fault of the debug info, arguably, it’s just that the optimizer rearranges the code to the extent that the notion of line numbers makes little sense.)
Hah, when I took a c class back in the 90s, my teacher showed us a job listing from the classified ads -- it was a similar code snippet to your first, with "See the problem? Call us."
That memory should make me feel old, I think. Calling a company about a programming job listed in the paper, whose challenge is now a bog-standard compiler warning? Those were the days
> D also does not allow the syntax `a < b < c`. C does, to the eternal damnation of anyone who tries it.
Far be it for me to teach the Pope to pray, but would it be reasonably reasonable to make that do the right thing? If your compiler can disallow it, it can detect it, so the grammar is obviously thinkable, and the semantics might be as well.
This is tricky, but it is at least a PEP 8 violation to compare a Boolean expression to True or False with ==, and a linter will complain. Programming in Python without a linter can be perilous in general.
The important thing is that it's unambiguous. One function is being called with three arguments. If you don't know what that function does, you look it up. No drama.
It's a really minor point, but this is one thing I like about Rust syntax for conditionals vs. C/C++. It always requires the {} around the block, so it has no special rule for single-statement conditionals like C. At the same time, always requiring the {} means that it can get rid of the () around the condition. So all in all, it's as short or shorter than the C style syntax, and clearer without special cases.
Looks like several newer languages adopted that idea - Go does it too (also getting rid of the round brackets for loops and conditions, as well as of semicolons at the end of lines), but I'm not sure who started it...
This is my problem as a lone amateur developer: I know they I made a mistake, I know they this is not a bug of the language, and I know the problem is obvious.
Yet I am left to myself and my cat to explain the problem. I usually managev to find it by explaining the problem to the cat, rubber duck style.
I spent a good 30 seconds wondering if "++i" had some side-effect compared to the more usual "i++", before I saw the real problem, and I write C all day.
Yes. This is why linters are invaluable (or indeed languages that are stricter with syntax). Programming is so much easier now that we have this kind of tooling.
I remember trying to write my first programs in MS Notepad (which doesn't even have syntax highlighting) and then run them in Internet Explorer which would popup error messages one at a time in a dialog box. And a lot of time was spent finding and fixing trivial errors like this.
> Programming is so much easier now that we have this kind of tooling.
Are you an Ent? What you mean by "now" may not be what most other sentient beings understand here...
The program lint(1) was written in 1978, and the -W option for gcc appeared in the late eighties. For most of us, "this kind of tooling" has existed since before we were born. And I consider myself a graybeard!
Hah, I wish I was an Ent! But I wasn't even born until well after 1978.
However, specfically for JavaScript which is the language I was using when I first started programming it seems that the first linter wasn't released until 2011. At first there wasn't even `console.log`. I also think access to these tools has improved considerably. These days any introductory resource will point your towards VS Code which has a built-in marketplace which will install these tools for you at the click of a button (cross-platform!). That certainly wasn't the case even 10 years ago.
Probably compilers allow that kind of for loop because you can construct a valid for loop that does actual work inside what should be the condition logic. And sure, that's horrifying and awful and people shouldn't do it, but the spec says that it's valid so the compiler has to allow it.
A friend of mine was getting a masters in computer science where they teach you all of the cs in the first class he is coming to business school and have a while loop with a semicolon at the end he had spent six and a half hours trying to figure out why it didn't work. I spent 2 minutes reading the code couldn't figure it out open up and debugger and then obviously saw it when the doctor wouldn't go past a while. It's kind of like when I spent 7 hours figuring out on my computer wouldn't start and then we saw that it was set to 2:20 instead of 112
Hi Walter,
I've been learning to program in C, and I enjoy it. The problem is that while I wish to write C, I do want some additional conveniences afforded by other languages. I think the traditional way people go about this is using C++ kind of like a C+. There are also people who use Zig. I'm not a fan of Zig, and I don't like how C++ doesn't always behave like C even when you write in the C style.
How does BetterC stack up to Zig and C++ in this situation? Does it allow me to write plain C but with some of the conveniences of D?
BetterC includes all of the features of D except the ones that require the D runtime library, which means everything except exception handling and operator new and array concatenation.
You've still got full use of metaprogramming, compile time function execution, modules, Unicode, all the memory safety features, etc.
> Does it allow me to write plain C but with some of the conveniences of D?
I've had less success pointing out errors to people 'smarter' than me.
One was stuck learning C because they knew just enough about scope to be dangerous. They'd declared 'i' globally and used it in every subroutine for looping. Which failed confusingly when they called one looping function inside a loop in another.
No amount of insisting each function needed it's own 'i' would convince them. I left them to it.
In college, I was writing something in Fortran, and I made a typo. I was trying to call the cosine routine, COS, and I overshot the "oh" key, and typed C0S. It took me two days to figure out why Fortran didn't know what cosine was.
First, it was supposed to be a capital letter O, because Fortran. Second, this was about 1982, on a green-screen terminal with not a lot of resolution. And third, I was a naive 20-year-old without a lot of experience at what fat-fingering things can do, so I wasn't really looking for that.
Sure. All I mean is, since the time immemorial the zero, on computer peripherals, has been visually distinct from the letter O (which was done by crossing the zero or putting a dot inside it).
So, you stumble on some high-quality code, very-high-quality code, that has worked flawlessly for decades (and if you don't perceive it as flawless, then convince yourself it's your fault), and you dare mature an idea about modifying it?
I worked on a code (DOCK for docking drugs into molecules) that had a core routine (a preprocessing step) written in ancient FORTRAN by a genius professor. He couldn't explain why it worked, even though he had written the paper and the code. Everybody who looked at it gave up. Eventually somebody invented an all new algorithm for the preprocessing and wrote it in C++ (comprehensibly) and the old routine was retired, to everybody's satisfaction.
58 comments
[ 3.5 ms ] story [ 121 ms ] threadYou got me by a major release. My introduction to Fortran was Fortran 77 on an antiquated IBM mainframe (event at that time).
Loved it, even if I could never remember what column I needed to use :-).
What about assigned goto? hollerith?equivalence? Please look at the following:
C THE CALLEE
SUBROUTINE X(I)
GO TO I
END
C THE CALLER ASSIGN 10 TO J
CALL X(J)
C THIS IS NEVER REACHED STOP 10 WRITE(3,100)
100 FORMAT(8H THIS IS)
END
Why would this work? Because recursive calls are not allowed, There should NOT be a stack. So allow rather arbitrary jumped around (setjmp/longjmp) is um... ok?!? And what happens if a normal integer is passed to that code? (and it is separately compiled). Yikes.
Within about 5 seconds I spotted he had forgotten a semi-colon from the end of an escape character and it was causing him all hell with rendering. I pointed it out right away but still looked over the rest to find errors and didn't find anything. He had a hard time believing it but trusted me enough to try it and alas I was correct.
From then on he treated me much better.
But this experience is why D does not allow a solitary ; to represent an empty line. Modern C compilers spit out a warning for it, but geez, just make it an error already and eliminate the scourge of those bugs. I've seen other variations like:
With small scale computation (single process, single node), we have control over the entire universe. We just have to realize it and then figure out how to introspect it.
@natnatenathan I am glad you stuck with it, but the cognitive flaw that is very hard to get over is broken partial information. We have to rollback (forget our knowledge) and then learn it again fresh which can be incredibly hard, we skip over things we "think" we already know.
My object file disassembler will do that:
https://www.digitalmars.com/ctg/obj2asm.html
if the source code was compiled with debug info turned on. This feature has been around at least since the 80s.
I remember PhD papers addressing that in the 1980s. Nobody has yet figured out how to revert optimizer hamburger back into source meat.
That memory should make me feel old, I think. Calling a company about a programming job listed in the paper, whose challenge is now a bog-standard compiler warning? Those were the days
`null` has another meaning in D, that of a null pointer.
D also does not allow the syntax `a < b < c`. C does, to the eternal damnation of anyone who tries it.
Far be it for me to teach the Pope to pray, but would it be reasonably reasonable to make that do the right thing? If your compiler can disallow it, it can detect it, so the grammar is obviously thinkable, and the semantics might be as well.
It could also just be a type error: `<` takes two integers and produces a boolean, so both `(a < b) < c` and `a < (b < c)` would be invalid.
I did think about that, and it's a good question. I did not out of concern for some C code translated to D then mysteriously misbehaving.
Yet I am left to myself and my cat to explain the problem. I usually managev to find it by explaining the problem to the cat, rubber duck style.
I spent a good 30 seconds wondering if "++i" had some side-effect compared to the more usual "i++", before I saw the real problem, and I write C all day.
I remember trying to write my first programs in MS Notepad (which doesn't even have syntax highlighting) and then run them in Internet Explorer which would popup error messages one at a time in a dialog box. And a lot of time was spent finding and fixing trivial errors like this.
Are you an Ent? What you mean by "now" may not be what most other sentient beings understand here...
The program lint(1) was written in 1978, and the -W option for gcc appeared in the late eighties. For most of us, "this kind of tooling" has existed since before we were born. And I consider myself a graybeard!
However, specfically for JavaScript which is the language I was using when I first started programming it seems that the first linter wasn't released until 2011. At first there wasn't even `console.log`. I also think access to these tools has improved considerably. These days any introductory resource will point your towards VS Code which has a built-in marketplace which will install these tools for you at the click of a button (cross-platform!). That certainly wasn't the case even 10 years ago.
(I once told the Coverity folks that the mission of D was to put Coverity out of business!)
How does BetterC stack up to Zig and C++ in this situation? Does it allow me to write plain C but with some of the conveniences of D?
You've still got full use of metaprogramming, compile time function execution, modules, Unicode, all the memory safety features, etc.
> Does it allow me to write plain C but with some of the conveniences of D?
Heck yeah.
One was stuck learning C because they knew just enough about scope to be dangerous. They'd declared 'i' globally and used it in every subroutine for looping. Which failed confusingly when they called one looping function inside a loop in another.
No amount of insisting each function needed it's own 'i' would convince them. I left them to it.
Mayhem be on you!
Nice.