What's the relationship between C and C++?

Just a quick note on this. I don’t want to go down a rat-hole over this business of slow (relatively speaking) interpreter performance because it was just a comment I made in passing that I thought was self-evident. There are clearly a number of reasons that interpreter performance may not matter, the most obvious of which is if the processor is fast enough relative to the application performance requirements that the impact on computational performance just doesn’t matter, which may often be the case with today’s fast processors.

Similarly, it’s just a truism that if an inefficient interpreted language is able to offload all its compute-intensive functions onto a bunch of optimized library modules, leaving the interpreter itself with nothing much to do, then clearly interpreter performance doesn’t matter. No doubt this can and does sometimes happen, but I don’t think it’s generally a very good argument. For one thing, it assumes that an interpreted language environment can, in fact, interface with such libraries, which is far from a given. It was considered rather innovative back in the day when some LISP implementations could run a mix of compiled and interpreted modules.

But more to the point, many languages are written specifically to facilitate the coding of certain types of computations: FORTRAN for scientific and engineering calculations, for example, or LISP originally as a computational implementation of a mathematical notation similar to the lambda calculus but soon transformed into a popular platform for AI applications. In such cases I’m doubtful that such targeted platforms could be reduced to mere shells executing calls to library routines; it seems to me that the very nature and purpose of the language would dictate that most of the code would be written in the language itself, and libraries would support only the most common and utilitarian functions. Things like, for instance, the venerable old Scientific Subroutine Library (SSL) for FORTRAN (now called ESSL – Engineering and Scientific Subroutine Library). Moreover, in keeping with the targeted purpose of FORTRAN that I mentioned, the SSL itself was – unsurprisingly – written in FORTRAN.

Again, I don’t want to make a big deal of this performance thing – I mean, I have a desktop computer in front of me that’s probably about a hundred times faster than I mostly really need, and it can run a simulation of a PDP-10 timesharing system many, many times faster than the original multi-million dollar mainframe – but I just wanted to clarify these points. And to be very clear, I fully appreciate that even when dealing with efficient compiled languages, it’s often advantageous to hand-code certain extremely compute-intensive functions as subroutines written in highly optimized assembler, where sometimes knowing how to eliminate one extra instruction or shave a microsecond off the execution cycle of some critical loop makes a huge overall performance difference. In fact it rather frustrates me that kids today tend to have little appreciation for the concepts of efficiency and optimization.

Interpreter performance doesn’t matter much for most applications these days because A) interpreters have gotten much better, and B) very few applications actually push the limits of machine performance.

The same goes for memory. We used to care a lot about ‘bloatware’ back when resources were critical. Now, memory efficiency for most applications is utterly irrelevant. Who cares if your program uses 4 megs instead of 2, when the computer has 16GB of available memory?

In the cases where performance and size still matter, you can optimize modules that need optimization and leave everything else alone. I have occassionally had to write assembler or C/C++ unmanaged code libraries to do things that can’t be done well in managed languages. Mostly real-time stuff where you really don’t want the unpredictable timings garbage collection can bring to the table.

It’s much more important these days to focus on quality, interoperability and maintainability than to use the most optimized language. And if you do need some optimized functions, there are often already-available libraries for that you can use.

I’m an older programmer, and it took me a while to get out of the mindset that interpreters sucked and performance and program size were critical. For a while I did a lot of server-side coding fhat would be best left to Javascript on the client side, because my experience with Javascript in my early career convinced me it was useless for any kind of serious processing. Then I moved to other languages and didn’t go back to it for a decade. I had to re-learn my preconceptions about what you could do in script in the browser.

I do mixed c /asm programming, where I’m looking at the asm generated by the c. When I was doing the same thing with Pascal, my pascal sources were close to the asm: that’s not true of c.

Earlier languages like FORTRAN and BASIC were line-oriented, and each line corresponded to one set of ASM lines, which is ‘close to ASM’. Block-oriented languages like Pascal and C compile blocks, and ASMs are line-oriented language, so there is a mismatch. Pascal is inherently a one-pass language (although it doesn’t have to be implemented like that), and the result is that, even though it is block oriented, the ASM matches the source code. That isn’t true of c.

