9 ms·
Why C Is Not Assembly
- onan_barbarian 16y agoI support the idea of labeling the 'sophistication level' of blog posts in their titles, but a bit of punctuation would be nice.
- dangrossman 16y agoThe author was trying to play on "more on...".
- Scriptor 16y agoI considered using a different title for the submission, but I figured he was using "moron" as a modifier.
- Das_Bruce 16y agoI have only written a small amount of assembly code, but I cannot imagine how someone who has written even a tiny bit of either could make such a mistake.
- derefr 16y agoThe statement "C is portable assembler" makes sense in exactly one case: writing a UNIX kernel in the 1970s. Both the assembler and C versions of the kernel would be single-threaded, both would have a standard stack-usage convention (where cdecl is just a codification of the particular assembler stack convention K&R agreed upon), and neither would be run through an optimizer (as an optimizing C compiler didn't exist until quite a while later.)
- jacquesm 16y agoEven today, when you look at the core routines of for instance X windows C is still used in exactly that way, you don't have to but you definitely can and even though C compilers have advances tremendously the fact that there is a direct 1:1 correspondence between input and output is exactly why C is used in those situations. C supports multiple stacks if you tweak setjmp and longjmp just right, and using co-operative multi-threading is possible without any OS support. Not very useful if you really have to do two things at the same time but a lot nicer than interleaving a bunch of code. And an optimizing compiler is still a transformation of the input according to a given ruleset, you could not get the same level of optimization by just processing the output of the code generation stage of the compiler because you would lose a bunch of higher level information that is invaluable when optimizing the code but you could see it as just another stage in 'transforming' from one language to another without losing any functional bits along the way.
- barrkel 16y agoThere simply isn't a 1:1 correspondence between C and its machine code; not in size, and not in functionality. Any half-decent compiler is going to perform a non-trivial transformation of a big switch statement to make it efficient. The expected performance semantics of a switch (something better than O(n) in the number of cases) rules out simplistic iterated jumps in big cases. The programmer expects O(1), or at worst, O(log n). But more importantly, CPUs generally have many more capabilities than are exposed by C, and this is where the "1:1 correspondence" really falls down. Assemblers generally have disassemblers that you can transparently round-trip through. That's a little harder in C. Perhaps you meant a injective relation, rather than bijective? But that's a long way short of an assembler.
- Someone 16y ago"But more importantly, CPUs generally have many more capabilities than are exposed by C." Hear, hear! Moreover, that has always been the case. As an example, try implementing multiple-word addition in C. On most architectures, you will learn that not having access to a carry bit makes that harder than it could be.
- jacquesm 16y agoYes, you're right, the switch statement is a good example of how modern compilers fudge the boundary. But the main point is that the difference between the generated code and the stuff you write is relatively small, when looking at the assembly that a C compiler generates I have relatively little trouble following the relationship between the two, and I can make reasonable predictions about what will pop out on the other end. And of course processors are 'richer' than what most C compilers will use, especially when it comes to special instructions that have no equivalent in the C language. I've worked on a 'decompiler' for the Mark Williams C compiler (yes, that's pretty long ago), and at the time the above still held true, today the boundaries are definitely fuzzier, mostly due to the increased smarts of compiler writers for the optimization stage. Gcc is clever enough to optimize whole branches of code out of existence if you set it to be aggressive enough and the code was written naively, that's one way of dramatically losing that 1:1 correspondence.
- Locke1689 16y agoThe author is not a moron but he is also not correct. Whether or not he thinks this is the best practice, ANSI C is not what is written in most of the important C systems applications. GCC intrinsics, Intel intrinsics and extensions, Nvidia extensions, inline assembler -- these are all things which you will find in most (if not all) of the very performant and useful C systems programs. Non-ANSI C is almost the de facto implementation in systems development and is mostly what people think of when they say "C is high level assembly." Moreover, people have to realize that this will never change unless intrinsics fall out of favor. For example, how would you begin to define a VMENTER intrinsic in the ANSI C standard? The author spends a lot of time railing against undefined behavior, but there is a very big difference between undefined behavior and implementation defined behavior. Simply because someone whips out GCC and says "see? GCC does it correctly" doesn't mean they're wrong unless they're only talking about undefined behavior. So I guess my perspective is that there are two different types of people using C: people who are writing systems applications and people using ANSI C. Personally, I would guess that the first group is much, much larger.
- mayank 16y agoSigh...pedantry at its finest. From TFA: > Most real C implementations will go some distance beyond the standard(s), of course, but I have to draw the line somewhere. You missed the guy's point, which was essentially that C code is munged and transformed by a good optimizing compiler, whereas assembly is left untouched by a good assembler.
- Locke1689 16y agoNo I don't think I did. I helped to write an optimized operating system kernel and virtual machine monitor for high performance computing. We absolutely treated C as high-level assembly. C provided a cost-saving measure in that we didn't have to run off customized asm blocks to do trivial one-liners that would be the same in the VMM implementation but slightly different in object code (two different opcodes). Not only is this common practice in academic and production systems development, but it also the recommended practice from most of my colleagues that added the intrinsics for their subsystems (Intel and Microsoft being my personal experiences). You missed the guy's point, which was essentially that C code is munged and transformed by a good optimizing compiler, whereas assembly is left untouched by a good assembler. That's irrelevant. They are both restricted on the basis of being functionally equivalent to the input code.
- scrame 16y agoNice article; short, sweet and enough to pique my curiosity about modern assembly. Some aphorisms like "C has the efficiency and speed of assembler, with the portability and readability of assembler" come to mind, but those are from ancient history in programming years, and usually said by people proficient in both. There are other assertions (like jwz in the 'Java Sucks' rant), that refer to C as "a PDP-11 assembler that thinks its a language", but again an exaggerated statement made by someone who knows. I have not met anyone who primarily codes in an interpreted language who think that C is just syntactic sugar for assembly. Maybe because most of them don't program in C. I can certainly see some amateur programming pundits (or maybe just forum dickheads) regurgitating the lines without understanding them, but there is a world of difference between the examples touched on, and the actual syntactic sugar of recent java releases (generics, unboxing).
- jacquesm 16y agoIt's not too much of a stretch to look at just 'C' as a very sophisticated macro assembler. And probably you should strip out the 'macro' bit there because in reality that's the pre-processor doing it's work and even though there isn't a C compiler without a pre-processor technically speaking it is not part of the compiler since all it does is output more C.
- Someone 16y ago"And probably you should strip out the 'macro' bit there because in reality that's the pre-processor" I disagree. Looping constructs that typically would be macros in an assembler such as 'while' and 'for' are C constructs. Also, C itself has automatic field offset computations.
- berntb 16y ago>>automatic field offset computations A macro for subroutine entry could reserve stack space and define offset constants. A macro for subroutine returning releases the extra space. It could be neat to use, if the processor architecture has sane addressing modes. (I might have seen/done something similar... doesn't feel like a new idea to me. :-) ) (The entry macro might need a simple preprocessor.) Disclaimer: C and lower was another life, dimly remembered. :-)
- edanm 16y agoI think the author misses the point of the phrase "C is just portable assembly". It does't literally mean that C is just like assembly, it's usually a shorthand for saying the following things: 1. C is used in most contexts where you previously had to use assembly, and would use assembly if C didn't exist. 2. C is the language with the "lowest level" code imaginable. 3. What this means is, you can map almost any command in C into a specific command or a few specific commands in assembly. That last line is the important part. C is optimized in the sense that every command in C will give you a deterministic amount of commands in assembly. When reading C code, someone who knows assembly can usually tell you what will happen in the compiled code. You won't see an operator which doesn't have a deterministic runtime or memory footprint. In fact, most of the questions on "Why C has this"/"Why C doesn't have that" can be answered exactly like that: you can't implement it with a deterministic set of commands. In those senses, C is just like Assembly, at least more so than any other language around.
- wlievens 16y ago> 3. What this means is, you can map almost any command in C into a specific command or a few specific commands in assembly. Is that really true? It depends very much on your compiler and on the optimizations that it uses. I'd say that the level of optimization performed is proportional to the similarity between input and output. Straightforward translation makes for crappy optimization.
- edanm 16y agoThe important point is that a C command will result in a deterministic amount of actual commands. For example, C won't implement the "raise to nth power" operator (in Python, 210 means 2 to the power of 10, for example), because it can't be done with one assembly instruction, only with a loop. Even with optimizations, you can reason about your code as if every C command is one Assembly command, and you won't be far off (you can say that every C command is O(1) assembly commands). This is very different from other languages.
- 1amzave 16y ago
- jedbrown 16y agoI suspect this guy never compiled the code he posted. Not that his claim was completely wrong, but gcc-4.4.4, gcc-4.5.1, and clang-1.1 (LLVM-2.7) all fail to vectorize that snippet, with `gcc-4.5.1 -O3 -ftree-vectorize -ftree-vectorizer-verbose=9`, I get note: not vectorized: number of iterations cannot be computed. note: bad loop form. note: vectorized 0 loops in function. However, gcc-4.5.1 and gcc-4.4.4 vectorize it when the operation is written as a standard `for` loop. Clang never vectorizes it.
- hackermom 16y agoHere's why: if you are not programming on mnemonic-level for the implied processor, you are not writing assembly. Period. Saying anything else is just romanticizing and paraphrasing (which in my opinion there is no room for in technics on this level).
- mfukar 16y ago+1 from me. It appears that not many among the HN crowd are willing to accept this, and I'm curious why this matter needs to be overanalyzed, in true geek fashion.
- andymorris 16y agoI have to argue against most of the comments here though: I think, after a very short time with C, you get a clear sense of what will happen in the assembly. It's also pretty trivial to reach that stage with C++ - really, all the basic control structures are implemented in a pretty standard way. Sure, sometimes it throws a curveball, but you know what? It was the exception rather than the rule. I'm fully convinced that I could sit down with the average C++ program and accurately predict the majority of the generated assembly. It's really not that hard - the compiler doesn't have THAT many instructions, and it only uses them in a limited set of occasions! -- Ayjay on Fedang #coding
- pajarito 16y agoReading only the comments, perhaps C is just a low level language very well suited for many applications that don't require class and objects. Is there something more in the original post?
- dfox 16y agoand incidentaly C tends to be better object oriented language than C++ :)
- zafka 16y ago>For example, C won't implement the "raise to nth power" >operator (in Python, 210 means 2 to the power of 10, for >example), because it can't be done with one assembly >instruction, only with a loop. Not a great example of your point. Here is Analog devices Assembly for the ADSP-2100 Family. Assuming Ar is loaded with 2: sr=LSHIFT ar by 10; Even without the assumption, no loop is needed for raising 2 to some power.
- mansr 16y agoWhat you are doing there is multiplying something by a power of two, and 'ar' should have the value 1 for 'sr' to be set to two to the power of 10. Multiplying by a power of 2 is easily done in C using the shift operator.
- hippich 16y agoJust like C is not Assembly, Digging in the Ruby/Python/etc source code is not a hacking.