C与跨平台开发
在众多高级编程语言中,C语言历史悠久,且生命力旺盛,系统开发和应用开发兼具,是信息技术发展的一把利器。这里简单介绍一下C语言的发展及其对跨平台开发的影响。
C语言
C语言是在1969到1973年间,由贝尔实验室的 Dennis Ritchie 最初为重写unix操作系统而开发的,它成功替代了汇编语言开发操作系统的模式,随后得到了广泛飞速的发展。由于几大流行操作系统的内核(Linux、Windows等)都是由C开发的,所以称之为系统编程语言,其能力不局限于系统开发。常见的高级编程语言或脚本语言,像Java、Python、Perl和PHP等都是应用类编程语言,对开发人员来说,由这些语言编写的代码,不存在运行平台的问题,很多高级语言也是由C来编写的。
而与众多流行的高级编程语言相比,C语言是一种与平台真正相关的编程语言(C++可以认为是C的超集)。编译工具将C源代码翻译成某种机器指令集的二进制程序,这种程序只能在相应的操作系统和硬件平台上运行。Java程序则仅需一次编译,就可到处运行,与具体的硬件平台无关,唯一条件就是该平台上得有java虚拟机。
跨平台开发
跨平台开发,是指一套代码(或者一种业务)在多个平台上运行的编程方式,也是一种开发技巧。平台就是业务运行的环境,Windows、Linux和Unix等就是最典型的计算机操作系统平台,还有像浏览器IE、Chrome和Firefox等是一类应用平台;这些“平台”也有自己的运行“平台”,Windows可以运行在x86、amd64和arm等硬件平台上,Linux可以跑的更多;这里讨论的平台指操作系统,涉及的平台分Windows和Unix-like。各种Unix和各种Linux视为同宗,Portable Operating System Interface (POSIX)这套规范在Unix-like上表现的较为一致,Windows上也有支持,但其上的Win32 API功能更为丰富。
跨平台开发当然是为了满足业务发展的需要而进行的,当你的软件在Windows上已运行良好,但随Linux市场的兴起,你不得不开发Linux上的产品,在Linux平台上重造一个“轮子”,业务与Windows上运行的软件没有差异,只是换了个平台而已。由于平台的差异,操作系统提供的接口不同,开发人员根据不同的系统调用实现相同的业务需求。在开发过程中,自然而然地出现一种抽象层,将业务和运行平台进行分离。
像Java这样的高级语言可以算是高级抽象,使用这些应用类语言来编写软件不用考虑平台,只需关注业务,这是一种比较常用的开发模式。这样似乎没有必要使用C来做应用开发,但在实践当中,许许多多的基础部件:数据库MySql、WEB服务器Apache等都是C来开发的,因为C开发的软件开销少、运行效率高。
跨平台问题
C语言本应该是跨平台的,几乎每个平台都原生支持C开发环境。由于C编译器实现的差异性和操作系统的多样性,导致用C开发应用时存在跨平台运行问题。
有必要说一下C语言的几个主要标准的进化
- K&R C
经典C,事实标准,许多编译器的最低标准要求 - C89
标准C,大部分C代码都是C89兼容的 - C99
引入了非常多的新特性,有较多的c编译器提供支持,gcc就支持的很好,但微软公司对这个标准不那么热心,其集成开发工具Visual Studio 2013才开始比较良好地支持C99特性,这也成了软件从Linux系统移植到Windows平台的一个障碍。
新特性有:
- 宏定义支持取可变参数 #define Macro(…) _VAARGS
- 使用宏定义时,允许省略参数,被省略的参数会被扩展成空串
- 增加了内联函数
- 支持不定长的数组,即数组长度可以在运行时决定,比如利用变量作为数组长度。声明时使用 int a[var] 的形式。
- 变量声明不必放在语句块的开头,随用随定义;for 语句常写成 for(int i=0;i<100;++i) 的形式,即i 只在 for 语句块内部有效;微软的一些编译器不支持这样的书写方式。
- 允许在 struct 的最后定义的数组不指定其长度,写做 type name[] 的形式,主要用在不定长结构体的定义中,这个特性在应用中较为常见;
结构定义
struct vectord {
size_t len;
double arr[]; // the flexible array member must be last
};
这样使用
//申请内存尺寸 sizeof(struct) + array_len*sizeof(array element)
struct vectord *vector = sizeof(struct vectord) + array_len*sizeof(double);
vector->len = ...;
for (int i = 0; i < vector->len; i++)
vector[i] = ...
- 初始化结构的时候允许对特定的元素赋值,形式为:
(微软的一些编译器同样不支持。)
struct test{int a[3],b;} foo[] = { [0].a = {1}, [1].a = 2 };
// 3,4 是对 .c,.d 赋值的
struct test{int a, b, c, d;} foo = { .a = 1, .c = 3, 4, .b = 5 };
- 其他标准
C11等,如果是跨平台开发,似乎可以无视最近标准引入的新特性了。
我们在用C进行开发时,尽量使用C89标准和部分C99特性,在需要依赖操作系统平台特性时,通过宏来控制相应平台上的特殊代码——
#if defined(_WIN32)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#else
#include <unistd.h>
#if defined(unix)
#include <sys/param.h>
#endif
#endif#if defined(_WIN32)
#elif defined(_AIX)
#include <fcntl.h>
#include <sys/procfs.h> …
#elif defined(linux) …
#elif defined(__sun) && defined(__SVR4)…
#endif
宏定义是C语言的一个特色,功能很多;可以利用它针对特定平台编译特定代码,其他平台的代码不会编译连接到执行文件中,这样产生的程序规模就会小很多,同时产生了平台依赖。而java程序,如果要执行特定平台的业务,需要在运行时来检查当前运行的环境,再来做出选择。
当然,我们是站在巨人肩膀上进行软件开发的,不用亲自实现每项功能,在开源世界里有许许多多通用的、成熟的工具库可以使用。
- NSPR (NetScape Portable Runtime)
它为非GUI(图形界面)开发提供了一套平台独立的系统工具库,涉及的内容包括:
NSPR的目标是在各个操作系统环境提供统一的API,它不是努力输出各个操作系统的最广泛特性,而是提供最优解或者说是最佳实践,这些功能是现代操作系统的共有特性。如果出现新的操作系统,将NSPR移植到新平台的成功率是非常高的,主流系统NSPR均有支持。浏览器Firfox就用到了它。
该库虽历史悠久,但生命力强盛。接口设计的比较稳定,具有很好的二进制兼容性。- 线程
- 线程同步
- 文件和网络IO
- 时间
- 内存管理
- 共享库处理
- APR(Apache Portable Runtime)
Apache的跨平台库,除了基本的操作系统抽象外,还提供了比较丰富的工具。 - OpenSSL
网络安全通讯库 - libcurl
客户端网络通信开发库,支持非常多的网络协议,HTTP(S)、FTP(S)、POP3、SCP和SMTP等等。
很多工具库首先以C(或C++)的形式出现,然后再为其他高级语言提供功能扩展。
跨平台开发,除了语言层面上的,还有编译工具链的问题,涉及如何建立工程文件,使用什么编译器等等。CMake系统可以帮助解决跨平台工程文件构建问题,先为平台生成对应开发环境的工程文件,再由平台上的编译工具进行编译;为可以生成 visual studio 工程文件,也可以为Unix-like系统生成Makefile。
小结
C语言既可进行操作系统开发,也可进行应用开发,适用范围广泛,对C开发人员来说,想象力限制了开发能力。但它不是马斯洛大锤,所要解决的问题也不都是钉子。在实践中,需要在软件运行速度和开发效率等问题上取得平衡。(徐品华 | 天存信息)
Ref
- 点赞
- 收藏
- 关注作者
评论(0)