硬件层

每个硬件都有自己的命令集,os通过这些命令集控制硬件并与硬件通信

不同硬件部分的命令集数量和复杂度差别很大

通常,同样的硬件如果由不同的硬件厂商实现,会在标准的命令集外提供一些扩展来发挥这些硬件的特性

每个硬件的生产厂家都有很多,不可能每个程序编写者都针对这些硬件重写一遍程序

于是上帝说,要有操作系统,于是人类有了一个方便的访问硬件的统一平台

os

os 目的之一就是把底层硬件的特性给封装起来,然后提供一个统一的接口,便于人去控制硬件

现代os不再允许应用程序直接访问硬件,os直接访问硬件层时,称os处于内核态(kernel mode)

老版os,如MS-DOS,允许编程人员直接访问硬件

虽然可使写出来的程序能够在一段短的时间内应用硬件的某些高级特性,但长远来说,这个程序将不能在这个硬件的新版本上运行

API

现代应用程序如果想访问硬件,必须通过os,具体就是用os提供的API

一个API就是一些功能的统一接口,能把硬件的这些功能抽象地统一起来,让程序员集中精力实现他们想要的功能

应用程序已经不可能绕过现代os直接访问硬件,一种更普遍的说法就是应用程序以用户态(user mode)运行

MS Windows提供的API是一群C函数的集合。Windows平台的最低层次的开发语言就是C语言

平台软件开发包(Platform Software Development Kit)

MS提供的促使软件开发员在windows平台上开发软件

  1. 包含这些API生命的头文件
  2. 用于连接的相应的库文件(通过它们能够在相应的DLL中定位到这些API函数)
  3. 相关的说明文档
  4. 各种帮助工具

例,CreateFile,头文件”WinBase.h”中声明,通过库文件”Kernel32.lib”在dll文件中进行函数定位

在相应的文档里,会详细的说明每个函数所需要的头文件、库文件以及能够运行的相关平台

C运行时程序库

基于操作系统的API函数,软件厂商实现了C运行时程序库(CRT)

由一些头文件和相应的源文件构成,这些文件实现了一些基础的公共操作,如字符串操作、一些数学运算函数和基本的输入/输出等操作

通常厂商发布了一个C编译器,它会附带一些CRT库。一些国际标准化组织负责制定C语言的标准并实现一些运行时程序库

标准和扩展

理论上,如果一个程序使用标准C语言开发的,并且有一个平台支持其相应的标准C编译器和动态库,那么这个程序就能在这个平台上运行

但是实际上每个C编译器开发厂家都会对C语言做一些有利于自己的扩展,程序员可以很方便的利用这些扩展开发程序,但是付出的代价就是这个程序不再具有可移植性

CRT的函数名称一般都是小写的,宏和常数是大写的,而一些扩展则通常具有一定的以下划线作为开头的标识,例如函数_mkdir。针对这些扩展,相应的文档都会详细的予以说明

Unicode

PSDK支持Unicode

实际上,Win32 API CreateFile这些,仅仅是一些宏,可以在PSDK的头文件中找到这些宏对用的函数名称

真实名称是CreateFileA和CreateFileW。CreateFile 代表了“两个”函数名,是同一个函数在不同Win32函数的两个不同的版本

以’A’结尾的函数接受ANSI字符串,即Unicode字符串,即wchar_ts型字符串。两种版本的函数都在模块kernel32.dll中实现

如果编程环境是Unicode,则宏CreateFile在编译是会被CreateFileW代替,否则用CreateFileA代替

Windows操作系统有三大家族:MS-DOS/9x-based,Windows CE,Windows NT

  • MS-DOS/9x-based系列 实际最低支持16位的操作环境,也能运行一些受限制的32位应用程序。之所以受限制,就是它们只支持ANSI版本的win32函数

  • Windows NT系列是真正的32位操作系统。既支持Unicode版本的win32函数,也支持ANSI版本的win32函数。操作系统内部的字符串是Unicode型字符,ANSI型的Win32 API函数实际上是Unicode版本的函数的包装

  • Windows CE系列只支持Unicode版Win32 API

PSDK的字符串解决方案:TCHARs

避免为不同的windows操作系统开发不同版本的PSDK,微软制订了一个统一的字符串类型TCHARs

TCHAR以及其他的相应的宏在头文件WinNT.h中有定义。程序员在程序中不需要为使用char还是wchar_t而纠结,只需要使用宏TCHAR就可以了

根据Unicode环境是否存在,编译器会自动进行相应的转换

同样道理,程序员不需要为使用’A’还是’W’型Win32 API函数纠结

CRT的字符串解决方案:_TCHARs

PSDK采用了一种泛型文字转换技术,它能够根据具体的CRT环境实现文字类型转换

CRT用了一个额外的头文件”tchar.h”来实现这种泛型技术

为了和C语言标准兼容,所有非标准的名称都已下划线开头

所以,CRT使用宏_T来代替在文件”WinNT.h”中定义的宏TEXT()

