星标/置顶 公众号????,硬核文章第一时间送达!

链接 | http://blog.csdn.net/absurd

以前做过两年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。若你对目标平台常见的资源限制了然于胸,可能有很大的帮助,一些问题很容易定位。

可移植性的问题决不限于以上几种,一方面,即使以前遇到过的问题,部份已经忘记了。另外一方面,还有很多未知的问题,根本没有遇到过。这里算是抛砖引玉吧,请大家补充。

往期推荐

☞ 专辑 | 趣味设计模式

☞ 专辑 | 音视频开发

☞ 专辑 | C++ 进阶

☞ 专辑 | 超硬核 Qt

☞ 专辑 | 玩转 Linux

☞ 专辑 | GitHub 开源推荐

☞ 专辑 | 程序人生

关注公众「高效程序员」????,一起优秀!

回复 “入群” 进技术交流群,回复 “1024” 获取海量学习资源。

编写可移植 C/C++ 程序的一些要点相关推荐

  1. Microsoft编写优质无错C程序秘诀

      编程精粹 ───     Microsoft编写优质无错C程序秘诀 Writing Clean Code ───     Microsoft Techniques for Developing B ...

  2. 编程精粹 --Microsoft编写优质无错C程序秘诀

    献给我的妻子Beth, 以及我的双亲Joseph和Julia Maguire ────为了他们的爱和支持 序 1986年,在为几家小公司咨询和工作了10年之后为了获得编写Macintosh应用程序的经 ...

  3. 每次调试都必须clean_如何使用“ The Clean Architecture”每次编写健壮的应用程序...

    每次调试都必须clean by Daniel Oliveira 丹尼尔·奥利维拉(Daniel Oliveira) 如何使用" The Clean Architecture"每次编 ...

  4. nodejs命令行执行程序_在NodeJS中编写命令行应用程序

    nodejs命令行执行程序 by Peter Benjamin 彼得·本杰明(Peter Benjamin) 在NodeJS中编写命令行应用程序 (Writing Command-Line Appli ...

  5. python3编写人工智能_人工智能学习第三章 编写第一个Python程序 及概念

    接下来我们将看见如何在 Python 中运行一个传统的"Hello World"程序. 本章将会教你如何编写.保存与运行 Python 程序. 通过 Python 来运行的你的程序 ...

  6. 编写优质嵌入式C程序(转)

    前言:这是一年前我为公司内部写的一个文档,旨在向年轻的嵌入式软件工程师们介绍如何在裸机环境下编写优质嵌入式C程序.感觉是有一定的参考价值,所以拿出来分享,抛砖引玉. 转载请注明出处:http://bl ...

  7. 如何编写优质嵌入式C程序

    前言:这是一年前我为公司内部写的一个文档,旨在向年轻的嵌入式软件工程师们介绍如何在裸机环境下编写优质嵌入式C程序.感觉是有一定的参考价值,所以拿出来分享,抛砖引玉. 转载请注明出处:http://bl ...

  8. 《Java 开发从入门到精通》—— 2.2 编写第一段Java程序

    本节书摘来异步社区<Java 开发从入门到精通>一书中的第2章,第2.2节,作者: 扶松柏 , 陈小玉,更多章节内容可以访问云栖社区"异步社区"公众号查看. 2.2 编 ...

  9. 编写和调试Shader程序(1)

    编写和调试Shader程序 (1)DirectX EffectEdit JohnsonFeng 常用的Shader编写程序有ATI Render Monkey和NVIDIA FX Composer,另 ...

  10. Xamarin iOS编写第一个应用程序创建工程

    Xamarin iOS编写第一个应用程序创建工程 在Xcode以及Xamarin安装好后,就可以在Xamarin Studio中编写程序了.本节将主要讲解在Xamarin Studio中如何进行工程的 ...

最新文章

  1. Scott Mitchell 的ASP.NET 2.0数据教程之十一: 基于数据的自定义格式化
  2. 节点、支路、回路的概念
  3. 【Python】PyCryptodome模块实现多种加密算法
  4. Squid 2.6 Configuration Manual - Log File Path Names and Cache Directories
  5. 数据中台不是企业的万能妙药
  6. 微pe添加网络组件_Nature子刊 | 微塑料影响微生物群落和氮循环
  7. Spring声明式事务配置的两种策略SpringAop和Bean后处理器的代理BeanNameAutoProxyCreator
  8. 朴素贝叶斯分类算法(Naive Bayes)
  9. win7开启wifi共享(热点)
  10. 折腾黑苹果 - 制作四叶草CLOVER引导U盘
  11. 魔术师usm安装服务器系统,魔术师u盘一键装系统
  12. 陀螺仪mpu6050的使用(附带HAL的使用)
  13. 通俗理解LDA主题模型(转)
  14. centos5安装nagios
  15. UVa 10074 - Take the Land
  16. 重磅官宣:GitHub App 终于来了
  17. 用XFS保护你的知识产权
  18. CSS 属性 content 有什么作用? 有什么应用?(琐碎知识点整理)
  19. docker容器中解决中文乱码(详解)
  20. 前端10个灵魂拷问 吃透这些你就能摆脱初级前端工程师!

热门文章

  1. RainMeter学习3
  2. 阿里云cdn设置不缓存
  3. wait和notify,sleep
  4. 威联通NAS备份到百度网盘
  5. 解决XP IIS连接访问人数限制的问题
  6. 从0开始的技术美术之路(美术篇)(二)角色设计基础
  7. jOOQ 3.13.2 代码生成过程及解决 daos 无法生成的问题
  8. 21.0425开课吧开课前学习
  9. Canonical Coordinate System
  10. 路由器恢复出厂设置后dns服务器未响应,路由器恢复出厂设置后连不上网怎么办?...