嵌入式中编写可移植 C/C++ 程序的要点方法

news2024/9/21 14:42:59

 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

3、尽量使用标准 C/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。

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/675451.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

InstructGPT论文解读

介绍 上图可以看出InstructGPT(PPO-ptx)及变体(variant trained without pretraining mix)(PPO)显著优于GPT,1.3B的InstructGPT优于175B的GPT,SFT作为RLHF第一阶段的模型效果比GPT好。当然结果是由人来评价的。 RLHF包含三个步骤…

多EIP下的UDP通信异常分析

背景 SRE和程序在测试DDos多EIP防御方案的过程中,发现多EIP模式下, 监听的UDP端口连接会出现客户端访问异常。 表现为客户端发送一次数据后服务端这边主动断开了,或是客户端和服务端同时断开。 该问题会导致业务在多EIP方案下无法达到预期效果&#xff0…

【Visual Studio】Qt 获取系统时间,并实时更新时间,使用 C++ 语言,配合 Qt 开发串口通信界面

知识不是单独的,一定是成体系的。更多我的个人总结和相关经验可查阅这个专栏:Visual Studio。 这个需求来源于这个工程:【Visual Studio】Qt 的实时绘图曲线功能,使用 C 语言,配合 Qt 开发串口通信界面。 文章目录 Qt…

chatgpt赋能python:Python求和1-100的方法

Python求和1-100的方法 Python语言简介 Python是一个广泛使用的高级编程语言,其设计哲学强调代码可读性和语法简洁性。Python语言作为一门多范式的编程语言,支持对象、函数式和结构化编程等多种形式。Python应用领域广泛,如机器学习、桌面应…

chatgpt赋能python:Python求1!:介绍

Python求1!:介绍 在Python编程中,阶乘是一个常见的数学运算。阶乘指定的数的所有小于或等于其本身的正整数之积,例如,1!等于1,2!等于2乘以1,3!等于3乘以2乘以1,以此类推。 在这篇文…

【工程项目管理】工程项目管理实践报告

前言: 1.大学课程的大作业,觉得存起来也没什么用就干脆发出来了。。。 2.很可能有不严谨之处,各位看官如若发现欢迎指出~ 创作者文章管理系统 1 实践环节作业1:选题及任务分解WBS (1)选题 a.项目名称&a…

【1 beego学习 -MAC框架与ORM数据库】

0 beego的启动流程 1 入口 package mainimport ( //全局使用的路由和models_ "studyDemo/models"_ "studyDemo/routers"beego "github.com/beego/beego/v2/server/web" )func main() {beego.Run() }2 根据请求路由加载对应的控制器 package r…

【计算机组成原理】微程序控制器

目录 一、微程序控制器概述 二、微程序控制器设计方法 三、微指令执行过程 四、控制字段的编码方式 五、下址字段的设计方法 六、微程序入口地址的产生方法 一、微程序控制器概述 微程序:微指令构成的有序集合,一条指令对应一段微程序 微指令&…

【小沐学Android】Material Design设计规范之颜色篇

文章目录 1、简介1.1 Android1.2 Material Design 2、Material Design 12.1 材料设计2.2 颜色 3、Material Design 23.1 材料系统3.2 颜色 4、Material Design 34.1 颜色样式4.2 配色方案4.3 Material Theme Builder 结语 1、简介 1.1 Android 谷歌在2007年发布了第一个测试版…

chatgpt赋能python:Python清除代码:让你的项目更加优美

Python清除代码:让你的项目更加优美 随着时间推移和项目规模扩大,代码中可能会出现许多冗余、无用或重复的代码。这不仅会让代码难以维护,还会降低代码的性能和可读性。而Python作为一种高级编程语言,提供了许多工具和技术来清除…

牛客练习赛108 E.琉焰(非树边性质/线段树分治+可撤销并查集 or LCT)

题目 思路来源 官方题解 题解 针对每个连通块,单独考虑: 一方面, 任取连通块的某棵生成树, 对于任意非树边(u,v),把树边u到v上的所有边都选中,即被覆盖1次, 任取某个非树边集合S&#xff…

LangChain for LLM Application Development 基于LangChain开发大语言应用模型(下)

以下内容均整理来自deeplearning.ai的同名课程 Location 课程访问地址 DLAI - Learning Platform Beta (deeplearning.ai) LangChain for LLM Application Development 基于LangChain开发大语言应用模型(上) 一、LangChain: Q&A over Documents基于文…

bert4rec简介

1、bert4rec提出动机 用户行为动态变化,序列行为建模取得了不错的效果 单向结构限制了行为序列中隐藏信息的挖掘 序列神经网络顺序依赖,无法并行计算 为此,提出了 基于双向self-attention和Cloze task的用户行为序列建模方法。据我们所知…

解决Jenkins报错

解决Jenkins报错 1 linux空间不够问题1.1 报错现象1.2 定位问题1.3 解决措施 2 bash问题2.1 问题现象2.2 问题定位2.3 解决措施 3 虚拟环境问题3.1 问题现象3.2 问题定位3.3 解决措施 4 jenkins构建完成但一直转圈问题4.1 问题现象4.2 问题定位4.3 解决措施 5 jenkins自动化部署…

C高级6.24

一、整理grep、find、cut、tar、apt-get、dpkg、ln、ln-s指令 1.grep ----->查找字符串 grep 字符串 文件名 -w:按单词查找 -R:实现递归查找,主要用于路径是目录的情况 -i:不区分大小写 -n:显示行号 grep -w "^ubuntu" /etc/passwd ---->查找以ub…

【深度学习】RepVGG解析和学习体会

文章目录 前言0. Vgg1.RepVGG Block 详解 前言 论文名称:RepVGG: Making VGG-style ConvNets Great Again 论文下载地址:https://arxiv.org/abs/2101.03697 官方源码(Pytorch实现):https://github.com/DingXiaoH/RepV…

今天是世界Wi-Fi日!

很多人都不知道,今天其实是世界Wi-Fi日: 这个特殊的纪念日,是由无线宽带联盟(Wireless Broadband Alliance)确定的,并得到了互联城市咨询委员会 (CCAB)等组织的大力支持。 无线宽带联…

数据处理神器tidyverse!教你如何秒速搞定数据处理!

一、前言 在R语言中,tidyverse是一个庞大的数据分析生态系统,它由一系列数据可视化和数据处理软件包组成,能够极大地提高数据分析的效率和准确性。 在使用 Tidyverse 的过程中,我们会经常用到以下几个工具: ggplot2&am…

chatgpt赋能python:Python浮点数:介绍、精度和应用

Python浮点数:介绍、精度和应用 Python是一种高级编程语言,许多程序员使用Python编写计算机程序。与其他编程语言不同,Python是一种动态类型的语言,并且它处理浮点数时更加灵活。在本文中,我们将介绍Python浮点数的概…

python自动化办公——读取PPT写入word表格

Python自动化办公——读取PPT内容写入word表格 文章目录 Python自动化办公——读取PPT内容写入word表格一、需求分析二、导入依赖三、代码四、结果及总结 一、需求分析 📖由于我们知识图谱课程需要将课堂小组汇报的PPT总结成word文档,而我觉得一页一页复…