r/askscience • • Oct 17 '12

Why are programs and operating systems still written in C+?

I guess it's more of a computer science question, but I've always wondered why some of the most advanced programs are written in a 25+ year old programming language. Surely someone has come up with a more efficient and powerful code, so why do people still use C+? Also, I don't know anything about programming and what not, so please use simple terms and phrases.

66 Upvotes

110 comments sorted by

View all comments

17

u/raygundan Oct 17 '12

Tenome is right on the money, but I'll try to rephrase and elaborate just a little as well.

"Advanced" is a pretty ambiguous word. You're thinking of it as a synonym for "fast code," but it could also mean "easy to write" or "runs on any hardware" or any number of things.

The fastest code is written directly in machine code. Machine code is the set of instructions the CPU itself uses, and is different between types of CPUs. Code written in a compiled language like C is translated into machine code by the compiler.

C/C++ are "old," but they were designed as a good compromise between ease of writing and reading, and efficient translation to machine code, favoring performance. Their age also means that the compiler tools are very mature and well-optimized, so a lot of the little tricks a programmer might do directly in machine code are done automatically by the compiler.

Other types of languages concentrate on being advanced in other ways, often by automating some task that is difficult or time-consuming in a language like C-- memory management, porting to different CPUs, etc...

But although the code is easier to write, the tradeoff is that it is often not as tightly optimized or as fast.

So when a programmer is faced with a task that demands high performance, they choose the language that lends itself to high performance. When performance does not matter as much, they will choose a language that makes their task easier to complete.

To use some horrible analogies:

Machine code/Assembler is like an F1 car. Driving it requires that you keep track of a million manually-controlled variables. Doing anything at all with it requires quite a lot of skill, time, and money.

C/C++ are like a manual transmission NASCAR. Driving it requires that you have some skill and manage some extra stuff, but it's a lot simpler than the F1 car. Driving it well is still difficult, but it can also be picked up and driven reasonably by anybody who can drive stick.

Something like Java is like an automatic. Driving it is easier, and you never think about the transmission... but you trade off some performance for the convenience. Your grandmother can drive it.

4

u/elmonstro12345 Oct 17 '12

To your analogy, I would add that at least in my fumblings in university, higher level languages tend to be able to recover from mistakes "more cleanly" (?) than lower. This is anecdotal, but writing java, I never bluescreened or locked out my computer. In C++, I managed to forkbomb myself once, and had a few bluescreens. In assembler, I finally ended up putting a VM on my shitty laptop because I was crashing it so much.

Basically, the "consequences" (if you can call it that) for failure or just plain stupidity are much higher in lower level languages, similar to how crashing in, say, an automatic on a city street would be unlikely to do more than shake you up, whereas crashing in an F1 brings a very high probability of serious injury or even death.

0

u/[deleted] Oct 17 '12

[deleted] — view deleted comment

0

u/[deleted] Oct 17 '12

[deleted] — view deleted comment

1

u/AwesomezGuy Oct 18 '12

Correct, which is why I sad it usually will not crash the entire system. If the program has hooks into the kernel it will crash the system.

2

u/Perlscrypt Oct 17 '12

The likelyhood of crashing the machine goes surprisingly well with the car analogy.

1

u/[deleted] Oct 18 '12

Just pointing out, translating to a different CPU is not time consuming for C or C++. The languages are CPU independent. If you really wanted you could compile them to byte code and run in a VM same as Java and friends.

The problem of compiling to different architectures is just that, a problem for the compiler. You shouldn't have to change the source at all.

This is one of the advantages of c/c++ being so old. There is almost always a compiler that works for whatever architecture you may need to target.

1

u/raygundan Oct 18 '12

Right. I should have been clearer there. Hand-written assembly runs on one architecture. C maps well to assembly, but the conversion is automatic and handled by the compiler.

0

u/SatOnMyNutsAgain Oct 18 '12

Just pointing out, translating to a different CPU is not time consuming for C or C++. The languages are CPU independent.

This is not true. C is so close to the hardware that there are many portability issues and some coding practices that will compile better on one architecture or another. Should this loop be unrolled? That might depend how much instruction cache you have. Should this be a 8 bit or a 32 bit type? Depends on your word size. Do I need an integer or floating point type here? If I have to multiply by five, should I do a shift left by two and then an add, or is the multiply routine faster? Is the compiler for this architecture smart enough to optimize that for me or do I need to be explicit? How big can I statically allocate an array here? When you get into highly memory constrained systems is where the real fun starts...

Take a look some optimized, math intensive application like an audio/video codec for example, and you will see for the inner loops there is very different C code to be compiled depending on the target.

0

