Some silicon vendors extend the C language so the programmers can easily interact with the silicon. Using these extensions tie production code to the silicon vendors compiler and consequently the code can only run on the target system. This is not a problem during production, but is a problem for off-target unit testing.
The good news is that we may be able to get around this problem without having to change production code, one of our goals when adding tests to legacy code.
Two common offenders are specialized C adornments like cregister
and interrupt
. cregister
might be used like this
//snip... /* Address Mode Register */ extern cregister volatile unsigned int AMR; //snip... |
interrupt
is used like this:
//snip... interrupt void one_ms_tic(void) { ... } //snip... |
These are pretty easy portability problems to get rid of. Use the C preprocessor to define cregister
and interrupt
to be nothing when compiling for off target execution with an include file like this:
#define cregister #define interrupt |
Now we have to go add this as the first line in every source file. What! never! you say. Yes that is too much trouble; let’s let the preprocessor do it for us using a forced include. If you are using CppUTest with the GCC toolchain, add this to your CppUTest based Makefile.
CPPUTEST_CPPFLAGS += -include mocks/hideStuff.h |
If you are not using CppUTest, but are using GCC, add the forced include to the CPPFLAGS
variable or somehow get it to your compilation command line.
If you are off-target testing with some form of Visual Studio, it too has a forced include control in the project settings.
I’ve shown another example of using forced includes in Spying on Embedded ‘asm’ directives.
OK, so we have work arounds for header file dependencies on the target compiler. I am hoping that you also know that you should limit your production code dependencies on the target-only specific constructs.
If this helped, tell me about it. If I’m missing something or there is a better way, tell me that too.
Typo detected:
#define interrup
I think you meant
#define interrupt
thanks
Great tip! I’m just starting to use TDD, with the help of your book, for 8051 C code, where there are several compiler extensions to deal with in this manner.
Hi James,
This is a good point. One thing to remember though that some compilers (Keil 8051 – I’m looking at you) declare some really general keywords like “data”. In these cases it is better to use an additional level of redirection (with a little bit more effort required).
ProductionMacros.h:
#define DATAMEM data
TestMacros.h
#define DATAMEM
Code.c:
int my_func(int DATAMEM x, int DATAMEM y){
}
Hi Mike
If I am creating new code with TDD, I would definitely use what you suggest. I would also be very careful to limit which files need specific hardware vendor compiler features. Given legacy code, I’d first try these compiler/preprocessor tricks to minimize code changes. Later, once the code is under test, we could start to fix the code for some of the sins of the past.
James
Nice post James.
As I also discussed on a LinkedIn forum recently, there is a huge payback to be gained from compiling and running code on a host system. One of the (many) great side effects of “writing on the PC first” (I even do this on tiny PIC projects) is that it forces you to come up with usable language abstractions that work across platforms, such as the ones you describe above. I have a cpu.h now that is force included on all my projects, and it selects the correct macro replacements for the chosen platform, where PLATFORM_XXX is defined in the compiler options to select the chosen platform.
One of the bigger challenges (especially when writing code that runs on multiple platforms, and compilers, as I often do), is those pesky #pragma statements. e.g. #pragma interrupt MyHandler 6 to set up interrupt vector 6 to MyHandler. A multi platform piece of library code often has lots of #ifdefs around the compiler specific #pragmas to support all the different platforms (I have a multi-platform pre-rolled rs232 driver that I port between all my projects, so you can imagine the mess in the interrupt setup part!)
It would be worth mentioning at this point that C99 introduced the _Pragma() unary operator that adds that all needed “extra level of indirection” as opposed to #pragma, allowing you to write (taken from C99 section 6.10.9):
#define LISTING(x) PRAGMA(listing on #x)
#define PRAGMA(x) _Pragma(#x)
….
LISTING(..\listing.dir)
David.
Hello,
Don’t know if this will be read, as the article is quite old, but:
I don’t understand how this should work. -include ” Process FILE as if ‘#include “file”‘ appeared as the first line of the primary source file.”. Any source file that uses one of the special keywords will thus include that header /after/ FILE and the keyword will be redefined (hopefully with a warning)…
Or are these vendors defining a real keyword in their own compiler? Then I’d understand…
Some compiler/silicon vendors add keywords and special mappings from keywords to hardware. The forced include is a way to edit the code on the fly converting the special keywords to standard constructs to allow the code to be compiled and then potentially tested. First step, get the code to compile.
You and other readers might find this recipe helpful too https://wingman-sw.com/articles/tdd-legacy-c.
Hi James, I found an interesting one while trying to attempt this
production code:
void foo(void) __attribute__ ((section (“.fasttext”)));
If I understand it correctly, the linker is trying to put “foo” into section “fasttext”
but not sure how to work around it. I tried defining it in a hidestuff.h file, but that wasn’t working.
#define __attribute__ “;//”
Hi Karl,
Try this:
#define __attribute__(goAway)