序言#

什么是代码(code)?#

代码,字面意义上来说是“用来替代的符号(系统)”。那么这个符号系统究竟是用来代替什么呢?

从CPU的角度来看,它所能理解的“语言”统统都是指令。指令很简单,一个指令只能执行一步操作(如“加、减、乘、除、移动”等),然后指定操作数和结果。

但是试想这样的一个表达式 \(a = 2 + 3 \times (5 + 7)\) 用指令写出来会是什么感觉(用r1、r2代表中间结果):

ADD: 5, 7 -> r1
MULTIPLY: 3, r1 -> r2
ADD: 2, r2 -> a

(上面不是不是真实的指令,真实的指令都是二进制形式的,比这还要难以理解)

就几个符号的算式还要调整计算顺序,添加中间结果。那处理一些稍微复杂的计算岂不是头脑体操? 代码就是一种以人类能够理解的方式替代和表达程序计算方式的符号

相比指令而言,它更加贴近人类的语言和逻辑。同时代码还保证了 简洁性和无歧义性,这是人类语言所不具有的。 代码最终会被编译(compile)为计算机指令,最终进入内存中被计算机执行(execute)。从这点来看,代码就是人类和计算机沟通的“桥梁”。

我们把代码所用到的符号系统称为编程语言(programming language)

C语言代码是怎么最终被计算机执行的?#

和绝大多数程序语言一样,C语言代码是文本文件,意味着它就是一串文本。它的文件名后缀通常为.c(源文件)或者.h(头文件)。要想被计算机执行,需要经历以下步骤:

编译#

编译器(compiler)会把我们的C语言代码中的逐字逐句地翻译成为机器指令。如果代码的语法正确,编译器会产出目标文件(object file)(文件名后缀:.obj)。否则,编译器会报错,引导我们修正。

链接#

有时,我们的代码引用了其他代码编写的功能。在编译阶段过后,需要整合多份目标文件。链接器(linker)会帮我们寻找引用并把他们正确地拼合到一起去。更通常的情况是,我们的代码使用操作系统提供的一些功能(如输入输出,文件读写等)。我们也需要链接器帮我们寻找功能引用并且链接到一起,这样程序才能找到这些功能的入口。如果链接成功,则链接器会生成可执行文件(executable file)(文件名后缀:.exe等),这个文件可以被计算机执行。如果链接失败(比如无效的引用等),链接器会报错,引导我们修正。

执行#

我们双击(或者以其他方式)运行生成好的可执行文件,这时候可执行文件的内容会进入内存,并最终让CPU“看到”。可执行文件的内部是一系列的指令,CPU会依次执行这些指令。

一般编译器会将前两步合在一起处理。也就是说,向编译器输入代码文件,输出的就是可执行文件。

我要怎么开发C语言程序?#

前面我们提过,C语言代码是文本文件。所以你可以使用文本编辑器编辑C语言文件,然后使用编译器编译。此外你还可以使用 调试器(debugger)来探查程序的中间信息、或是单步执行,方便找出程序中的任何bug。

但是一个个地调用工具实在太过麻烦。通常我们使用集成开发环境(Intergrated Development Environment, IDE)来进行开发。一般来说,IDE有以下的功能:

  1. 集成文本编辑器、编译器和调试器于一身,方便程序生成和调试。
  2. 语法高亮(syntax highlighting)。将代码不同语法类型的部分用不同颜色区分,方便开发者阅读。
  3. 语法错误检查、自动补全……

可见集成开发环境极大地方便了程序开发。

说了这么多,C语言究竟能干什么?#

很多初次听说编程语言的人都可能对于编程语言有一个狭隘的认知,比如A语言可以做某事,而B语言可以做另外某事。“某物是用来做某事的”,这对于日常工具看起来没有问题,譬如杯子是用来盛水的,碗是用来盛饭的。

但是仔细想想,你手上的计算机能够做什么呢?好像能够做很多事:处理文档、收发邮件、收看视频、游玩游戏……但是计算机能做的无外乎两件事:计算数据转移数据。之所以计算机能够这么“万能”,是因为我们的计算机是“通用计算机(general purpose computer)”,设计出来就不是为了某一专一目的而计算,而是将所有功能都转化成了计算和转移操作。

使用程序语言开发程序,就是设计一系列的数据计算和转移,让目标计算机完成某一功能。不管是哪种高级程序语言,其能实现的功能和目标计算机的功能是相同的。由此来看,C语言既是“专一的”,又是“万能的”。

什么是C标准(C standard)?它和我的开发有什么关系?#

C标准规定了C语言的组成语法、以及实现。我们可以从多种角度理解这件事:

  • C标准使得C语言能在每一台计算机、每一种操作系统上都有一致无误的实现。
  • C标准使得C语言无歧义,指导我们怎么去使用语言。
  • C标准规范了编译器的行为,也规定了每种C语言编译器都应该至少实现什么功能。

C标准现在由国际标准化组织(International Organization for Standardization, ISO) 出版并维护,其编号为:ISO/IEC 9899。 它是一个动态的标准,意味着它有版本的迭代升级。常见的标准有:C89、C95、C99、C11、C17等。

C标准指定了绝大多数的程序行为,但是它在某些地方“留有空间”,不指定它们的行为的结果。 通常使用以下的术语来表示:

  • 实现定义行为(implementation-defined behavior):标准容许二种或多种行为, 每一个编译器指定实现哪一个行为并成为其规范。如字节的位数。
  • 未定义行为(undefined behavior):对此行为没有限制。 出现该行为程序可以产生非预期的结果。编译器不需要检查出未定义行为。 通常这说明程序在设计上有问题,比如出现了除以0。