Technical Article

Assembly vs. C: Why Learn Assembly?

September 20, 2019 by Colin Walls

This article discusses two programming languages, namely, C and Assembly, and presents the need to know Assembly language for programming embedded systems.

Assembly Language and the Rise of Inexpensive Memory

Currently, most embedded systems programming is done in C; if not C, then another high-level language like C++.

It was not always like this. In the early days of embedded systems, code was all written in assembly language; that was the only option. In those days, memory was extremely limited, so very tight control of its use was essential and assembly provided that control. But, apart from that, no high-level language tools were available.

It was some years before tools arrived on the market and quite a few more years before their quality was really good enough for serious code development. The tools came along at just the right time, as processors were becoming more powerful (16-bit and 32-bit devices became viable), memory was getting cheaper and denser and application complexity was increasing.

So, what about today? We have hugely powerful processors which may be provided with enormous amounts of memory, running extremely complex applications, which are developed by large teams of programmers.

Where do assembly language skills fit in?

 

Why Learn Assembly? Embedded Systems Programming Skills

There are really two skills, each of which may be valuable: the ability to read/understand assembly language and the ability to write it.

 

Why You Should Know How to Read Assembly Language

Most embedded software developers should have some ability to read assembly language. This is needed for two reasons.

First, the efficiency of code in an embedded system is almost always critical. Modern compilers generally do a really great job of optimizing code. However, being able to understand what great things the compiler has done is important. Otherwise, there may be confusion while debugging.

Compilers tend not to just translate C to assembly language. A good, modern compiler takes an algorithm expressed in C and outputs a functionally equivalent algorithm expressed in assembly. Not the same thing. This is why debugging can be challenging.

It is also possible the compiler did not do a perfect job—perhaps the C code was not written in the clearest way—and the developer needs to be able to understand what has gone awry. Inspection of compiler-generated code should be a routine part of the development process. This gives the opportunity to ensure that the compiler output really does what the programmer intended and has not been misinterpreted by an overly-zealous optimizer.

The second reason why some developers need to be able to read assembly is that it is essential when coding “close to the hardware”. Drivers are not necessarily written in 100% assembly nowadays, but some assembly language content is almost inevitable. Being able to understand what a driver is doing, in detail, is necessary to use it most effectively and to perform troubleshooting.

 

Why You Should Know How to Write Assembly Language

What about writing assembly language? Nowadays, it would be very unusual for an entire application to be written in assembly language; most of the code, at least, is written in C. So, C programming skills are the key requirement for embedded software development. However, a few developers need to have a grasp of assembly language programming. Of course, this skill is specific to a particular processor; however, if a designer has mastered the assembly language of one CPU, migrating to another need not be too challenging.

There are two reasons to write assembly language. The first and most important reason is to implement some functionality that is not possible to express in C. A simple example might be disabling interrupts. This might be achieved by writing an assembly language subroutine and calling it as if it were a C function. To do that, the call/return protocol of the C compiler in use must be known, but this is generally easy to figure out. You could just look at compiler-generated code, for example.

The other way to implement assembly language code is to insert it inline into the C code, normally using the asm extension keyword. This makes particular sense when a single or just a few assembler instructions are needed, as the call/return overhead is eliminated. The implementation of this extension varies from one compiler to another, but commonly an asm statement takes this kind of form:

asm(" trap #0");

Typically, the only places where functionality that cannot be expressed in C is required are start-up code and device drivers. This part of the development of an embedded software application involves a small number of developers. Thus, the need for assembly-writing skills is, as mentioned above, limited to a select group of engineers.

Some developers feel that they need to know how to write assembly language in order to implement code in a “more efficient” way than the compiler will manage. It is possible that, on some very rare occasions, they may be right. However, most modern compilers do a remarkable job of optimizing and generating efficient code (keep in mind that “efficient” can mean fast or compact—you choose, though sometimes you can get both).

Here is an example:

