It wasn't until the 90s that it became clear that C was not appropriate for applications work (and this was in the era of 4-16MB machines), although that took a long time to sink in.
It wasn't until the 90s that it became clear that C was not appropriate for applications work (and this was in the era of 4-16MB machines), although that took a long time to sink in.
The statement "the alternative to C was assembly" is simply incorrect.
On the limited environment where C got created, it was the only option. And everybody suddenly adopted that limited environment, because it was cheap. And then it improved until you could use any of those other languages, but at that point everybody was using C already.
Still, that was a much more powerful machine than the ones people wrote C for. And when the cheap segment of those "fridge computers" became powerful enough to run whatever you wanted to put on it, people started using small workstations. And when those workstations became powerful enough, we got PCs.
It's only when the PCs got powerful enough that we could start to ignore the compiler demands and go with whatever language fit us better. (The later reductions all were based on cross-compiling, so they don't matter here.)
Let not say that a 1961 system is more powerful than a PDP-11.
https://www.cs.virginia.edu/~evans/cs655/readings/bwk-on-pas...
One of the major problems, as I recall, was that Wirth's basic Pascal definition was very limited---the max 256 character strings, for example. So the makers of "production" Pascal compilers added extensions to do all of the things that you would want to do and unfortunately those extensions were all incompatible.
Here are two, RatC, Small-C.
Additionally what do you think GCC C is, in regards to K&R C and ANSI C?
Several years later, a similar encounter with Mesa's evolution, Mesa/Cedar, gave birth to Oberon.
As for being ahead of its time, very much indeed.
Many people relate to Smalltalk and Interlisp-D, and are unaware of strong typed systems programming at Xerox, with a tooling that only decades cater came to be.
Mesa authors were quite adamant that in order to succeed, Mesa had to support comparable tooling to Smalltalk and Interlisp-D environments.
It's really dependent on which field you're in. Not all scientific computing requires a beefy computer, but for a very long time it (and I guess LISP) dominated scientific computing. That said, I think it's a very good point to bring up the network effect of using C - if I need to hire a developer in 1985, it's probably easier to find someone with industry (not academic) experience who knows C than it is to find someone who knows Fortran.
I do kinda prefer Fortran to C though, it's so much cleaner to do math in. Maybe somewhere there's an alternate universe where IBM invented C and Bell invented Fortran to win the popularity war.
The other mainstream language at the time was BASIC, comparable to the way PHP is viewed today by many.
And with the advent of the 32 bit x86 era GCC and djgpp as well as early Linux systems really unlocked a lot of power. Before then you'd have to have access to a VAX or a fancy workstation to get that kind of performance out of PC hardware. It's funny how long it took for the 386 to become fully operational, many years after it was launched you had to jump through all kinds of hoops to be able to use the system properly, whereas on a 68K based system costing a tiny fraction that was considered completely normal.
How did people see Fortran back then - nowadays it's seen as outdated but fast, but was it seen as interesting, and what drove you to seek it out?
Other side question if it's okay, I keep seeing references to VAXen around historical documents and usenet posts from the 80s and 90s, what made them special compared to other hardware you had back then?
The reason that library existed in FORTRAN was that it had a native 'vector' type and allowed for decent optimization on the proper hardware (ie: multiply and accumulate) which we did not have. But the library had been validated and was approved for civil engineering purposes, porting it over would have been a ton of work and would not have served any purpose, besides it would have required recertification.
As for VAXen: A VAX 11/780 is a 32 bit minicomputer, something the size of a large fridge (though later there were also smaller ones and today you could probably stick one on a business card). It had a - for the time - a relatively large amount of memory, and was a timesharing system, in other words, multiple people used the same computer via terminals.
They weren't special per se other than that a whole raft of programmers from those days cut their teeth on them either because they came across them in universities or because they worked with them professionally. They were 'affordable' in the sense that you did not need to be a multinational or a bank in order to buy one, think a few hundred thousand $ (which was still quite a fortune back then).
I had occasional access to one through the uni account of a friend, but never did any serious work with them. The first powerful machine I got my hands on was the Atari ST, which had a 68K chip in it and allowed the connection of a hard drive. Together those two things really boosted my capabilities, suddenly I had access to a whole 512K of RAM (later 1M) and some serious compute. Compared to a time shared VAX it was much better, though the VAX had more raw power.
Concurrent to that I programmed on mainframes for a bank for about a year or so, as well as on the BBC Micro computer (6502 based) and the Dragon 32 (a UK based Color Computer clone).
Fun times. Computing back then was both more and less accessible than it is today. More because the machines were so much simpler, less because you didn't have the internet at your disposal to look stuff up.
The 386 was hampered by backwards compatibility with the weird memory architecture for the 286 and 8086, IIRC. 68Ks were just soooo much easier.
I remember making a bootloader for my own OS using TurboC that somehow had to fish four files from a minix based filesystem and dump them in memory at certain addresses and then switch to flat mode and start executing the kernel which would then initialize the system properly. That was some really weird mixed mode voodoo to switch back and forth between protected mode and real mode to be able to both use BIOS calls to fetch blocks from disk and to be able to park the data in the right spots in contiguous memory.
Very tricky stuff to get right, it took me forever before I had the first indication that something was executing after the inevitable hail Mary jump to the first instruction in the loaded kernel. But I got it to work. I actually had a guitar footpedal hooked to the reset button because it took me too many dives under the desk to recover from a crash. That was a very slow development cycle without any chance of debugging. At some point I had 8 leds hooked up to the printer port to use some 'out' opcodes to tell me where I had gotten to in the code. Poor mans emulator :)
I remember meeting people who could not wrap their heads around the idea that function parameters weren't all passed by reference. The idea of recursion would have caused them to detonate.
So that left C (before cfront was introduced). And we ran with it, everybody around was using C, there wasn't the enormous choice in languages that you have today, you either programmed in C or you were working in assembler for serious and time-critical work.
Easily proven by looking into computing magazine archives.
So lets not distorce history.
As if the BBC Micro was an example of the industry adoption at large during those days.
Any Byte, Computer Shopper, DDJ, The C Users Journal (later The C/C++ Users Journal), Crash, Amiga Format, Input, PC Techniques, You Computer, Your Sinclair,... from 1980 - 1995 thereabouts, shows a different picture in article contents and ad sections, in terms of language adoption and available set of compilers being sold.
It's a bit like me saying that today Erlang is widely adopted. Yes it is, in some circles and I absolutely love it. But even though it has some adoption the big heavy lifters today are Python, C++, C and Java. Maybe C# on account of MS and probably Javascript should rate a mention. That doesn't mean that the other 2500 or so computer programming languages do not exist or do not have relevance. But it is a realistic reflection of market share. At the computer store where I occasionally worked you could see this reflected in the requests for boxed copies of compilers and interpreters for various languages.
FoxPRO + Delphi became a very powerful combination for SME administrative systems (and before that DBIII) and once Apple launched Objective-C that got some significant market share on their platform too. But any kind of commercially developed software outside of those niches was likely to be built on the list above.
I've taken a great interest in various programming languages and learned a lot of them to the point that I could write code in them to compare them, so I'm well aware all of these (and many more!) existed. The machines I worked with were: Dragon32, BBC Micro, Acorn Atom, Apple II, C64 (though, mostly from a hardware point of view, to fix them), Atari ST (lots of work on that one), IBM PC, some mainframes (notably: IBM 4381, Sperry 1100/90) and probably others that I don't remember right now.
Some of those only had proprietary languages and compilers on them, and on some things were more free. If there was a language on any of those systems that made it large enough to rate an article in the magazines you mentioned I probably played with it. But across all of the people that I worked with at the time I've met exactly one person the programmed in Pascal and they abandoned the project because it was dog slow (it got ported to assembly...) and Modula-2 or Oberon I've never come across in the wild other than with my own experiments.
BASIC, COBOL, Assembly and C were the languages that I recall being in mainstream use. That does not invalidate your experience, it may simply not match mine.
What triggers me is the usual kind of message that C wiped everything away, being the first of its kind, when on other realities, it only started to matter when UNIX tooling became part of the picture.
On my bubble C became a thing, when Windows 3.0 went mainstream.
Coding in C on MS-DOS was mostly preparing exercises for Xenix programming classes, a single tower that students had to take turns on, so we need to prepare everything in advance on Turbo C 2.0.
On Amiga it was mostly Assembly and AMOS, on the demoscene community I was part of.
In 1992, using a mix of Turbo Pascal, DBase III+ and Clipper on MS-DOS computers, and Novell Netware.
No, I was just curious. Why would you even think I was trying to trick you? And into what?
You normally make a ton of sense but in this thread I can't follow you and I sense a ton of emotion and projection that are entirely out of character for you.
"Object oriented programming is just functions with persistent state and multiple entry points, and we all learned how bad that was in the 70s."
I'm talking about applications developers who came out of the small mainframe/minicomputer world of the 70s and into the workstation/microcomputer world of the 80s. They started with assembly, and prying them off of it was as hard as convincing engineers to use FORTRAN. Convincing those application developers to use a garbage collected language, Java, was hard in the 90s.
Burroughs, IBM, DEC, Xerox, ETHZ, MIT, Intel,....
Plus, legacy code.
No, it almost always is. The designers of a generic library can't anticipate the use case, so can't make appropriate tradeoffs.
For example, compare `std::unordered_map` to any well written C hash table. The vast majority of hash tables will never have individual items removed from them, but a significant amount of complexity and performance is lost to this feature.
A library author can spend ridiculous amounts of time refining and optimizing their implementations, far more than any application programmer could afford or justify.
The designers of a generic library can't anticipate the use case, so can't make appropriate tradeoffs.
This is definitely not true. Take C++ for instance, not only is it possible to specialize generic code for particular types, but it's absolutely routine to do so. Furthermore, with all sorts of C++ template features (type traits, SFINAE, CRTP, Concepts, etc) even user-defined types can be specialized, in fact it's possible to provide users with all sorts of dials and knobs to customize the behavior of generic code for their own use case. This functionality is not just a quality-of-life improvement for library users, it has profound implications for performance portability.
For example, compare `std::unordered_map` to any well written C hash table.
std::unordered_map is a strawman. There are a plethora of generic C++ hash tables which would match, if not soundly outperform, their C counterpart. Also, even if we blindly accepted your claim, then how do you explain qsort often being beaten by std::sort or printf and its variants being crushed by libfmt? What about the fact that Eigen is a better linear algebra library than any alternative written in C?
That's true. But simply having knowledge of the goal and a few simplifying assumptions can beat all the optimization in the world. In other words, a polished sub-optimal approach isn't as good as just having a better approach. `std::unordered_map` is heavily optimized, but can't make any tradeoffs because it's a general tool.
> plethora of generic C++ hash tables which would match, if not soundly outperform, their C counterpart.
Post one.
> not only is it possible to specialize generic code for particular types, but it's absolutely routine to do so.
Yep, it can do type base specialization, not application based specialization though. That requires a programmer.
> how do you explain qsort often being beaten by std::sort
a standard library C function often cannot be inlined to remove the comparison function pointer call, whereas std::sort trivially can.
If you wrote one yourself for a particular problem, it would not have this issue. This is actually a great example of where C excels because the choice of sorting algorithm so much depends on the kind of data you are sorting.
Let me be clear about my claim: tailor made solutions to each problem will almost always be faster than generic solutions. Do you really disagree with that? If you want to argue that maybe it's not productive to work that way, that's a different argument.
I disagree with it in the sense that I disagree with the statement "A human will always be able to write the same or better assembly than a C compiler, because humans can learn the compiler's tricks and make optimizations which the compiler is not allowed to make." It's a true statement, but it's so detached and irrelevant that it hardly matters.
Generic code has proven itself time and time again, even Go caved in and supported it.
> Generic code has proven itself time and time again, even Go caved in and supported it.
I'm not saying anything against the language feature generics. There is plenty of use for them even in a self contained code base.
Abseil or folly both have optimized hashtables, I believe. Rust's standard HashMap follows the same design. It involves SIMD to look for a bucket whose hash matches the query's so redoing it in C every time you need a hash table will be quite impractical.
Did I get it right that you argue for re-implementation in every of your apps of some sorting algorithm which is most fit to your data?
Why not use instead a generic library implementing a particular sorting algorithm parameterized by the data type and maybe by some policies specifying minor variations of the algorithm?
"Let me be clear about my claim: tailor made solutions to each problem will almost always be faster than generic solutions. Do you really disagree with that?"
I do. I don't think even you invent a special sorting algorithm for each of your applications that need sorting.
> Yes, you read that correctly: my naive Rust was ~32% faster than my carefully implemented C.[0]
> As a result, this code spends all of its time constantly updating an efficient data structure to be able to make this decision. For the C version, this is a binary search tree (an AVL tree), but Rust (interestingly) doesn’t offer a binary search tree — and it is instead implemented with a BTreeSet, which implements a B-tree. B-trees are common when dealing with on-disk state, where the cost of loading a node contained in a disk block is much, much less than the cost of searching that node for a desired datum, but they are less common as a replacement for an in-memory BST[1]
> So, where does all of this leave us? Certainly, Rust’s foundational data structures perform very well. Indeed, it might be tempting to conclude that, because a significant fraction of the delta here is the difference in data structures (i.e., BST vs. B-tree), the difference in language (i.e., C vs. Rust) doesn’t matter at all.[1]
> Implementing a B-tree this way, however, would be a mess. The value of a B-tree is in the contiguity of nodes — that is, it is the allocation that is a core part of the win of the data structure. I’m sure it isn’t impossible to implement an intrusive B-tree in C, but it would require so much more caller cooperation (and therefore a more complicated and more error-prone interface) that I do imagine that it would have you questioning life choices quite a bit along the way. (After all, a B-tree is a win — but it’s a constant-time win.)[1]
> All of this adds up to the existential win of Rust: powerful abstractions without sacrificing performance.[1]
[0]: http://dtrace.org/blogs/bmc/2018/09/18/falling-in-love-with-...
[1]: http://dtrace.org/blogs/bmc/2018/09/28/the-relative-performa...
No amount of optimisation will make a hash table designed for items to be removed competitive with one where items do not need to be removed.
>Take C++ for instance, not only is it possible to specialize generic code for particular types, but it's absolutely routine to do so.
So it's not a generic data structure, then. When you specialise a template, you essentially write a concrete data structure for a particular type. Rather than writing a big generic data structure that's inefficient then specialise it to the particular type, it is much easier just to write that specialised data structure in the first place.
>std::unordered_map is a strawman. There are a plethora of generic C++ hash tables which would match, if not soundly outperform, their C counterpart.
How is it a strawman? It's in the standard library.
>Also, even if we blindly accepted your claim, then how do you explain qsort often being beaten by std::sort or printf and its variants being crushed by libfmt?
printf is on the order of 50 years old. libfmt as written about 5 minutes ago. Do you take into account in your comparison the many more years in which printf has been useful? Do you take into account the amount of time it takes printf to compile vs a huge C++ library like libfmt?
Do you take into account all the code that has been slowed down by C++ programmers writing bad code and assuming a sufficiently smart compiler will inline everything for them? Do you take into account all the horrifically slow iostreams code out there?
qsort and std::sort do completely different things. Comparing them is absurd. qsort takes the size and comparison operator at runtime. std::sort requires them to be specified at compile times. I frequently use qsort in a way that you simply could not use std::sort, because those things are runtime-variable.
The proper comparison to std::sort is the implementation of a sorting algorithm written in C, specialised to the code it was written to work with. Then you can debate 'is it worth using this for the minor performance gain' etc. But comparing it to qsort is inane and demonstrates you don't even know what the two functions do.
In generic C++ code, you can specialize a part of the generic algorithm to tune it to a particular use case. Usually it takes the form of a small class template which can be specialized for a particular type and is used by the generic algorithm operating on that type. This class template is called trait, policy or strategy depending on the way it is used.
"qsort and std::sort do completely different things. Comparing them is absurd. qsort takes the size and comparison operator at runtime. std::sort requires them to be specified at compile times."
Not at all, you can pass a function pointer to std::sort just as well if you need to [0]. Most of the time you don't need this indirection but in C you are stuck with it unless you copy-paste-edit qsort.
https://github.com/tmmcguire/rust-toys/blob/master/alternati...
is a program that mmap's an anagram dictionary file and builds a fast-n-dirty hashmap dictionary over the file data. It took about an afternoon to write and was pretty decent.
https://maniagnosis.crsr.net/2014/08/letterpress-cheating-in...
But, generally one should just reach for a library first before doing a complex data structure regardless of language. And for example, the linux kernel does a fine job of doing some fairly complex data structures in a reusable way with little more than the macro processor and sometimes a support module. In a way the ease of writing linked lists/etc are why there are so many differing implementations. So, if your application is GPL, just steal the kernel functions, they are reasonably well optimized, and tend to be largely debugged, are standalone, etc.
Gnome programs are largely still written in C, but it isn't actually C. Not really. It's glib, which is almost a whole new language written on top of C. It's its own little world and it's not a good world. For example, it calls abort() whenever it fails to allocate memory.
Of course you do, especially in applications where there is no benefit to be gained versus using a GC. This is why Java was such a huge success, despite offering very little else over C++ other than GC (I think OCaml is a much better example of a GC language). Consider that an entire book has been written on such details as move semantics. For most GUI apps, a GC or other automatic memory management has proved fine.
> Gnome programs are largely still written in C, but it isn't actually C.
It's still C whatever libraries are used. It's still manual memory management with footguns. This is why they created "Vala".
I am afraid you misunderstood my comment. Of course C isn't good for writing code that calls 'malloc' millions of times per second. That's just bad code, which you can write in any language. You should not be dynamically allocating memory all over the place. If you have sane allocation strategies, then having to 'manually' manage them is completely fine.
>This is why Java was such a huge success, despite offering very little else over C++ other than GC (I think OCaml is a much better example of a GC language).
Java is the quintessential example of a language that was made popular through marketing and hype.
Getting reasonable performance out of the JVM requires far greater expertise than doing the same in C. The JVM's GC is insanely complicated and has a million different knobs which can drastically affect performance. Or they can apparently do nothing. Until you turn other knobs...
>Consider that an entire book has been written on such details as move semantics.
There are no 'move semantics' in C. Yet another way in which it is superior to C++. C++, which is a horrible language designed by nerds who enjoy standardese more than they love their own children, has 'move semantics' bolted on with an arcane 'rvalue references' mechanism that gets more complicated in every version of the standard.
>For most GUI apps, a GC or other automatic memory management has proved fine.
Retained-mode UI is something that is only reasonable with automatic memory management. The way that lifetimes of objects works in those APIs really does require a garbage collector because it's all so dynamic.
The immediate-mode UI approach, which has many other benefits as well, works perfectly fine with manual memory management.
>It's still C whatever libraries are used. It's still manual memory management with footguns. This is why they created "Vala".
Objective-C is more like C than "glib" C is. As the suckless guys said:
"glib - implements C++ STL on top of C (because C++ sucks so much, let's reinvent it!), adding lots of useless data types for "portability" and "readability" reasons. even worse, it is not possible to write robust applications using glib, since it aborts in out-of-memory situations. glib usage is required to write gtk+ and gnome applications, but is also used when common functionality is needed (e.g. hashlists, base64 decoder, etc). it is not suited at all for static linking due to its huge size and the authors explicitly state that "static linking is not supported"."
What systems are you thinking about?
Another answer is that C doesn't have much in the way of guardrails; in a world where programmer time is much more expensive than machine time, guardrails make programming much faster.
Your answer is just buzzword after buzzword. What makes C bad for writing applications?