百度地图|C语言:整合数据实现的过程


百度地图|C语言:整合数据实现的过程

文章图片


百度地图|C语言:整合数据实现的过程

文章图片


C 语言为我们提供了高于机器指令的一定抽象能力 , 这使得我们能够以接近自然语言的方式来构建应用程序 。 如果说使用 C 语言是用砖块来造房子 , 那使用其他高抽象粒度编程语言 , 就是直接以墙面为单位来搭建 。 很明显 , 从这个角度来说 , C 语言用起来不如其他高级语言方便 , 但它也同时给予了更细的构建粒度 , 让我们能够按照自己的想法 , 灵活自定义墙面的形态 。
对于这里提到的砖块和墙面 , 你可以将它们简单理解为编程语言在构建程序时使用的数据类型 。 比如在 Python 语言中 , 我们可以使用集合(set)、字典(dict)等复杂数据类型 。 而在 Java 语言中 , Map 本身又会被细分为 HashMap、LinkedHashMap、EnumMap 等多种类型 , 供不同应用场景使用 。
为了在保持自身精简的同时也保证足够高的灵活性 , C 语言在提供基本数值类型和指针类型的基础上 , 又为我们提供了结构(struct)、联合(union)与枚举(enum)这三种类型 。 结合使用这些类型 , 我们就能将小的“砖块”组合起来 , 从而将它们拼接成为更大的、具有特定功能结构的复杂构建单元 。
接下来 , 就让我们一起看看:编译器是如何在背后实现这三种数据类型的?而在实现上 , 为了兼顾程序的性能要求 , 编译器又做了哪些特殊优化?
枚举在编程语言中 , 枚举(Enumeration)这种数据类型可以由程序员自行定义 , 用来表示某类可取值范围有限的抽象概念 。
下面我们来看一个经典的例子:应该如何使用编程语言来表示 “周工作日(weekday)” 这个概念呢?
周工作日属于现实世界中的一种抽象概念 , 它包含周一到周五共五个有效值 。 不同于数值、字符等概念 , 它无法直接对应到物理计算机中的任何软硬件实现上 。 因此 , 为了能够在程序中更加精确地表达这类信息 , 我们可以用枚举来自定义对应的类型 。
在 C 语言中 , 我们可以这样实现:

为了便于观察 , 我直接展示了 C 代码及其对应的汇编代码 。 可以看到 , 编译器没有为左侧红框内的枚举类型定义生成任何的机器指令 。 实际上 , 在 C 语言中 , 每一个自定义枚举类型中的枚举值 , 都是以 int 类型的方式被存储的 , 因此 , 这些枚举值有时也被称为“具名整型” 。 你可以从上图右侧蓝框内的汇编代码中看到 , 当函数 foo 被调用时 , 传入的枚举值 Mon 正对应于通过 edi 寄存器传入的字面量数字 0 。 也就是说 , 枚举值 Mon 在底层是由数字值 0 表示的 。
同样地 , 在左侧 C 代码的第 11 行 , 我们也使用了泛型宏来判断枚举值 Mon 的具体类型 。 你可以尝试运行这段代码 , 并观察程序的输出结果 , 以验证我们的结论 。
需要注意的是 , C 标准直接将枚举值当作整数进行处理的这种方式 , 可能会导致我们在构建程序时遇到意想不到的问题 。 比如 , 对于上述这段 C 代码 , 函数 foo 在被调用时 , 实际上允许传入任何可以被隐式转换为 int 类型的值 , 哪怕这个值来源于另一个枚举类型的变量 。 因此 , 让枚举类型有助于组织程序代码的同时并确保它不被乱用 , 也是我们在构建高质量程序时需要注意的一个问题 。
结构在 C 语言中 , 数组用来将一簇相同类型的数据存放在连续的内存段上 。 而结构(Struct)实际上与其类似 , 只不过在结构内部 , 我们可以存放不同类型的数据 。 先来看一段代码:

在上图左侧的 C 代码中 , 我们定义了一个名为 S 的结构 。 对于每一个结构 S 的对象 , 其内部都会连续存放三个类型完全不同的数据值 , 即一个字符指针、一个字符值、一个长整型数值 。
在代码的第 10 行 , 我们通过括号列表初始化的方式 , 构造了结构 S 的一个对象 s 。 通过右上方蓝框中的汇编代码 , 我们可以看到编译器是如何实现对它的初始化的 。 本质上 , 结构只是对其内部所包含各类数据的一个封装 , 因此从编译产物的角度来看 , 只需要把它封装的这些数据连续地存放在内存中即可 。 事实也正是如此 , 对结构 S 内部三个数据的初始化过程 , 均是由指令 mov 完成的 , 这些数据被初始化在栈内存中 。
结构中的数据项被初始化在内存中 , 这毋庸置疑 , 但它们真的“连续”吗?