The idea that ‘c is close to ASM’ dates from when CS courses like MIT (and my school) offered two languages in their primary stream. C and something else: some object oriented or functional language like LISP, now some object oriented and functional language like Python.

C is much closer to ASM than is LISP.*

BASIC is a bit different. Although it is easy to translate between ASM and BASIC (and yes, I’ve done ASM~BASIC programming), operators are typically implemented as function calls. It is ‘close to the ASM’ in the sense that you could take any individual word or sentence or paragraph or page in either and translate it (interpret it) back and forth, but it’s not the same as c or Pascal ASM.

People who haven’t done mixed language programming often confuse the way c handles pointers as being ‘close to ASM’. This is a fundamental misunderstanding, which isn’t obvious to people who haven’t done mixed language programming.

*Unless you have a LISP processor

Fair enough; I was thinking more along the lines of both the way that the statements correspond to instructions pretty closely AND the way you can actually manipulate things like memory and other hardware items with C in ways that are more difficult or impossible in say… Pascal. FORTRAN might be more like C in that regard; my FORTRAN is fairly rudimentary.

But as far as how close the assembly is to the actual source code, isn’t that wholly dependent on the compiler design and its optimizations? Seems to me that techniques like peephole optimization kind of blow away that correspondence.

Forth also has a lot of that (non-functional, non-object-oriented) low-level correspondence between statements and code, right? For example, the interpreter takes a string, looks it up in the dictionary, finds its execution token, and runs the code. Or something like that.

FORTRAN is unlike c except in the fact that a generation of processors were designed to run FORTRAN – and then c was implemented on the FORTRAN hardware. FORTRAN originally ran on hardware that didn’t have any memory to implement a heap – and then on virtualized / shared systems that didn’t permit hardware memory management.

Peephole optimization is a technique to make c code – which doesn’t natural assemble very well – optimize to the kind of small/fast code that comes naturally to FORTRAN or Pascal. To be fair, one of the reasons peephole optimization is required is because of intermediate-language compilation, which messes up any language, but particularly with c, the use of c pointers naturally optimizes to pointer variables that have to be kept synchronized with the native ASM pointers, and the peephole optimization is used to remove the unnecessary duplication. C also handles integers in a way that is inherently inefficient, and peephole optimization is used to make the ASM implement something like FORTRAN/Pascal semantics, where that has the same result.

Lifting and unrolling makes all code different. Again, lifting makes more difference to c, because of c pointers, and then when you combine the pointers and the lifting and the peephole optimization, you get something almost unrecognizable, but closer to the original FORTRAN than to the original c.

The use of the postfix increment operator in the name of C++ suggests things get better only after you’ve finish using it. That was certainly my experience.

I am shocked, shocked I say, that no one has yet brought up templates.

Templates get a bad rap for their obtuse syntax, but they are the only way to write high performance generic code. For instance, the standard library qsort in C is slow, because the only way it can perform a comparison between two elements is via a function pointer. That is enormous overhead if the type is something trivial like an int. In C++, the sorting routines are compiled for each type they’re used for because they use templates. Since the compiler knows the type, it can simply use the native comparison instead of indirecting through a function pointer.

Of course, if you needed a fast int sort (for instance) in C, you could simply write a custom one. But then the code is not generic. Templates get you the best of both worlds.

Frankly, between the C++ object system and templates, if I had to give up one of them I would keep the templates. You can largely fake C++ objects with C structs (virtual functions are just syntactic sugar on C function pointers). But you can’t easily fake templates.

I write code that runs on very limited systems; ones with a handful of registers, no memory allocations, and no function pointer support. We still use templates on these systems.

It is a common misconception that computers are fast. They are not. Computers are slow, and only occasionally seem fast because a great number of people have optimized the low-level libraries that underpin everything. These libraries are almost invariably written in C or C++ (or, very occasionally, another compiled language like FORTRAN; and very, very occasionally some bits of assembly). Python, for instance, is only usable because it’s not written in Python.

It was on IBM 3270 terminals so looked a bit like your Clipper screen except green. It didn’t even have lower case letters!

