1

I was working on an embedded program using C.

There are tons of hardware macros like

#ifdef HardwareA 
do A 
#endif

It's not readable, and hard to cover all the different paths with unit tests.

So, I decided to move the hardware related code to arch folders, and using macros in the makefile to decide which arch folder is linked. Like in the Linux kernel code.

But when I saw the Linux kernel, I noticed there are so many duplicates in the arch folders.

How do they make the changes to all related hardware when a bug was found in one hardware, but might affect all others?

I think doing this way will inevitably bring duplicates into the code base.

Does anyone have experience with this type of problem?

How to unit test on code which has lots of hardware macros?

Refactoring the code to move hardware macros off source code?

Chris Zheng
  • 1,509
  • 1
  • 15
  • 20
  • Is there anyone have the experience of unit testing on the program which have different branches by different hardware, how to cover different path? Thx. – Chris Zheng Aug 26 '11 at 01:55

4 Answers4

3

It sounds like you are replacing a function like this:

somefunc()
{
    /* generic code ... */

    #ifdef HardwareA 
    do A 
    #endif

    /* more generic code ... */
}

with multiple implementations, one in each arch folder, like this:

somefunc()
{
    /* generic code ... */

    /* more generic code ... */
}

somefunc()
{
    /* generic code ... */

    do A 

    /* more generic code ... */
}

The duplication of the generic code is what you're worried about. Don't do that: instead, have one implementation of the function like this:

somefunc()
{
    /* generic code ... */

    do_A();

    /* more generic code ... */
}

..and then implement do_A() in the arch folders: on Hardware A it has the code for that hardware, and on the other hardware, it is an empty function.

Don't be afraid of empty functions - if you make them inline functions defined in the arch header file, they'll be completely optimised out.

caf
  • 233,326
  • 40
  • 323
  • 462
2

Linux tries to avoid code duplicated between multiple arch directories. You'll see the same functions implemented, but implemented differently. After all, all architectures need code for managing the page tables, but the details differ. So they all have the same functions, but with different definitions.

For some functions, there are CONFIG_GENERIC_* defined by the build system that replace unnecessary architecture hooks with generic versions as well (often no-ops). For example, an arch without a FPU doesn't need hooks to save/restore FPU state on context switch.

bdonlan
  • 224,562
  • 31
  • 268
  • 324
  • Problems I facing is a little different with Linux kernel, generic code is dominated, and hardware related code is really less, in hardware A, just one line code added, for another hardware B, two lines of code, so, it is really hard for me to extract some common interface or function out of it and put them in arch folder, how do you think? – Chris Zheng Aug 23 '11 at 06:12
1

This kind of #ifdef hell is definitely to be avoided, but naturally you also want to avoid code duplication. I don't claim this will solve all your problems, but I think the single biggest step you can make it changing your #ifdefs from #ifdef HardwareX to #ifdef HAVE_FeatureY or #ifdef USE_FeatureZ. What this allows you to do is factor the knowledge of which hardware/OS/etc. targets have which features/interfaces out of all your source files and into a single header, which avoids things like:

#if defined(HardwareA) || (defined(HardwareB) && HardwareB_VersionMajor>4 || ...

rendering your sources unreadable.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • I don't get it, use feature instead of hardware, still need to ifdef to decide which code on or off, do you mean we can move them to a certain header file? How to do that? Thanks. – Chris Zheng Aug 23 '11 at 05:23
1

I tend to move the hardware specific #defines into one header per platform, then select it in a "platform.h" file, which all source files include.

platform.h:

#if defined PLATFORM_X86_32BIT
#include "Platform_X86_32Bit.h"
#elsif defined PLATFORM_TI_2812
#include "Platform_TI_2812.h"
#else
#error "Project File must define a platform"
#endif

The architecture specific headers will contain 2 things.

1) Typedefs for all the common integer sizes, like typedef short int16_t; Note that c99 specifies a 'stdint.h' which has these predefined. (Never use a raw int in portable code).

2) Function headers or Macros for all the hardware specific behavior. By extracting all the dependencies to functions, the main body of code remains clean:

  //example data receive function
  HW_ReceiverPrepare();
  HW_ReceiveBytes(buffer, bytesToFetch);
  isGood = (Checksum(buffer+1, bytesToFetch-1) == buffer[0])
  HW_ReceiverReset();

Then one platform specific header may provide the prototype to a complex HW_ReceiverPrepare() function, while another simply defines it away with #define HW_ReceiverPrepare()

This works very well in situations like the one described in your comment where the differences between platforms are usually one or two lines. Just encapsulate those lines as function/macro calls, and you can keep the code readable while minimizing duplication.

AShelly
  • 34,686
  • 15
  • 91
  • 152