编写可移植C/C++程序的要点

发布时间:2023-09-26  

以前做过两年 ++ 移植工作,从 Win32 平台移植到 Linux 平台。大约有上百万行 /++代码,历时一年多。

本文引用地址:

在开发 Win32 版本时,已经强调了的可植性,无奈 Win32 团队里对 Linux 精通的人比较少,很多问题没有想到,直到后来移植工作开始时,才发现移植并非像想的那样简单。

后来,我发现大家对移植工程师都比较轻视,不管是从工资待遇还是管理层的态度来看都是这样。他们往往认为,你们不过是把别人实现好的东西移植过去罢了,你老老实实,按步就班去做就行了,根本不需要丝毫创意。

事实并非如此,特别是对于大项目,其中遇到的问题和困难可谓一言难尽。比如前面提到的那个项目,虽然过去好几年了,很多问题我仍然记忆犹新。

这里总结一些经验吧,这些经验,无一不是经过大量汗水换来的,有的引起的 BUG 甚至耗费数周时间才查出来。写出来,供类似的项目参考,不用再走这些弯路。

1、分层设计,隔离平台相关的代码。

就像可测试性一样,可移植性也要从设计抓起。一般来说,最上层和最下层都不具有良好的可移植性。

最上层是 GUI,大多数 GUI 都不是跨平台的,如Win32 SDK 和 MFC。最下层是操作系统API,大多部分操作系统API都是专用的。

如果这两层的代码散布在整个软件中,那么这个软件的可植性将非常的差,这是不言自明的。

那么如何避免这种情况呢?当然是分层设计了:最底层采用 Adapter 模式,把不同操作系统的 API 封装成一套统一的接口。至于封装成类还是封装成函数,要看你采用的 C 还是 写的了。

这看起来很简单,其实不尽然(看完整篇文章后你会明白的),它将耗去你大量的时间去编写代码,去测试它们。

采用现存的程序库,是明智的做法,有很多这样的库,比如,C 库有 glib(GNOME 的基础类), 库有 ACE(ADAPTIVE Communication Environment)等等,在开发第一个平台时就采用这些库,可以大大减少移植的工作量。

最上层采用 MVC 模型,分离界面表现与内部逻辑代码。把大部分代码放到内部逻辑里面,界面仅仅是显示和接收输入,即使要换一套 GUI,工作量也不大。这同时也是提高可测试性的手段之一,当然还有其它一些附加好处。

所以即使你采用 QT 或者 GTK+ 等跨平台的 GUI 设计软件界面,分离界面表现与内部逻辑也是非常有用的。若做到了以上两点,程序的可移植性基本上有保障了,其它的只是技术细节问题。

2、事先熟悉各目标平台,合理抽象底层功能。

这一点是建立在分层设计之上的,大多数底层函数,像线程、同步机制和 IPC 机制等等,不同平台提供的函数,几乎是一一对应的,封装这些函数很简单,实现 Adapter 的工作几乎只是体力活。

然而,对于一些比较特殊的应用,如图形组件本身,就拿 GTK+ 来说吧,基于 X Window 的功能和基于Win32的功能,两者差巨大,除了窗口、事件等基本概念外,几乎没有什么相同的,如果不事先了解各个平台的特性,在设计时就精心考虑的话,抽象出来的抽口在另外一个平台几乎无法实现。

3、尽量使用标准 C/ 函数。

大多数平台都会实现 POSIX(Portable Operating System Interface)规定的函数,但这些函数较原生(Native) 函数来说,性能上的表现可能较次一些,用起来也不如原生函数方便。

但是,最好不要贪图这种便宜而使用原生函数函数,否则搬起的石头最终会轧到自己的脚。比如,文件操作就用 fopen 之类的函数,而不要用 CreateFile 之类的函数等。

4、尽量不要使用 C/C++ 新标准里出现的特性。

并不是所有的编译器都支持这些特性,像 VC 就不支持 C99 里面要求的可变参数的宏,VC 对一些模板特性的支持也不全面。为了安全起见,这方面不要太激进了。

5、尽量不要使用 C/C++ 标准里没有明确规定的特性。

比如你有多个动态库,每个动态库都有全局对象,而且这些全局对象的构造还有依赖关系,那你迟早会遇到麻烦的,这些全局对象构造的先后顺序在标准里是没有规定的。在一个平台上运行正确,在另外一个平台上可能莫明其妙的死机,最终还是要对程序作大量修改。