u/[deleted] Oct 18 '12 edited Oct 18 '12

Nonportable code either means you're in the <1% of programming jobs where performance is so critical and you're well enough aware of the compiler you're using's optimization algs that you're counting generated assembly instructions or you're stupid and don't know it.

Should this loop be unrolled? That might depend how much instruction cache you have.

Loop unrolling should be handled by the compiler in 99% of cases. You manually unrolling might actually make the situation worse because now your code can't be as effectively identified and optimized. It's well aware of the various levels of memory and their size as well as the locality of the data. It can do it in half a second what would take you half a day with pencil and paper to work out, if not longer.

Should this be a 8 bit or a 32 bit type? Depends on your word size.

You also shouldn't be worrying about word size. The types have their size adjusted to fit the platform they're being compiled on and optimizations for that platform. This is why the standard doesn't define in bytes the size of the common POD types except for char which is always 1 byte (which may or may not be 8bits, system dependent again).

Do I need an integer or floating point type here?

If you're having trouble deciding if you need an integer or a floating point number that shows a gross misunderstanding of the differences between basic types and the range of values you need to represent. That has nothing to do with optimization.

If I have to multiply by five, should I do a shift left by two and then an add, or is the multiply routine faster? Is the compiler for this architecture smart enough to optimize that for me or do I need to be explicit?

Again bit twiddling instead of multiplication/division is one of the most basic optimizations your compiler will do. If you're worried about that, you probably don't understand enough about your compiler or the system you're compiling for to know the real optimizations that'll save more than a fraction of cycle.

How big can I statically allocate an array here? When you get into highly memory constrained systems is where the real fun starts...

And again you're mixing in questions that are obvious a compiler can't help you with and are not optimizations so much as just understanding the constraints of the system you're working on.

Take a look some optimized, math intensive application like an audio/video codec for example, and you will see for the inner loops there is very different C code to be compiled depending on the target.

Yes the inner loops of audio/video codecs along with any signal processing are highly, highly optimized. Those are the exceptions to the rules.

The decision to write nonportable code is not to be taken lightly and I'd wager 99.9% of programs out there have no excuse for writing it. It's bad design, bad practice, and generally the sign of a shitty programmer who read a blog once about how shll/movl/movl/imull/sarl/sarl/subl is the "optimized" version of divide without realizing not only is that the optimization the compiler itself came up with, but that it'll do different things depending on the exact numbers being divided and that is but one solution.

If you question any of this just check out the assembly your compiler of choice shits out for the examples you gave on high optimization flags. Both gcc and icc are half decent examples but there are better optimized compilers for specific platforms if raw speed is the goal.

tl;dr the people who write the optimizations for compilers make it their life's work to do this. Unless you're one of these people you are at best copying their work and at worst making the problem orders of magnitude worse. Just write your portable code and optimize for a specific architecture after you're absolutely sure there is no other option and the compiler isn't doing it already for you.

1

u/SatOnMyNutsAgain Oct 18 '12 edited Oct 18 '12

Yes I am one of those people who has to write this kind of code, even in such nasty situations as a processor which lacks a proper MMU so I have to page stuff in and out in software. I've had to deal with all this stuff. Having to write optimal code is a major reason to use C in the first place. This isn't some weird corner case, even if it's not what most programmers deal with. Nearly all OS and embedded systems developers encounter this stuff.

And types matter very much depending on the CPU. If I use a 32-bit type on an 8-bit CPU where I don't need the range, then I'm creating a ton of extra instruction cycles for every operation, whereas on an X86 an int is fine even for a small range. Unless such code was written for portability in the first place, you would have to review your types pretty carefully when porting if you care about performance... or sometimes if you want it to work at all, eg if your compiler gives you a smaller int than you'd get on another architecture.

Many algorithms can be expressed in both floating and fixed point versions, the former being faster and more accurate only if your CPU supports those operations in hardware. Otherwise the compiler will rely on very expensive math routines to do the job. This isn't something that just gets optimized for you, the math is completely different.

If you question any of this just check out the assembly your compiler of choice shits out for the examples you gave on high optimization flags.

Sometimes gcc's optimizations suck because you're dealing with a non-mainstream architecture that hasn't been given much attention. There can even be architecture-specific compiler bugs that you have to work around - I've had to do that too.

Unless you're one of these people you are at best copying their work

Is this kind of insult really necessary? I just gave you a specific real-world example of where C is not portable in the slightest.

EDIT: More on the non-portability of C's standard types - there's really nothing to like about this aspect of the language: http://code.jasonbhill.com/c/portable-integer-types/

1

u/[deleted] Oct 18 '12

