本节书摘来自华章计算机《C语言编程魔法书:基于C11标准》一书中的第1章,第1.1节,作者: 陈轶 更多章节内容可以访问云栖社区“华章计算机”公众号查看。
第一篇 预备知识篇
第1章 C魔法概览
本章内容主要对C编程语言(以下简称C语言)进行大体介绍,包括它的历史以及C语言标准的演化进程。然后介绍一下C语言编程思想,当前主流C语言编译器以及GNU语法扩展。最后简单介绍一下从用C语言编写程序到编译、构建一个可执行程序的大致过程。
计算机编程语言从对计算机硬件底层的抽象程度进行分类,可分为:机器语言、汇编语言以及高级语言。下面由底层到高层分别介绍这几种类别的编程语言。
1.1 例说编程语言
1)机器语言是直接通过十六进制数表示当前处理器架构的机器指令码。指令码包含了当前指令的功能(比如算术逻辑运算、移位、分支、中断、I/O等)、寄存器、立即数等多种元素。每种处理器架构所对应的机器码的字节长度也各不相同,有些是固定长度的(比如ARM、MIPS等架构),有些是可变长度的(比如x86架构)。
2)汇编语言(Assembly Language)通过简单的指令助记符(memonics)来表示对应机器指令的功能、寄存器编号、立即数(immediates)等元素。汇编语言是对机器指令的简单抽象,通过汇编器(assembler)可以将汇编语句翻译成对应的机器指令码。
3)高级语言的表达形式更为抽象且贴近我们日常的语言表述。而且,高级语言比起汇编语言往往更具有表达力,且拥有更加丰富的语法特性,以便将程序进行结构化和模块化。比如,高级语言具有自定义变量标识符、自定义数据结构、分支与循环、更形象自然的表达式等。高级语言一般通过编译器(compiler)可直接将表达式翻译为对应的机器指令码;也可以将高级语言先翻译为中间语言(类似于汇编,但可能比汇编适用范围更广、更利于跨平台的字节码),最后将中间语言翻译为最终的机器指令码。
当然,有些书中还介绍了第四代语言,它基于高级语言,比高级语言更抽象,只需要一些简单的描述语句就能让计算机做比较复杂的工作。比如SQL(结构化查询语言,用于数据库查询)算是一种第四代语言。
下面,为了能让大家对这三种层次的编程语言有一个感性的认识,这里将列举ARMv8架构处理器下的机器语言、汇编语言,加上它们相应的C语言。读者如果手头有Xcode,并且有包含Apple A7或更高版本处理器的iOS设备的话,可以直接编译运行,并能看到最终效果。
下面首先列出一个文件名为my_sub.s的汇编源文件,其中包含了机器语言和汇编语言。见代码清单1-1:
代码清单1-1 机器语言与汇编语言
.text
.align 4
#ifdef __arm64__
.globl _my_sub_machine
.globl _my_sub_assembly
// 用机器语言实现减法操作
_my_sub_machine:
.long 0x4b010000
.long 0xd65f03c0
// 用汇编语言实现减法操作
_my_sub_assembly:
sub w0, w0, w1
ret
#endif
在代码清单1-1中,_my_sub_machine程序片段中的两条.long语句即为机器指令。这两条机器指令正好与_my_sub_assembly中的两条汇编指令相对应。也就是说,“0x4b010000”这串32位的十六进制代码意思就是“sub w0, w0, w1”,表示将寄存器w0与寄存器w1的值进行相减,然后将结果写回w0寄存器中。而“0xd65f03c0”指令码对应于“ret”(更确切地说是ret x30),表示返回当前过程(procedure)。在汇编语言中,一般会使用过程或者例程(routine)来表示一个可执行的程序片段。在C语言中一般都用函数(function)表示。我们在这里能够明显看到,汇编语言采用指令助记符的方式比写机器指令码要直观得多,而且也不容易出错。“sub”指令的功能从助记符上就能知道是“减法”功能;而w0、w1也明确指明了使用的寄存器是w0和w1。这些在“0x4b010000”这种机器指令码上都无法直观地表现出来。
代码清单1-2列出C语言是如何表达一个减法操作的。
代码清单1-2 减法操作对应的C语言
static int my_sub_c(int a, int b)
{
return a - b;
}
代码清单1-2所列出的C语言代码与代码清单1-1中的机器指令码和汇编语言完全对应,意思一目了然——将参数变量a的值与参数变量b的值进行相减,然后将结果返回。从这里我们就能看到机器语言、汇编语言以及以C语言为代表的高级语言之间在表达力上的差距了。高级语言的目的就是为了给程序员提供更良好的编程工具,更简洁、更富有表达力的语言,使得我们程序员能提升生产力,并且能构思出更多精彩炫酷的应用,而不是把太多的精力都投入在如何让计算机执行的细节上。
代码清单1-3能让我们在主函数或其他函数中测试上述已经编写好的函数。
代码清单1-3 展示减法操作的结果
#ifdef __arm64__
extern int my_sub_machine(int a, int b);
extern int my_sub_assembly(int a, int b);
int result_machine = my_sub_machine(10, 2);
int result_assembly = my_sub_assembly(5, 3);
int result_c = my_sub_c(6, 2);
printf("Three results: %d, %d, %d\n", result_machine, result_assembly, result_c);
#endif
执行了上述代码之后,我们最后能在控制台看到输出结果:“Three results: 8, 2, 4”。可见,上述三种不同的编程语言,计算功能是完全一致的,都是对两个输入参数做减法操作,然后返回差值。然而就可读性、可理解性以及编程便利性而言,显然C语言比起其他两者要强得多。而可读性最差的无疑就是机器指令码了。
1.C语言的类别与产生
对于高级语言来说,从表达上又可分为命令式编程语言(imperative programming language)和陈述型编程语言(declarative programming language)。命令式语言主要包括过程式(procedural)、结构化(structured)以及面向对象(object-oriented)的编程语言;陈述型编程语言主要包括函数式(functional)以及逻辑型(logical)编程语言。而C语言则属于结构化的命令式编程语言。不过现在很多命令式编程语言也包含了一些函数式编程语言的特征。在本书中,后面第18章中谈到的Blocks语法就是一个很典型的函数式编程语言的语法。
C语言最初由Dennis Ritchie于1969年到1973年在AT&T贝尔实验室里开发出来,主要用于重新实现Unix操作系统。此时,C语言又被称为K&R C。其中,K表示Kernighan的首字母,而R则是Ritchie的首字母。K&R C语言与后来标准化的C语言有很大差异。比如,如果函数返回类型为int,则int可省:int my_function() { },也可以写成my_function(){ }。编译器不会有任何警告,更不会报错。另外,还有现在看来比较奇葩的函数定义,像我们现在定义这么一个函数——void my_function(int a, char p){ },如果是用K&R C语法定义的话要写成:void my_function(a, p) int a; char p; { }。K&R的C语法中,定义一个函数时,其形参列表先列出形参的标识符,然后在函数声明的后面紧跟着对形参标识符的完整声明,最后是函数体。这在现行标准中已经被逐步废弃使用了。另外,当时的第一本C语言专业书《The C Programming Language》也并非一个正式的编程语言规范,但被用了许多年。
2.C90标准
由于C语言被各大公司所使用(包括当时处于鼎盛时期的IBM PC),因此到了1989年,C语言由美国国家标准协会(ANSI)进行了标准化,此时C语言又被称为ANSI C。而仅过一年,ANSI C就被国际标准化组织ISO给采纳了。此时,C语言在ISO中有了一个官方名称——ISO/IEC 9899:1990。其中,9899是C语言在ISO标准中的代号,像C++在ISO标准中的代号是14882。而冒号后面的1990表示当前修订好的版本是在1990年发布的。对于ISO/IEC 9899:1990的俗称或简称,有些地方称为C89,有些地方称为C90,或者C89/90。不管怎么称呼,它们都指代这个最初的C语言国际标准。这个版本的C语言标准作为K&R C的一个超集(即K&R C是此标准C的一个子集),把后来引入的许多非官方特性也一起整合了进去。其中包括了从C++借鉴的函数原型(Function Prototypes),指向void的指针,对国际字符集以及本地语言环境的支持。在此标准中,尽管已经将函数定义的方式改为现在我们常用的那种方式,不过K&R的语法形式仍然兼容。
3.C99标准
在随后的几年里,C语言的标准化委员会又不断地对C语言进行改进,到了1999年,正式发布了ISO/IEC 9899:1999,简称为C99标准。C99标准引入了许多特性,包括内联函数(inline functions)、可变长度的数组、灵活的数组成员(用于结构体)、复合字面量、指定成员的初始化器、对IEEE754浮点数的改进、支持不定参数个数的宏定义,在数据类型上还增加了long long int以及复数类型。毫不夸张地说,即便到目前为止,很少有C语言编译器是完整支持C99的。像主流的GCC以及Clang编译器都能支持高达90%以上,而微软的Visual Studio 2015中的C编译器只能支持到70%左右。
4.C11标准
2007年,C语言标准委员会又重新开始修订C语言,到了2011年正式发布了ISO/IEC 9899:2011,简称为C11标准。C11标准新引入的特征尽管没C99相对C90引入的那么多,但是这些也都十分有用,比如:字节对齐说明符、泛型机制(generic selection)、对多线程的支持、静态断言、原子操作以及对Unicode的支持。本书将主要针对C11标准为大家详细讲解C编程语言
笔者近两年也是在不断地了解C语言标准委员会的最新动态,其中看到有人提出想为C语言添加面向对象的特性,包括增加类、继承、多态等已被C++语言所广泛使用的语法特性,但是最终被委员会驳回了。因为这些复杂的语法特性并不符合C语言的设计理念以及设计哲学,况且C++已经有了这些特性,C语言无需再对它们进行支持。笔者将在第19章给大家谈谈C语言设计理念与发展方向。