C 消除嵌入式软件中的硬件宏
我当时正在用C编写一个嵌入式程序 有很多像这样的硬件宏C 消除嵌入式软件中的硬件宏,c,unit-testing,refactoring,C,Unit Testing,Refactoring,我当时正在用C编写一个嵌入式程序 有很多像这样的硬件宏 #ifdef HardwareA do A #endif 它不可读,并且很难用单元测试覆盖所有不同的路径 因此,我决定将与硬件相关的代码移动到arch文件夹中,并使用makefile中的宏来决定链接哪个arch文件夹。就像在Linux内核代码中一样 但是当我看到Linux内核时,我注意到在arch文件夹中有太多的副本 当在一个硬件中发现错误,但可能影响所有其他硬件时,他们如何对所有相关硬件进行更改 我认为这样做将不可避免地在代码库中引
#ifdef HardwareA
do A
#endif
它不可读,并且很难用单元测试覆盖所有不同的路径
因此,我决定将与硬件相关的代码移动到arch文件夹中,并使用makefile中的宏来决定链接哪个arch文件夹。就像在Linux内核代码中一样
但是当我看到Linux内核时,我注意到在arch文件夹中有太多的副本
当在一个硬件中发现错误,但可能影响所有其他硬件时,他们如何对所有相关硬件进行更改
我认为这样做将不可避免地在代码库中引入重复项
有人有过这类问题的经验吗
如何对包含大量硬件宏的代码进行单元测试
重构代码以将硬件宏移出源代码?Linux试图避免代码在多个目录之间重复。您将看到实现了相同的函数,但实现方式不同。毕竟,所有体系结构都需要用于管理页表的代码,但细节有所不同。所以它们都有相同的函数,但定义不同
对于某些函数,构建系统定义了
CONFIG\u GENERIC.*
,它们也用通用版本(通常没有操作)替换不必要的架构挂钩。例如,没有FPU的arch不需要钩子来保存/恢复上下文开关上的FPU状态。Linux试图避免代码在多个arch目录之间重复。您将看到实现了相同的函数,但实现方式不同。毕竟,所有体系结构都需要用于管理页表的代码,但细节有所不同。所以它们都有相同的函数,但定义不同
对于某些函数,构建系统定义了CONFIG\u GENERIC.*
,它们也用通用版本(通常没有操作)替换不必要的架构挂钩。例如,没有FPU的arch不需要钩子来保存/恢复上下文开关上的FPU状态。这种类型的\ifdef
地狱肯定是要避免的,但自然也要避免代码重复。我不认为这能解决你所有的问题,但我认为你能做的最大的一步就是将你的#ifdef
从#ifdef HardwareX
改变为#ifdef HAVE_FeatureY
或#ifdef USE_FeatureZ
。这使您能够将哪些硬件/操作系统/等的知识从所有源文件中提取出来,并将哪些功能/接口分配到单个头文件中,从而避免了以下情况:
#if defined(HardwareA) || (defined(HardwareB) && HardwareB_VersionMajor>4 || ...
使源代码不可读。这种类型的#ifdef
地狱肯定是要避免的,但自然也要避免代码重复。我不认为这能解决你所有的问题,但我认为你能做的最大的一步就是将你的#ifdef
从#ifdef HardwareX
改变为#ifdef HAVE_FeatureY
或#ifdef USE_FeatureZ
。这使您能够将哪些硬件/操作系统/等的知识从所有源文件中提取出来,并将哪些功能/接口分配到单个头文件中,从而避免了以下情况:
#if defined(HardwareA) || (defined(HardwareB) && HardwareB_VersionMajor>4 || ...
使您的源代码不可读。听起来您正在替换这样的函数:
somefunc()
{
/* generic code ... */
#ifdef HardwareA
do A
#endif
/* more generic code ... */
}
somefunc()
{
/* generic code ... */
/* more generic code ... */
}
somefunc()
{
/* generic code ... */
do A
/* more generic code ... */
}
somefunc()
{
/* generic code ... */
do_A();
/* more generic code ... */
}
对于多个实现,每个arch文件夹中有一个,如下所示:
somefunc()
{
/* generic code ... */
#ifdef HardwareA
do A
#endif
/* more generic code ... */
}
somefunc()
{
/* generic code ... */
/* more generic code ... */
}
somefunc()
{
/* generic code ... */
do A
/* more generic code ... */
}
somefunc()
{
/* generic code ... */
do_A();
/* more generic code ... */
}
你担心的是通用代码的重复。不要这样做:相反,要有这样一个函数的实现:
somefunc()
{
/* generic code ... */
#ifdef HardwareA
do A
#endif
/* more generic code ... */
}
somefunc()
{
/* generic code ... */
/* more generic code ... */
}
somefunc()
{
/* generic code ... */
do A
/* more generic code ... */
}
somefunc()
{
/* generic code ... */
do_A();
/* more generic code ... */
}
…然后在arch文件夹中实现do_A()
:在硬件A上,它有该硬件的代码,而在其他硬件上,它是一个空函数
不要害怕空函数-如果将它们设置为arch头文件中定义的
内联
函数,它们将被完全优化。听起来像是在替换这样的函数:
somefunc()
{
/* generic code ... */
#ifdef HardwareA
do A
#endif
/* more generic code ... */
}
somefunc()
{
/* generic code ... */
/* more generic code ... */
}
somefunc()
{
/* generic code ... */
do A
/* more generic code ... */
}
somefunc()
{
/* generic code ... */
do_A();
/* more generic code ... */
}
对于多个实现,每个arch文件夹中有一个,如下所示:
somefunc()
{
/* generic code ... */
#ifdef HardwareA
do A
#endif
/* more generic code ... */
}
somefunc()
{
/* generic code ... */
/* more generic code ... */
}
somefunc()
{
/* generic code ... */
do A
/* more generic code ... */
}
somefunc()
{
/* generic code ... */
do_A();
/* more generic code ... */
}
你担心的是通用代码的重复。不要这样做:相反,要有这样一个函数的实现:
somefunc()
{
/* generic code ... */
#ifdef HardwareA
do A
#endif
/* more generic code ... */
}
somefunc()
{
/* generic code ... */
/* more generic code ... */
}
somefunc()
{
/* generic code ... */
do A
/* more generic code ... */
}
somefunc()
{
/* generic code ... */
do_A();
/* more generic code ... */
}
…然后在arch文件夹中实现do_A()
:在硬件A上,它有该硬件的代码,而在其他硬件上,它是一个空函数
不要害怕空函数-如果将它们设置为arch头文件中定义的
内联
函数,它们将被完全优化。我倾向于将特定于硬件的#定义移动到每个平台的头文件中,然后在所有源文件都包含的“platform.h”文件中选择它
平台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
特定于体系结构的标题将包含两个内容
1) typedef适用于所有常见的整数大小,如typedef short int16\t代码>请注意,c99指定了一个“stdint.h”,它具有这些预定义的属性。(切勿在可移植代码中使用原始int
)
2) 所有硬件特定行为的函数头或宏。通过提取函数的所有依赖项,代码主体保持干净:
//example data receive function
HW_ReceiverPrepare();
HW_ReceiveBytes(buffer, bytesToFetch);
isGood = (Checksum(buffer+1, bytesToFetch-1) == buffer[0])
HW_ReceiverReset();
然后,一个特定于平台的头可以向复杂的HW\u ReceiverPrepare()
函数提供原型,而另一个头只是用\define HW\u ReceiverPrepare()
这在您的评论中描述的平台之间的差异通常为一行或两行的情况下非常有效。只需将这些行封装为函数/宏调用,您就可以在最小化重复的同时保持代码的可读性。我倾向于将特定于硬件的#定义移动到每个平台的一个标头中,然后在所有源文件都包含的“platform.h”文件中选择它
平台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
特定于体系结构的标题将包含两个内容
1) typedef适用于所有常见的整数大小,如typedef short int16\t代码>注意