Choosing a Compiler: The Little Things
Pages: 1, 2
Assembly Language Generation
Can your current cross compiler generate an assembly-language listing? Mine can. A command-line argument causes this compiler to produce assembly-language listings as part of the compilation process. Each input C/C++ source file results in a single assembly-language file being created.
The assembly language files contain the results of each compilation, exactly as the target processor will execute it. But this listing is in a human-readable form. The original C/C++ code is provided in comments that are interspersed with the assembly. Each line of source code is followed by the compiled result.
I find this feature very helpful for manual code optimization. This is because you can easily see what code is produced for each line of your high-level language program. And if a particular function is too slow for a given application, you'll be able to easily select the best part of the function to rewrite in assembly.
|
Related Articles: Manipulating Fixed-Width Integer Data Types |
Standard Libraries
When you're developing application software for a general-purpose computer,
you expect that your compiler will include a set of standard C libraries, math
libraries, and C++ classes. These include various routines like
memcpy(), sin(), and cout, respectively. But because
the functions in these libraries are not strictly part of the C or C++ language
standards (the library standards are separate), a compiler vendor may choose to
omit them. Such omissions are more common among vendors of the cross compilers
used by embedded systems programmers. So in many cases, you've got to fight for
your right to the standard libraries.
Just think about how much time you'd spend rewriting all of those functions
yourself. Then spend some of that time insisting that standard libraries be
included with any compiler that you buy. Of course, it's unlikely you'll be
using printf() in the majority of embedded systems applications. But
you may not realize just how many of the other functions you will need until
it's already too late.
If standard libraries are provided, be sure that they are reentrant. In other words, that each of the functions within those libraries can be executed multiple times simultaneously. Reentrant functions can be called recursively or from within multiple threads of execution. What this means in the case of a library routine is that it may not use global variables. All of its internal data must be on the stack.
Fortunately, if you absolutely must buy a compiler that does not come with standard libraries, there is an alternative. Cygnus has put together a standard C library and math library specifically for embedded systems. All of the functions include source code and are designed with reentrancy in mind. And porting these libraries to new platforms is made easier by a design that puts all board and processor-specific interfaces into a single directory. The package is called newlib and the latest version is available for download at ftp://ftp.cygnus.com/pub/newlib/.
Startup Code
Another thing that non-embedded software development tools usually do for you
automatically is to include startup code. Startup code is an extra piece of
software that executes prior to main(). The startup code is generally
written in assembly language and linked with any executable that you build. It
prepares the way for the execution of programs written in a high-level language.
Each such language has its own set of expectations about the run-time
environment in which programs are executed. For example, many languages utilize
a stack. Space for the stack must be allocated and some registers or data
structures initialized before software written in the high-level language can be
properly executed.
Startup code for an embedded system should be provided with the cross compiler. If the compiler is designed to be used for embedded software development, it generally will be. But it is also important to consider whether this code and its proper use are well documented. The startup code will likely be written in the assembly language of your target processor and should, ideally, be provided to you in source code form. If properly implemented, you shouldn't ever need to make any changes, but it's still helpful to look at it and understand what it does.
Startup code for C/C++ usually performs the following actions:
- Disable interrupts
- Copy any initialized data from ROM to RAM
- Zero the uninitialized data area
- Allocate space for and initialize the stack
- Create and initialize the heap
- Execute the constructors and initializers for all global variables (C++ only)
- Enable interrupts
- Call
main()
To properly utilize the compiler-supplied startup code, you'll need to know how it should be linked with your program. You'll also need to know where and how to place the initialized data in ROM (so they can be copied to RAM) and how to set the size of the stack and heap. In the best case, these steps are documented in the literature provided by the compiler vendor. But I have seen many cases in which they were not.
| Target Processor |
| The first step in selecting a cross compiler is finding one that will produce code for your target processor. |
| Host Platform |
| The next step is to decide on a development platform. If there are several platforms available, you may want to check some of the other items in this list before making a decision about the host. |
| RTOS Support |
| If you're planning to use an RTOS, does the compiler vendor have a working relationship with your RTOS vendor? This is important because part or all of the RTOS may be provided in object files or libraries. For compatibility, your compiler and linker must support that same object file format. |
| Integration with Other Tools |
| Is the compiler compatible with any debugging environments? Is a make utility included? If the compiler is shipped with an IDE, is it extensible so that you can integrate your version control tool? |
| Standard Libraries |
| Will you need functions from the standard C library, math library, or C++ classes? If so, are they provided with the compiler? Are all of the functions in those libraries reentrant? |
| Startup Code |
| Is startup code for embedded systems provided? Are the code and its use well documented? If you can't find any mention of startup code in the user's manuals for a potential cross compiler, consider that a bad sign. |
| Execution Speed Optimizations |
| If your program is too slow, you'll want the compiler to try to speed it up. Will the compiler do this? If so, what specific optimizations are supported? Can they be individually enabled or disabled? |
| Program Size Optimizations |
| If your program is too big for your target memory, you'll want the compiler to attempt to reduce the amount of code space used. Will the compiler do this? If so, what specific optimizations are supported? |
| Support for Embedded C++ |
| The Embedded C++ (EC++) standard is a proper subset of the C++ language and libraries that reduces run-time overhead. In order to restrict yourself to EC++ functionality, you'll need a cross compiler that knows what features of the language it is not allowed to use. |
Details, Details, Details
Obviously, a compiler that lacks some of the features I've mentioned above may still be a good compiler. And there will undoubtedly be a few people who would argue that non-standard features like the 'asm' and 'interrupt' keywords reduce code portability. But if you've got a choice between two or more cross compilers that are otherwise equivalent, you may want to look for these things. Even if they aren't strictly required to get the job done, they may just make your work easier. And they will certainly reduce programmer frustration.
Michael Barr is a leading authority on the design of embedded computer systems. He has provided expert testimony in court, appeared on the PBS show "American Business Review", and been quoted in newspaper articles. Barr is also the author of more than forty technical articles, co-author of the "Embedded Systems Dictionary", and founder of Embedipedia.net.
This article was originally published in the May 1999 issue of Embedded Systems Programming.
O'Reilly & Associates published Programming Embedded Systems in C and C++ in January 1999.
You can look at the Table of Contents, the Index, and the Full Description of the book.
For more information, or to order the book, click here.
Return to the O'Reilly Network.



