《C语言编程魔法书:基于C11标准》——第一篇 预备知识篇 第1章 C魔法概览1.1 例说编程语言

简介:

本节书摘来自华章计算机《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
AI 代码解读

在代码清单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;
}
AI 代码解读

代码清单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
AI 代码解读

执行了上述代码之后,我们最后能在控制台看到输出结果:“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语言设计理念与发展方向。

目录
打赏
0
1
0
0
1408
分享
相关文章
[oeasy]python0033_先有操作系统还是先有编程语言_c语言是怎么来的
本文回顾了计算机语言与操作系统的起源,探讨了早期 Unix 操作系统及其与 C 语言的相互促进发展。Unix 最初用汇编语言编写,运行在 PDP-7 上,后来 Thompson 和 Ritchie 开发了 C 语言及编译器,使 Unix 重写并成功编译。1974 年 Ritchie 发表论文,Unix 开始被学术界关注,并逐渐普及。伯克利分校也在此过程中发挥了重要作用,推动了 Unix 和 C 语言的广泛传播。
158 9
[oeasy]python0033_先有操作系统还是先有编程语言_c语言是怎么来的
【C语言】数据类型全解析:编程效率提升的秘诀
在C语言中,合理选择和使用数据类型是编程的关键。通过深入理解基本数据类型和派生数据类型,掌握类型限定符和扩展技巧,可以编写出高效、稳定、可维护的代码。无论是在普通应用还是嵌入式系统中,数据类型的合理使用都能显著提升程序的性能和可靠性。
164 8
C语言中的模块化编程思想,介绍了模块化编程的概念、实现方式及其优势,强调了合理划分模块、明确接口、保持独立性和内聚性的实践技巧
本文深入探讨了C语言中的模块化编程思想,介绍了模块化编程的概念、实现方式及其优势,强调了合理划分模块、明确接口、保持独立性和内聚性的实践技巧,并通过案例分析展示了其应用,展望了未来的发展趋势,旨在帮助读者提升程序质量和开发效率。
177 5
|
5月前
|
C语言编程中,错误处理至关重要,能提升程序的健壮性和可靠性
C语言编程中,错误处理至关重要,能提升程序的健壮性和可靠性。本文探讨了C语言中的错误类型(如语法错误、运行时错误)、基本处理方法(如返回值、全局变量、自定义异常处理)、常见策略(如检查返回值、设置标志位、记录错误信息)及错误处理函数(如perror、strerror)。强调了不忽略错误、保持处理一致性及避免过度处理的重要性,并通过文件操作和网络编程实例展示了错误处理的应用。
149 4
C 语言作为经典编程语言,其函数编程技巧对提高代码质量至关重要
C 语言作为经典编程语言,其函数编程技巧对提高代码质量至关重要。本文深入探讨了函数的定义、设计原则、参数传递、返回值处理、调用约定、嵌套与递归调用、优化技巧及常见错误,旨在帮助读者提升编程水平。
88 1
十二个C语言新手编程时常犯的错误及解决方式
C语言初学者常遇错误包括语法错误、未初始化变量、数组越界、指针错误、函数声明与定义不匹配、忘记包含头文件、格式化字符串错误、忘记返回值、内存泄漏、逻辑错误、字符串未正确终止及递归无退出条件。解决方法涉及仔细检查代码、初始化变量、确保索引有效、正确使用指针与格式化字符串、包含必要头文件、使用调试工具跟踪逻辑、避免内存泄漏及确保递归有基准情况。利用调试器、编写注释及查阅资料也有助于提高编程效率。避免这些错误可使代码更稳定、高效。
1240 12
C语言 多进程编程(六)共享内存
本文介绍了Linux系统下的多进程通信机制——共享内存的使用方法。首先详细讲解了如何通过`shmget()`函数创建共享内存,并提供了示例代码。接着介绍了如何利用`shmctl()`函数删除共享内存。随后,文章解释了共享内存映射的概念及其实现方法,包括使用`shmat()`函数进行映射以及使用`shmdt()`函数解除映射,并给出了相应的示例代码。最后,展示了如何在共享内存中读写数据的具体操作流程。
C语言 多进程编程(七)信号量
本文档详细介绍了进程间通信中的信号量机制。首先解释了资源竞争、临界资源和临界区的概念,并重点阐述了信号量如何解决这些问题。信号量作为一种协调共享资源访问的机制,包括互斥和同步两方面。文档还详细描述了无名信号量的初始化、等待、释放及销毁等操作,并提供了相应的 C 语言示例代码。此外,还介绍了如何创建信号量集合、初始化信号量以及信号量的操作方法。最后,通过实际示例展示了信号量在进程互斥和同步中的应用,包括如何使用信号量避免资源竞争,并实现了父子进程间的同步输出。附带的 `sem.h` 和 `sem.c` 文件提供了信号量操作的具体实现。
【C语言程序设计——函数】素数判定(头歌实践教学平台习题)【合集】
本内容介绍了编写一个判断素数的子函数的任务,涵盖循环控制与跳转语句、算术运算符(%)、以及素数的概念。任务要求在主函数中输入整数并输出是否为素数的信息。相关知识包括 `for` 和 `while` 循环、`break` 和 `continue` 语句、取余运算符 `%` 的使用及素数定义、分布规律和应用场景。编程要求根据提示补充代码,测试说明提供了输入输出示例,最后给出通关代码和测试结果。 任务核心:编写判断素数的子函数并在主函数中调用,涉及循环结构和条件判断。
191 23
一文彻底搞清楚C语言的函数
本文介绍C语言函数:函数是程序模块化的工具,由函数头和函数体组成,涵盖定义、调用、参数传递及声明等内容。值传递确保实参不受影响,函数声明增强代码可读性。君志所向,一往无前!
37 1
一文彻底搞清楚C语言的函数
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等