#define ARRAYSIZE 4

char aaa[ARRAYSIZE];

int main()
{
    int i;
    for (i=0; i

This looks like a simple loop that sets each element of the array to zero. If you compile this with a reasonable amount of optimization activated and try to debug the code, you will get an odd result: it would jump straight through the loop (i.e., behaves as if there were no loop at all). This is because the compiler determines that a 32-bit move of zero into the array would do the job much more efficiently than a loop.

The resulting code (in this case for an Arm processor) looks something like this:

<code> mov r3, #0
  ldr r2, .L3
  mov r0, r3
  str r3, [r2]
  bx lr
.L3:
  .word .LANCHOR0

Tweaking the value of <strong>ARRAYSIZE</strong> produces some interesting results. Setting it to <strong>5</strong> gives this:

<code>  mov r3, #0
  ldr r2, .L3
  mov r0, r3
  str r3, [r2]
  strb r3, [r2, #4]

Still no loop. Making it <strong>8</strong> carries on in this vein:

<code>  mov r3, #0
  ldr r2, .L3
  mov r0, r3
  str r3, [r2]
  str r3, [r2, #4]

Then, building this code for a 64-bit CPU gets even better:

<code> mov w0, 0
  str xzr, [x1, #:lo12:.LANCHOR0]

And so it continues. Larger array sizes result in efficient loops or maybe just calling a library function like <strong>memset</strong>(), a standard C library function that can be called from assembly.

The bottom line is that assembly language skills are far from obsolete, but many highly skilled and very productive embedded software developers may be limited to competent assembly code reading.


 

If you'd like to learn more about the other side of this concept, check out Robert Keim's article on <a href="https://www.allaboutcircuits.com/technical-articles/introduction-to-the-c-programming-language-for-embedded-applications/" target="_blank">C language for embedded programming</a>.

Share your thoughts and experiences regarding the utility of assembly language in the comments below.

9 Comments
  • L
    Lo_volt September 23, 2019

    I believe it was the MOVEM command in the Motorola MC68000 assembly language that allowed my coworker to move a block of data far faster than could be achieved using c++ alone.  MOVEM allowed the programmer to set a addresses for the data locations (from and to) and a counter.  Once the MOVEM command was executed the processor moved the data without any further commands.  c++ implementation would have required a loop and several repeated commands to complete it.  Execution time using assembly was a small fraction of the compiled c++ code time.

    Like. Reply
    • 42BS September 27, 2019
      MOVEM => move multiple. You could load/store multiple registers. But no looping. Max. was movem.l d0-d7/a0-a6,(a7)+, so store 17*4 bytes at once.
      Like. Reply
  • M
    MIS42N September 27, 2019

    Long ago I looked at a program written in FORTRAN that did some serious array manipulation. The computer was in use on business days, the program took too long to run overnight so it ran only on weekends. This seriously impacted the research the program was used for. I did a calculation how long the array manipulation should take if written in assembler, and came up with around 5 hours. The compiler was able to print out the assembler equivalent of the instructions it generated, to my surprise it was about 10% slower than a hand coded equivalent. It turned out the time was being wasted in moving array elements to and from disk. A few changed lines and the disk I/O was done 1000 elements at a time and the program could easily be run overnight or multiple times on the weekend. Although there was no assembler in the final program, it was an appreciation of assembler that led firstly to thinking the program was not running efficiently, and the ability to hunt down the real culprit.

    Like. Reply
  • M
    mjaa September 27, 2019

    Another important factor in the choice between assembler and a HLL is readibility. With increasing MIPS and memory availability the efficiency factor can be traded wth increased readability leading to better maintainability and re-use within a development context. Through the 70’s we wrote well structured assembler to maximise efficiency. With smart macro usage and extensive use of meaningful symbols and commentary a good piece of assembler was much more readable and safer to re-use than a lazy slab of ill disciplined C, Basic, Fortran etc especially with the constraint of 64Kbyte address spaces!
    In designing and considering many of the relative advantages of compilers over assemblers (for fun) in my retired years I believe the the optimum approach is neither but instead what I call High Level Assembler which is device independent in its syntax (through extensive configuration files), highly readable, forced structured (like Pascal vs C) and full access to the device h/w, registers, stack etc AS NEEDED.er I currently have 2 products competing for my preference HelGA (a universal High Level Assembler Language ) and a form of Pascal rewritten for use in RTOS , multitasking, event driven, time critical code. My observation over the decades is that the art of well written embedded software is being lost because it would appear it has lost the art of excellence in its own right. NB Don’t try and argue with me sonny, I am a senior and know everything 😊
    Mark

    Like. Reply
  • J
    JDenenberg September 27, 2019

    My reason for teaching Assembly programming is to educate programmers on how a computer actually works.  Modern high level programming hides how a computer works from the programmer

    Like. Reply
  • tannenba September 27, 2019

    The problem I see (I used to program the old GE600 in assembly) is that assembly is device based. Ei: x86, arm, etc, which would mean a lot of learning for someone that programs multiple architecture devices, where knowing C or other compiler is pretty much the same over the same devices.  As an old fart, I have trouble remembering one device, let alone multiples.

    Like. Reply
  • M
    MichaelKlos September 27, 2019

    “It was not always like this. In the early days of embedded systems, code was all written in assembly language; that was the only option.” Not exactly true.  Even with the 8008 there was PLM.

    Like. Reply
  • M
    MichaelKlos September 27, 2019

    There have been many times in my career that Assembly was necessary for performance.  Especially when writing drivers in real time systems.

    Example: The Ubicom ip2022, an 8 bit CPU, used a serdes (16 bit) to implement Ethernet.  Being the shift register being only 16 bits, the processor had to service it every 1.6us.  And it had a lot to do in that time.

    I, personally, am tired of the idea of just throw a fast, or bigger, processor at the problem.  I’m tired of machines that have a hard time dealing with real-time events.  I am tired of the crap and bloat we get from companies like Microsoft.

    I write most of my code in C, but assembler is essential for many real-time performance tasks.

    Like. Reply
  • L
    LDSand September 27, 2019

    Can’t thank you enough for your excellent articles.  I’m an experienced assembly language programmer in Microchip’s processors, from back in the days when C compilers for it were few and far between.  I had to learn the disciplined habits of structured programming the hard way, grappling with the undisciplined arena of early assembler.  My career went off in other directions after that, but now many years later I need to do some fresh work with embedded controllers and I had always promised myself that I’d be working in some incarnation of C the next time.  I do have one comment to add about the comparative merits of C versus assembler: one of the problems some of my applications had was that repeated sampling in moving mechanical systems had to be precisely timed, which tends to discourage the widespread use of interrupts in the program.  Using assember it was easy to maintain very tight and precise control of processor tasks timing in that situation, far easier than it was to hold the reins on C generated code.  Libraries and processor power have increased so much since those days that it’s probably no longer much of a problem, but I do think having a detailed up close and personal insight into what the hardware is doing is of great help.  My assembly programs would always include a couple of test bits on one of the processor’s spare ports, the one serving as a strobe synced to the clock and the other outputting serial status codes, so I could hook a scope up and actually see what the processor was doing.  This proved to be an invaluable diagnostic tool when debugging the programs.  It did not replace the simulator and conventional code debugging but rather was an excellent guide to steer me quickly to where the program was running off the rails when (inevitably) early drafts of the programs would head off into la-la-land.  The tidbits of assembly code for such diagnostics are extremely easy to write in assembler.  Being able to get the best advantages of both programming approaches seems like just good sense.  One of the problems for an experienced tech looking to move into any new area is always to find a nice clear compact primer that is neither too introductory nor too advanced to be practical and useful.  Your series of articles here in AAC are an excellent resource.  Thanks again.  DS

    Like. Reply