6、尽量不要使用准标准函数。

有些函数大多数平台上都有,它们使用得太广泛了,以至于大家都把它们当成标准了,比如 atoi(把字符串转换成整数)、strdup(克隆字符串)、alloca(在栈分配自动内存)等等。不怕一万,就怕万一,除非明白你在做什么,否则还是别碰它们为好。

7、注意标准函数的细节。

也许你不相信,即使是标准函数,抛开内部实现不论,就其外在表现的差异也有时令人惊讶。这里略举几个例子:

(1) int accept(int s, struct sockaddr *addr, socklen_t *addrlen);addr/ addrlen本来是输出参数,如果是 C++ 程序员,不管怎么样,你已经习惯于初始化所有的变量,不会有问题。如果是 C 程序员,就难说了,若没有初始化它们,程序可能莫名其妙的 crash,而你做梦也怀疑不到它头它。这在 Win32 下没问题,在 Linux 下才会出现。

(2)int snprintf(char *str, size_t size, const char *format, ...);第二个参数size,在 Win32 下不包括空字符在内,在 Linux 下包括空字符,这一个字符的差异,也可能让你耗上几个小时。

(3) int stat(const char *file_name, struct stat *buf);这个函数本身没有问题,问题出在结构 stat 上,st_ctime 在 Win32 下代表创建(create)时间,在 Linux 下代表最后修改(change)时间。

(4)FILE *fopen(const char *path, const char *mode);在读取二进制文件,没有什么问题。在读取文本文件可要小心,Win32下 自动预处理,读出来的内容与文件实际都长度不一样,在 Linux 则没有问题。

8、小心数据标准数据类型。

不少人已经吃过 int 类型由 16 位转变成 32 位带来的苦头,这已经是陈年往事了,这里且不谈。

你可知道 char 在有的系统上是有符号的,在有的系统是无符号的吗?你可知道 wchar_t 在 Win32 下是 16 位的,在 Linux 下是 32 位的吗?你可知道有符号的 1bit 的位域,取值是 0 和 -1 而不是 0 和 1 吗?这些貌合神离的东东,端的是神出鬼没,一不小心着了它的道。

9、最好不要使用平台独有的特性。

比如 Win32 下 DLL 可以提供一个 DllMain 函数,在特定的时间,操作系统的 Loader 会自动调用这个函数。这类功能很好用,但最好不要用,目标平台可不能保证有这种功能。

10、最好不要使用编译器特有的特性。

现代的编译器都做很人性化,考虑得很周到,一些功能用起非常方便。像在 VC 里,你要实现线程局部存储,你都不调用 TlsGetValue /Tls TlsSetValue 之类的函数,在变量前加一个 __declspec( thread ) 就行了,然而尽管在 pthread 里有类似的功能,却不能按这种方式实现,所以无法移植到 Linux 下。同样 gcc 也有很多扩展,是在 VC 或者其它编译器里所没有的。

11、注意平台的特性。

比如:在 Win32 下的 DLL 里面,除非明确指明为 export 的函数外,其它函数对外都是不可见的。而在 Linux 下,所有的非 static 的全局变量和函数,对外全部是可见的。这要特别小心,同名函数引起的问题,让你查上两天也不为过。

(1)目录分隔符,在 Win32 下用’//’,在 Linux 下用’/’。

(2)文本文件换行符,在 Win32 下用’/r/n’,在 Linux 下用’/n’,在 MacOS 下用’/r’。

(3)字节顺序(大端/小端),不同硬件平台的字节顺序可能不一样。

(4)字节对齐,在有的平台(如x86)上,字节不对齐,无非速度慢一点,而有的平台(如arm)上,它完全用错误的方式去读取数据,而且不会给你一点提示。若出问题,可能让你一点头绪都没有。

12、最好清楚不同平台的资源限制。

想必你还记得 DOS 下同时打开的文件个数限制在几十个的情形吧,如今操作系统的功能已经强大多了,但是并非没有限制。比如 Linux 下的共享内存默认的最大值是 4M。

若你对目标平台常见的资源限制了然于胸,可能有很大的帮助,一些问题很容易定位。

可移植性的问题决不限于以上几种,一方面,即使以前遇到过的问题,部份已经忘记了