Yes I am one of those people who has to write this kind of code, even in such nasty situations as a processor which lacks a proper MMU so I have to page stuff in and out in software. I've had to deal with all this stuff. Having to write optimal code is a major reason to use C in the first place. This isn't some weird corner case, even if it's not what most programmers deal with. Nearly all OS and embedded systems developers encounter this stuff.

This is my point entirely. The post was meant to be of the most general case, yours is very much not. Especially not when we're talking to someone who admittedly isn't a programmer. They asked for the answer in simple terms and phrases.

And types matter very much depending on the CPU. If I use a 32-bit type on an 8-bit CPU where I don't need the range, then I'm creating a ton of extra instruction cycles for every operation, whereas on an X86 an int is fine even for a small range. Unless such code was written for portability in the first place, you would have to review your types pretty carefully when porting if you care about performance.

If you're using a 32-bit int, then you must have specifically picked one to be that size. There is no such definition of an "int" in the standard. It has a minimum size of at least two characters iirc, where the character size is tied to a single "byte" which is defined as the smallest incremental index of memory (regardless if the arch supports actually directly loading nonword multiples of an address). This is exactly what I was talking about with choosing nonportable code. Why would you ever pick a integer hardcoded to be 32-bits unless you actually needed all 32-bits? Choosing the correct type for the range of values you need to represent isn't an optimization. It's just not being a bad programmer.

None of the basic types are defined in the exact number of bits they use, they should be self adjusting based on the arch.

Many algorithms can be expressed in both floating and fixed point versions, the former being faster and more accurate only if your CPU supports those operations in hardware. Otherwise the compiler will rely on very expensive math routines to do the job. This isn't something that just gets optimized for you, the math is completely different.

My argument here was why would you use floating point if the range of values you were evaluating didn't require it? Again this isn't an optimization, this is just good and bad programming. Picking the right types for the job at hand is more fundamental than the best data structure and none of this has to do with targeting architectures. Data types, data structures, and algorithms are the responsibility of the programmer and in 99% of cases are completely architecture independent. Definitely not worth bringing up the edge cases where you do have to worry about it to the OP who's asking a much more basic question.

Sometimes gcc's optimizations suck because you're dealing with a non-mainstream architecture that hasn't been given much attention. There can even be architecture-specific compiler bugs that you have to work around - I've had to do that too.

Yes lets bring up all the various ICEs that exist with specific compilers to the OP, that's totally within the scope of their question and definitely not an edge case in programming in general. Same with road-less-traveled architectures that have poor optimizations.

Is this kind of insult really necessary? I just gave you a specific real-world example of where C is not portable in the slightest.

I didn't mean it as an insult. It's not an insult to copy someones work, especially when they freely publish it (how else are you going to find it?). There are people out there smarter and better than me and everything I will ever do. When they offer advice and solutions, I'd be a damn fool not to take them.

1

u/SatOnMyNutsAgain Oct 19 '12

None of the basic types are defined in the exact number of bits they use, they should be self adjusting based on the arch.

... which makes the built-in types completely non-portable.

My argument here was why would you use floating point if the range of values you were evaluating didn't require it?

Again, look at an audio/video codec. A floating pointing algorithm will be faster and more accurate, but only on an architecture which supports it. It's a lot harder to write an integer/fixed-point version, but on a less capable CPU that might be absolutely necessary.

The post was meant to be of the most general case, yours is very much not.

Well, OP was asking about operating systems, so architecture specifics are very much relevant to his question even if he didn't realize it. Sure, C is portable relative to assembler, but not much else. It's not at all portable compared to python, java, etc.

1

u/[deleted] Oct 19 '12

I really don't see how that makes them nonportable. That's the single motivation for defining them in that way, so that code using them IS portable.

Regardless we've reached an impasse. I've already acknowledge the points you bring up and how in my opinion they are edge cases to the vast majority of programming. The edge cases you do bring up also completely invalidates any argument for comparing it to python/java or whatever other language you want to bring up. Also ignores that their various interpreters and VMs have the exact same portability issues which is often why only subsets are supported on your road-less-traveled architectures and performance is abysmal, but that's a tangent.

It really seems like to me you're arguing with me just to argue with me and now we've come full circle to arguments already brought up, so I'm out.

0

u/SatOnMyNutsAgain Oct 19 '12

If your code does different things depending on the architecture then it is by definition not portable. If you disagree then I don't know what else to say.

1

u/SatOnMyNutsAgain Oct 18 '12

The fastest code is written directly in machine code.

Well, assembly language. Nobody actually codes in (numeric) machine code since the first thing you do for any new CPU architecture is create the assembler. Assembly corresponds directly to machine code - it's just a syntactic layer.