CRT的开发者为了使得这个库能够广泛的应用开来,CRT能识别三种字符集:

  • SBCS - 单字节字符集。字符类型是char。一个char元素存储一个ASCII字符。不必为每个单独的工程定义一个字符集。这个字符集来源于上世纪70年代的C语言,0x00 - 0x7F留给英语字符集合,而剩余的0x80 - 0xFF就留给非英语字符集合使用,具体的这个集合中每个字符代表什么含义则由当前的active code page决定
  • MBCS - 多字节字符集。多字节string字符集也依赖char型字符类型。 一个多字节字符可能占用一个或两个char型元素空间存储。 如果要用多字节字符集,就需要在工程中定义宏_MBCS。宏_MBCS向后兼容SBCS模式,并且MS Visual C++ 8.0(2005)以前的版本都默认使用这种字符集。东亚语言,如日语、韩语和中文,一般采用这种字符集。现在,多字节字符集已经被Unicode字符集代替。过去在Windows 9x/Me平台上,只有多字节字符集才能处理东亚语言
  • UNICODE -统一编码字符集。一个Unicode字符占用一个wchar_t类型空间。windows上的wchar_t型一般占用16比特大小,所以在windows上可以使用使用大约65535个不同的字符。从MS Visual C++ 8.0 (2005)以后默认使用这种字符集

C++标准库

C++编程语言有其自己的一套标准库。这些库包含了一系列的类和函数,可以在编程中使用

通常,人们一提起C++标准库就下意识地直接联想到STL。STL最近的版本是作为C++标准库的一个子集发布的。但是现实中STL已经无处不在了,成了C++标准库的同义语

国际标准化组织IOS负责定义C++语言的标准以及其标准库

C++标准库的内容

  • .容器 一些普通的数据结构,如vector,set,list,map
  • 迭代器 提供统一的访问标准容器的方法
  • 算法 实现了一些常用的算法,一般都通过迭代器访问容器,而不是直接访问容器,所以一种算法可以作用于不同的容器
  • 分配器 负责容器内每个元素的内存空间的分配与回收
  • 函数对象以及Utilities 利用他们便于算法作用于各种容器
  • 字符流 把输入/输出流作为一个统一的对象处理
  • C动态运行库。由于要向后兼容C,所以CRT要作为C++标准库的一部分出现

代码重用

两种方式可以实现把CRT和C++库的代码嵌入一个项目中:静态链接和动态链接

针对CRT介绍,但是这些概念在C++标准库上也是同义的或者说是近似的

静态链接

静态链接CRT/C++库,程序启动时这些库的代码也一并会被加载紧内存中

优点

  • 使用方便。很容易把程序拷贝到目标计算机,并使它运行起来。不用为CRT/C++复杂的使用步骤担心

  • 不需要额外的文件。一个可执行文件,不用担心它的完整性遭到破坏

缺点

  • 通过静态链接方式实现的程序不能区分一个库的版本,即旧版本和新版本对它来说都是一样的

  • 容易导致多米诺效应

程序不可能完全由一个组织去实现。现代的软件项目非常复杂,严重依赖第三方的构件和库

所以一个软件一般被划分为若干个耦合关系非常松散的模块。如果静态链接到CRT中的某一部分,将严重影响到这些模块之间的互操作性,这也将迫使程序员依赖于这些模块之间的最大的公共部分

如 不同的CRT实例之间很难共享一个内置的CRT对象

一个CRT实例所分配的内存空间还必须由这个实例来释放,一个CRT实例所打开的文件也只能有这个实例来操作并完成关闭,等等

因为CRT只能内部操作这些获取的资源。任何一个CRT实例如果想释放另一个CRT实例的内存活通过FILE*指针访问另一个实例打开的文件都将改变被访问者的状态甚至导致被访问者的程序的崩溃

所以静态地使用CRT的程序员一般都要提供额外的函数来释放他所开发的模块所使用的资源,并且要求调用这个模块的人一定要记住通过这些函数来对这些资源进行释放,否则将造成内存泄露

如果不同的模块通过静态方式连接到CRT,则这些模块之间就不能共享STL的容器对象或C++对象

通过调用malloc函数来使用一段内存缓存

模块1以静态的方式连接到CRT,而模块2和3则以动态的方式连接到CRT,模块2和3之间可以相互传递CRT所用有的对象

模块3使用malloc所分配的一段内存空间可以由模块2释放,因为对malloc和free的调用都是由同一个CRT对象实现的

但是,模块1的资源就很难由其他模块释放。模块1所占有的每个资源都应该由它自己来释放。因为模块1是以静态的方式链接到CRT的一个实例

图中的模块2就必须通过调用模块1所提供的函数来释放它通过模块1所获取的内存空间

动态链接

则程序运行时只需要把很小一部分需要的导入库链接到程序中。这些导入库包含了说明CRT/C++的相应的函数的具体的实现的地址所在

程序启动时,程序会根据这些指令的说明把一些相关的dll加载进程序的内存空间中来

优点

  • 容易实现模块化,并且各个模块之间可以很方便的共享比较高级的对象数据
  • 更快的启动。系统启动时已经加载了这些CRT DLL,不仅节省了内存空间,更节省了页交换的时间

缺点

  • 部署起来比较复杂

这些CRT库必须被重新被分配一遍,而且为了一个程序能够工作,这些库在内存中必须有序地放置。这就需要一个额外的启动项目,并且部署的时候要小心谨慎

总结

对用户态的程序来说,Windows API是可以用到的计算机的最低一层

处于Windows API之上的则是C的动态运行库,它对操作系统进行了封装,并隐藏了不同的操作系统之间的差异

标准C++库则提供了更多的功能,并且把CRT作为它的一部分

通过标准的函数以及相关类可以写出跨平台的程序,这个程序只需在新的平台上重新编译一次发布出来,代码不需要改动