另外一方面,还有很多未知的问题,根本没有遇到过。这里算是抛砖引玉吧,请大家补充。

文章来源于:电子产品世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。

相关文章

    要优点是占用资源少、程序执行效率高。但是不同的CPU,其汇编语言可能有所差异,所以不易移植C语言是一种结构化的高级语言。其优点是可读性好移植容易,是普遍使用的一种计算机语言。缺点是占用资源较多,执行......
    化数据结构:Rust支持泛型 (generic) 和特征 (trait) 等数据结构概念,提高了源代码的可维护性。 虽然C语言存在诸多缺陷,但它仍然是当今的主流语言。因此,Rust的创建者确保了这种新兴语言可......
    单片机编程用C语言还是汇编?;单片机是一种可编程器件,单片机的出现使硬件设计变得更为简单,产品的功能也更强大,而程序就是单片机的灵魂。目前功能稍微复杂一点的电子产品,都是以单片机为核心,再加......
    LED闪烁(2024-08-16)
    ;             delay(60000);          } } typedef unsigned int u16; 这个定义是为了增强程序的可移植性的,现在是运行在89c52上,今后可能移植到stm32上,这两......
    些不知道从哪里生出来的。 所以说了这么多,为了代码的通用可移植性,建议采用标准提供的形式,还是推荐使用标准文档中规定的这两种写法。如果一个函数确定无需传入任何参数,那么用void限定是一个不错的选择。 所以......
    上面的知识和编写启动代码这项准备工作完成后,就可以进入具体移植阶段了。主要完成以下工作: ① 为了增强代码的可移植性,所有C文件添加头文件includes.h。 ② 用户程序添加config.h。 ③ 在文件OS_CPU.H中需......
    、G0等)就只有STM32Cube HAL 和 LL库了。 5 四种库对比 来自官方的对比信息,包含可移植性、优化、难以程度等。 其中: Portability:可移植性 Optimization......
    义在各自的.c文件里。 跟我做的那个太阳能热水器控制板的程序对比,虽然全局变量的数量可能没变,但是很明显模块化的写法更加清晰。 当然,这不是让代码看起来更清爽这么简单,还有功能可扩展性强,可移植性......
    议大家都学习一下。而且新出来的型号(如L5、G4等系列)没有标准外设库,只有STM32Cube HAL 和 LL库了。 4、四种库对比 来自官方的对比信息,包含可移植性、优化、难易程度等。 其中......
    ,FreeRTOS是一个非常受欢迎的嵌入式实时操作系统,因为它简单易用,资源占用小,功能丰富,可移植性好,对于嵌入式系统开发非常有帮助。 FreeRTOS相对于其他操作系统有什么特点 FreeRTOS......

我们与500+贴片厂合作,完美满足客户的定制需求。为品牌提供定制化的推广方案、专属产品特色页,多渠道推广,SEM/SEO精准营销以及与公众号的联合推广...详细>>

利用葫芦芯平台的卓越技术服务和新产品推广能力,原厂代理能轻松打入消费物联网(IOT)、信息与通信(ICT)、汽车及新能源汽车、工业自动化及工业物联网、装备及功率电子...详细>>

充分利用其强大的电子元器件采购流量,创新性地为这些物料提供了一个全新的窗口。我们的高效数字营销技术,不仅可以助你轻松识别与连接到需求方,更能够极大地提高“闲置物料”的处理能力,通过葫芦芯平台...详细>>

我们的目标很明确:构建一个全方位的半导体产业生态系统。成为一家全球领先的半导体互联网生态公司。目前,我们已成功打造了智能汽车、智能家居、大健康医疗、机器人和材料等五大生态领域。更为重要的是...详细>>

我们深知加工与定制类服务商的价值和重要性,因此,我们倾力为您提供最顶尖的营销资源。在我们的平台上,您可以直接接触到100万的研发工程师和采购工程师,以及10万的活跃客户群体...详细>>

凭借我们强大的专业流量和尖端的互联网数字营销技术,我们承诺为原厂提供免费的产品资料推广服务。无论是最新的资讯、技术动态还是创新产品,都可以通过我们的平台迅速传达给目标客户...详细>>

我们不止于将线索转化为潜在客户。葫芦芯平台致力于形成业务闭环,从引流、宣传到最终销售,全程跟进,确保每一个potential lead都得到妥善处理,从而大幅提高转化率。不仅如此...详细>>