My first job was on a similar system except we use PL/1 rather than COBOL. As I remember it IMS transactions tended to be rather simple screen-at-a-time applications, a bit like early server side web apps. It is remarkable how so many of the same issues keep reappearing and are solved in similar ways, even though today’s systems are so much more sophisticated. For example instead of HTML we had MFS (message formatting system) to design the screen layout. Session data could be stored in the SPA (scratchpad area) in a similar way to cookies, or as non-display fields which get re-submitted which is a technique I have also seen used in HTML.

Part of the reason for the slow uptake of OOP and other modern techniques in the corporate world was, I believe, that systems were conceived by senior designers and analysts whose imagination was severely restricted by COBOL-Think. They couldn’t see a use for this new-fangled stuff because their idea of what a system could and should do was already limited to solutions COBOL could readily achieve.

I would say that 90% of programmers today write little more than ‘glue’ between already existing libraries. A UI is written that leverages all kinds of UI libraries either client or server side. UI sends requests to the server, which often then just calls out to some service either on the web of locally. The service itself may just broker requests and shuffle them off to SQL server or MySQL or something. 90% of the new code is simply stuff that formats requests, reads database records and displays information, etc. All heavy math or data summation or whatever is done by the database server or application frameworks.

The guys who write libraries or work on application server code need to really know what they are doing and write tight, optimized code. But the programmers consuming those services really don’t.

Back in the day, you wrote your own sort routines, you wrote your own image processing algorithms, if you needed to communicate you wrote the code to directly manipulate the UART in your computer, etc. And you were doing it on slow, limited hardware.

Today, you can write a program that goes out to the web, scrapes a bunch of data and presents it in an interative graph, and you can do it by writing about 20 lines of code that calls existing free libraries. That’s the kind of stuff most programmers are writing now - front ends to databases, code that leverages Azure or AWS, web sites that use standard open-source technology stacks, etc.

There are still people who need to write tight, optimized code. But many, many more who need to be more focused on choosing the right libraries, writing proper interop code, learning the myriad of ever-changing libraries of support code, building usable UIs, and doing it all with sound software engineering techniques (unit testing, code coverage tools, build systems, continuous integration, install systems, etc).

The skill set of the average programmer has changed. The way CS is being taught has changed as well. New grads come out now with a giant laundry list of exposure to different platforms and services, but weaker fundamental CS knowledge. At least that’s what I have observed from interviewing people over the years. Of course there are always exceptions.

I went to a talk by Dijkstra when I was in grad school, and the only problem with him was that he seemed to think the average programmer was as smart as he was.
His comments on programming languages in SIGPLAN Notices, where the quote comes from, are still on the money. But perhaps you think gotos as used in 1975 weren’t harmful. I saw plenty of cases where they were.

Well, I could just imagine the reaction of our VP if I had told him we wouldn’t have manufacturing data on our new processor because I needed to take time out to learn Python.
My experience in forcing the class to write structured assembly language tells me that you can write good code in any language, but some languages more or less force you to write decent code while others require you to know what you’re doing.
My several orders of magnitude bigger than 10 lines of Perl worked fine, and was a critical part of our division. It was a hack - it had to be given the constraints of time, resources, and the lack of a consistent set of specifications. It also lasted longer than the division.

By computer do you mean computer system, or do you mean processor? Computer systems are not much faster than before -though they do more - because people keep throwing in crap until the performance gets too bad.
Processors are fast, and they have been for a long time. In 1975 I ran an emulation of an LGP-21 from 1962 on a PDP11 simulating Lockheed SUE microcode running the emultor. It ran as fast as the bare LGP21 would have.
I’d suspect time to market beats out code optimization to the point no one would notice it every time.

Agreed…it’s just that Perl deliberately encourages you to hack stuff together because it is literally a language designed around the concept of doing things any way that works for people who just want to hack stuff together. I don’t say that as a criticism per se; the longevity and broad use is a testament to its own utility, but it is designed to not force you to think ahead or follow any particular practices or design patterns. Given any five Perl programmers a problem and they’ll come up with thirteen ways to solve it, at least half of which shouldn’t work but somehow do anyway.

Stranger

Absolutely, as long as we’re not implying that one is harder or true scotsman programming.
I’ve worked commercially as a software dev for more than 20 years, and more than 10 years as a hobbyist before that.

