编写平台特定代码的最佳(最干净)方式

Best (cleanest) way for writing platform specific code

本文关键字:方式 最佳 平台 代码      更新时间:2023-10-16

假设你有一段代码,它必须根据你的程序运行的操作系统而不同。
这是老派的做法:

#ifdef WIN32
   // code for Windows systems
#else
   // code for other systems
#endif

但是肯定有比这个更干净的解,对吧?

在我的职业生涯中,我在六家公司中亲眼看到的典型方法是使用硬件抽象层(HAL)。

这个想法是你把最低级别的东西放在一个专用的头文件和静态链接库中,其中包括如下内容:

  • 固定宽度整数(Linux上的int64_t, Windows上的__int64等)。
  • 常用库函数(strtok_r() vs strtok_s() Linux vs Windows).
  • 一个通用的数据类型设置(即:所有数据类型的typedefs,如xInt, xFloat等,在整个代码中使用,这样如果一个平台的底层类型改变,或者一个新平台突然被支持,不需要重写和重新测试依赖于它的代码,这在劳动力方面可能是非常昂贵的)。

HAL本身通常充满了预处理器指令,就像你的例子一样,这只是现实问题。如果用运行时if/else语句包装它,则由于无法解析的符号,编译将失败。或者更糟的是,您可能包含额外的符号,这会增加输出的大小,并且如果频繁执行该代码,可能会减慢程序的速度。

只要HAL编写得很好,HAL的头文件和库就会为您提供一个公共接口和一组数据类型,以便在您的其余代码中使用,从而减少麻烦。

从专业的角度来看,最美妙的方面是所有其他代码的都不必关注架构或操作系统的细节。您将在不同的系统上拥有相同的代码流,这将允许您以各种不同的方式测试相同的代码,并发现您通常不会期望或测试的错误。从公司的角度来看,这节省了大量的劳动力成本,并且不会因为客户对产品软件中的错误感到愤怒而失去客户。

在我的职业生涯中,我不得不做很多这样的事情,支持在嵌入式设备和windows上构建和运行的代码,然后还让它在不同的ASICS和/或ASICS的修订版上运行。

我倾向于做你建议的,然后当事情真的发生分歧时,继续定义我希望在平台之间固定的接口,然后有单独的实现文件甚至库。当代码库变旧,需要添加更多异常时,它会变得非常混乱。

有时你可以把这些东西隐藏在头文件中,这样你的代码看起来"干净",但很多时候,这只是混淆了一堆宏魔术背后发生的事情。

我要添加的唯一一件事是,如果没有定义任何选项,我倾向于使#ifdef/#else/#endif链失败。这迫使我在新版本出现时重新审视这个问题。有些人喜欢它有一个默认值,但我发现这只是隐藏潜在的失败。

当然,我在嵌入式世界中工作,代码空间是至关重要的(因为内存很小且固定),不幸的是,代码清洁度不得不退居次要位置。

对于重要的项目,采用的做法是在单独的文件中编写特定于平平台的代码(在适用的情况下,在单独的目录中),尽可能避免"本地化"#ifdef

假设你正在开发一个名为"Example"的库,example.hpp将是你的库头:

example.hpp

#include "platform.hpp"
//
// here: platform-independent declarations, includes etc
//

// below: platform-specific includes    
#if defined(WINDOWS)
#include "windowswin32_specific_code.hpp"
// other win32 headers
#elif defined(POSIX)
#include "posix/linux_specific_code.hpp"
// other linux headers
#endif

platform.hpp

(简化)
#if defined(WIN32) && !defined(UNIX)
#define WINDOWS
#elif defined(UNIX) && !defined(WIN32)
#define POSIX
#endif

win32_specific_code.hpp

void Function1();

win32_specific_code.cpp

#include "../platform.hpp"
#ifdef WINDOWS  // We should not violate the One Definition Rule
#include "win32_specific_code.hpp"
#include <iostream>
void Function1()
{
    std::cout << "You are on WINDOWS" << std::endl;
}
//...
#endif /* WINDOWS */

当然,也要在你的linux_specific_code.hpp文件中声明Function1()

然后,在Linux上实现它时(在linux_specific_code.cpp文件中),确保也为条件编译包围所有内容,类似于我上面所做的(例如。使用#ifdef POSIX)。否则,编译器会生成多个定义,你会得到一个链接器错误。

现在,库的用户必须做的所有事情都是在他的代码中#include <example.hpp>,并将#define WINDOWS#define POSIX放在编译器的预处理器定义中。实际上,假设他的环境已经定义了WIN32UNIX宏中的一个,那么第二步可能根本没有必要。这样,Function1()就可以在代码中以跨平台的方式使用了。


这种方法与Boost c++库中使用的方法非常相似。我个人觉得它既干净又明智。但是,如果您不喜欢它,您可以阅读Chromium的多平台开发惯例,以获得稍微不同的策略。