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

  1. C (programming language)
  2. Flexible array member
  3. Mozilla-About_NSPR
  4. CMake
posted @ 2021-05-26 14:56  天存信息  阅读(768)  评论(0编辑  收藏  举报