I’ve had a lot of exposure to both flavors of dev work: wringing out every byte of memory and optimization that you can, including diving into assembler, vs building at the top of a stack that includes a managed language, .NET, Unity etc.

I don’t feel as if the latter is easier, as expectations for what programs can accomplish (and how reliable they should be doing it) is so much higher.

Or you could put it another way: where once there was just the role of “programmer” (or “code monkey” :slight_smile: ), the industry has matured to the point where a lot of specialization is required. Systems, embedded / hardware and library developers are just some of the roles within that ecosystem. Many projects need devs with such a specialization and devs with higher-level (I mean in the categorization sense, not “better”) focus.

I hope that wasn’t what I was implying. They are just different skill sets, and you need both kinds of people. Sometimes the guys who were really good at writing highly optimized complex code were the worst at managing all the myriad engineering details around reuse, testability, building, etc. And the guys who wouldn’t know how to write an efficient sort routine if you forced them were excellent at building and managing large code bases and running projects and staying up to date on the latest new tech.

Also, the new world of programming requires constant training and retraining. I’ve been out of the imdustrial software biz for three years, and it’s already full of buzzwords and frameworks I’ve never heard of. The new tech comes at you fast and furious. Github, then Docker. Whole new ways of distributing and installing software. A million UI frameworks come and go. AWS and Azure come along and change everything. New communication protocols constantly being invented and need to be learned. Security evolves constantly.

The last thing I did on my old job was to convert the security on an intranet app to use OpenID, which didn’t exist a few years previously.

So one isn’t easier than the other. They are just very different skill sets.

I really think this misses the point of what @bump was saying about C, which was basically correct. I think maybe you’re talking about different ideas of what “close to assembler” means and are talking past each other.

The salient issue here is what is the useful meaning of saying that a high-level language is “close to assembler”? In practical terms it has nothing to do with being able to see some obvious relationship between language statements and the generated code. It has everything to do with the language being capable of functionality essential to systems implementation that is typically only found at the machine instruction level. C has several such important features, like direct access to memory addresses including memory-mapped I/O registers, the ability to manipulate individual bytes, the ability to perform bitwise logical operations, and to conveniently manage lists and pushdown stacks.

There’s a reason that many operating systems are largely written in C or its variants, including the two most common operating systems in the world, Linux and Windows. No one would even consider trying to write a major full-function OS in BASIC or FORTRAN.

Additionally, every implementation of these languages I’ve ever seen has to drag along a massive infrastructure in the form of a runtime system to support its executables. These are very much application languages. C has little to no such baggage (runtime systems are not to be confused with the C standard libraries) and for all the above reasons C can be effectively used as a system implementation language. Application-targeted languages generally cannot.

Yeah. It’s unfortunate, because the platforms change every few years, but the underlying CS knowledge does not. We end up looking at projects outside of school. Our most recent guy wrote a Dreamcast emulator. That requires serious perf optimization as well as deep low-level hardware knowledge. Those are the kind of people we look for.

Snert. I guess “back in the way” means in the last few weeks. Ok, the last time I wrote a bit-bang UART was over 5 years ago, so I guess it’s been a little while.

Whatever you like. I am being a bit glib, because obviously processors have gotten immensely faster over time. But they still aren’t fast enough. There are many new things I could do if they were 1000x or 1000000x faster.

A 1-cm cube of silicon has something like 1022 atoms. If we have 1000 atoms/transistor (very generous, I think), I should have 1019 transistors. But instead I only have 1010 transistors. Even if I allow that power/heat limits will mean I can only run at 0.1% the clock rate, my computer should still be 106 times faster than it is.

I spent a decent chunk of the last week reorganizing some members of a struct so that I only had to clear a portion of them upon a “reset” call. Some parts had to be zeroed, and other parts did not, but they were interleaved in a way that required zeroing everything. It’s not a very large struct–a few tens of kilobytes–and the reorg only saved about a microsecond. But the call is made up to perhaps 100,000 times per second. So it’s potentially a 10% savings under some scenarios, and optimizing it is an excellent use of my time considering that the savings is multiplied by hundreds of millions of users. Heck, there’s a good chance I’ll have saved a few kilotons of CO2 from entering the atmosphere.

I wouldn’t be scrounging for microseconds if computers were really fast, though…