3 ms·
Assembly has one huge advantage over C: no undefined behavior. What you see is what you get. There's none of the it-might-work-for-years-before-betraying-you wo
by akkartik 11y ago
Assembly has one huge advantage over C: no undefined behavior. What you see is what you get. There's none of the it-might-work-for-years-before-betraying-you worry when you deal with assembly. Even if you need to think about multiple machine types, you can think about the finite set of machine types you care about, and not worry about some abstract ideal of portability that will cause the C compiler to do strange things (http://blog.regehr.org/archives/213 http://blog.regehr.org/archives/213).
Unfortunately, this advantage means you can't understand all of C just by understanding assembly, even though C programs turn into just assembly at bottom.
Edit: after the responses below I've been (re)reading https://www.scss.tcd.ie/John.Waldron/3d1/arm_arm.pdf https://www.scss.tcd.ie/John.Waldron/3d1/arm_arm.pdf and http://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-instruction-set-reference-manual-325383.pdf http://www.intel.com/content/dam/www/public/us/en/documents/.... And marvelling.
- pcwalton 11y ago> Assembly has one huge advantage over C: no undefined behavior. Sadly it sure does. Grep through the ARM architecture reference manual for "UNPREDICTABLE" (helpfully typeset in all caps), for example…
- akkartik 11y agoInteresting. :(
- jevinskie 11y agoYeah I just hit a LLVM AArch64 codegen bug today. Ugly! str x9, [x9], #8 Luckily it was fixed upstream. I don't like working in tablegen files. =) https://github.com/llvm-mirror/llvm/commit/7f4f923aa57ad8d7ed3f84c532b583f590a68a49 https://github.com/llvm-mirror/llvm/commit/7f4f923aa57ad8d7e...
- ndesaulniers 11y ago> There's none of the it-might-work-for-years-before-betraying-you worry when you deal with assembly. That's not quite true. For example, in my blog post on assembly [0] one of the first points I make is that on x86-64, the stack must be aligned by 16 B when calling into libc. (Note: the point I'm making is invalid if we're talking assembly with no libc interop, but c'mon). That's actually not a hard and fast rule; it has more to do with requirements of the implementation of that function for alignment. For instance, I can invoke memset from x86-64 assembly with a misaligned stack without segfaulting on _my_ machine, _but_ that is because my implementation of memset I'm linking against doesn't require alignment. That might work for me today, but let's say the next upgrade to my libc suddenly has a new implementation of memset that requires alignment for whatever reason (I think vector register access requires stack alignment, for instance). Suddenly, my assembly program that worked the day before (possibly for years) has now betrayed me. And you just have to keep track of alignment in your head. Oh, someone pushed a 64b register on the stack? Misaligned by 8B! I'm not arguing the point about undefined behavior (though I'm sure there's probably known bugs with certain instructions on certain families of processors [1]). My point is that it's very possible for assembly to betray you, and compiler warnings from C/C++ compilers are a god send compared to the usual seg faults and bus errors experienced when writing assembly. (honestly, I find pencil & paper very useful for debugging assembly; by sketching out the state of things and trying to reason about them, you find failed assumptions and mistakes). [0] http://nickdesaulniers.github.io/blog/2014/04/18/lets-write-some-x86-64/ http://nickdesaulniers.github.io/blog/2014/04/18/lets-write-... [1] http://en.wikipedia.org/wiki/Pentium_FDIV_bug http://en.wikipedia.org/wiki/Pentium_FDIV_bug
- davidtgoldblatt 11y agoMany processors have undefined behavior. The x86 manuals, for instance, specify (or rather, explicitly decline to specify) lots of it: - Values of certain flags after certain instructions - The result of bsf or bsr on 0. - I think there are some floating point operations that can be undefined when the results fall off the edge of representability, but I don't remember off the top of my head. These have behavior that differs from processor to processor. This isn't to mention the various kinds of processor errata that pop up: loads being reordered with LOCK'd x86 instructions, or ARM memory reads that go backwards in time, both of which happen in real processors despite being architecturally forbidden.
- gecko 11y ago> Assembly has one huge advantage over C: no undefined behavior. In the olden days, maybe, but nowadays? No way. As the most extreme example: forget to use memory barriers when you should? Congratulations, the OOO nature of modern day CPUs means your code may not execute identically on all platforms. But even lesser things can impact you: instructions that were cheap become expensive, or suddenly registers that you know you weren't supposed to touch but did (and that happened to work) suddenly don't because a library got changed, or half a dozen other things. With assembly on a modern CPU, congratulations: you have eliminated one level of abstraction. But there are still many, many turtles to go before you're really all the way down.
- ndesaulniers 11y ago> instructions that were cheap become expensive Agree. At which point, C gives you a huge advantage. Sure, you can hand write assembly, but I bet the libc implementation you're linking against has optimized to death with intimate knowledge of timing sequences and inner state of the processor from the processor vendor. I would expect a company like Intel to contribute patches to glibc (GNU libc) based on running functions from the standard library on a very very expensive FPGA (or just expensive-software simulation of hardware). Memory ordering operations are an advanced topic; I have a copy of C++ Concurrency in Action, currently sitting on my bookshelf, that has a chapter explaining the concepts. Should be a fascinating read!
- pcwalton 11y agoAgreed. I think there's no clearer example of C giving you an advantage than integer division by a constant. Even on architectures where it's supported (x86), the divide instruction is really slow. The standard approach is to use the Hacker's Delight technique to convert the division into a fixed-point multiplication and shift. All production-quality compilers will do this for you by generating the magic number, but if you're doing it yourself in assembler you'd have to look up the magic number and shift. Lots of assembly language programmers won't even bother doing this and will just write "idiv" in x86 (or "bl __divsi3" on ARM), which slows down their programs.
- 11y ago
- mwcampbell 11y agoWe can see in the replies that asm does in fact have undefined behavior. So what about Rust? Presumably the point of Rust's safety is that, assuming the (hopefully minimal) unsafe code you depend on is well debugged, there is no undefined behavior; either the program works, it fails fast and cleanly, or it doesn't compile.