zoukankan      html  css  js  c++  java
  • C语言笔记——简介与编译过程初探

    序言

    从今天起,详细说说C语言。这一年多,在大多数语言和技术之间转了一大圈,终于看清楚了事实,决心静下心来好好学学C语言。初学者会认为C语言是个入门用的东西,没有必要深入研究。但对计算机领域再稍加了解之后,就会发现C语言的重要性,而且它并非是个简单的东西。

    我想很多朋友跟我一样是个金庸迷,犹记得《天龙八部》中,乔峰大闹聚贤庄,一套“太祖长拳”击败少林数高僧,我还清楚的记得那回的名字:虽千万人吾往矣!何等气魄。江湖尽人皆知“太祖长拳”,是最基础的武功,每个人也都能使几下,但群雄看到乔峰的功力之后,才暗暗佩服原来“太祖长拳”也能所向披靡。

    所以,把C语言比作“太祖长拳”再合适不过了,它是各种语言的基础,是Unix的编写语言,可以说是计算机技术领域的一块重要基石。很多人认为C语言简单,其实我更想说C语言简洁但不是简单。有时候我会下载Linux内核源码或者其他大型开源软件源码来看看,发现其中的C语言让国际上这些大“宗师”们用的变换莫测,你会惊叹这些人可以用C写出功能如此强大的软件,甚至是操作系统。

    C语言博大精深,人人都想成为“乔峰”,但路还是要一步一步走,我觉得学编程最重要的一点就是不要被表象所迷惑,一定要想办法看清本质,不然会很痛苦,迷失在各种变化中而失去对它的兴趣。最近还看好了另外两门语言,一个是C++,一个是Python,尤其是Python,极力向大家推荐。

    C起源

    说到C就不得不提Unix,正是Unix催生了C,也是因为有了C,Unix(和后来的Linux)才有今天。最早创造Unix的人是贝尔实验室的汤姆森(Ken Thompson),Unix和C全诞生在这个实验室。但在没有C语言之前,Unix是用汇编写的,无法独立于硬件,也就是说,想把Unix移植到另一个型号或厂家的CPU上,就要把Unix重写一遍,这样很不现实。汤姆森的同事,也是Unix早期开发者之一,也就是C语言之父,丹尼斯 里奇(Dennis Ritchie,见下图)编写了C语言。之后,因为C语言是独立于硬件的,这帮贝尔实验室的天才们又用C语言重写了一遍Unix,而这个以C语言重写的Unix经过不断的改进和衍生,一直到了今天。如今里奇已经仙逝,汤姆森也已年近古稀,但Unix和C的精神却一直在各大技术社区流淌,生生不息。例如现在的Linus和Stallman,他们依然继承着前任的精神,在各自的领域里坚守着,并且影响了一代又一代的programmer。

    0103

    C标准

          重要更正:由博友garbageMan提示,目前最新标准为C11,2011年修订。具体内容可查阅相关文档。

      C语言一共存在三个标准,分别制定于89年、95年、99年,所以分别简称为C89、C95、C99,而与之对应,89年之前的C语言成为传统C。关于C语言标准的演变,以下抄录《C语言参考手册》中的内容:

    从传统C到C89:

    1、添加了真正的标准函数库。

    2、新的预处理命令和特性。

    3、函数原型,允许程序员在函数声明中指定参数的类型。

    4、增加了一些新的关键字,包括const、volatile和signed。

    5、宽字符、宽字符串和多字节字符。

    6、在转换规则、声明和类型检测方面的许多小改动和澄清。

    从C89到C95:

    1、3个新的标准库头文件:iso646.h、wctype.h和wchar.h。

    2、几个新的标记和宏,用于替换有些国家的字符集中不存在的操作符和标点符号。

    3、printf/scanf函数家族的一些新的格式代码。

    4、大量用于多字节符的新函数以及一些类型的常量。

    从C95到C99

    1、复数运算

    2、整数类型的扩展,包括更长的标准类型。

    3、可变长度的数组。

    4、对非英语字符集提供了更好的支持。

    5、对浮点类型提供了更好的支持,包括所有类型的数学函数。

    6、C++风格的注释(//)

    关于C++标准:

    这里先不做详细讨论,只引用《C参考》中的一句话:标准C++近乎是(但不完全是)标准C的超集。

    关于Clean C:

    用标准C和标准C++的公共子集编写的C代码叫做Clean C。

    注:博主认为,任何一门语言(也不止局限于语言,例如类Unix系统的标准),弄清楚它的标准都很重要,这样有助于了解一门技术的全貌。

    C编译过程

    到了这篇文章的重头戏了,很多朋友在学习C的过程中会忽略这一点。上来就开始不顾一切的编程,而不去剖析编译的原理,这就明显犯了避重就轻的错误,而不能看清楚事物的本质。所以,这里首先要搞清楚整个程序编译的过程。

    我们先来看看gcc的帮助文档里面的阐述(如下图):

    0101

    从C语言源代码,也就是.c文件,一直到最后的可执行文件,要通过四个关卡:preprocessing(预处理)、compilation(编译)、assembly(汇编)、linking(链接)。具体过程如下图,之后我会逐个阐述每个过程:

    0102

    我们通过一个最简单的实验描述整个过程。首先编写一个最简单的C语言程序hello.c,这就是我们要编译的源代码,如下图,只有7行这样简单

    0104

    第一步:preprocessing(预处理)

    gcc 中,若要程序只进行预处理而不再往下编译,利用选项 -E,完整的命令为 gcc -E hello.c -o hello.i  ,预处理之后生成 hello.i(部分代码如下图)。预处理阶段主要处理预处理命令,hello.c 中就是第一行代码 #include <stdio.h>  ,因为 hello.c 程序中要利用 printf  函数,所以要从 stdio.h 这个头文件里引入 printf 的声明。可以看到,预处理之后的文件足足有855行,这全都是 printf 函数做的怪,可见我们最常用的printf 函数并不简单,仅头文件的声明就有这么多,所以C的编译器和库函数一起为我们隐藏了许多细节(这里再往深入探究下去,会涉及系统调用的原理,printf是一个需要用到系统调用的函数)。

    01050106

    第二步,compilation(编译):

    这一步将预处理之后的 hello.i 编译成汇编代码 hello.sgcc 中的选项是 –S ,完整命令为 gcc -S hello.i -o hello.s ,编译之后生成汇编代码 hello.s (如下图),这份代码就是和体系结构相关的了,不过我们现在所用到的电脑大部分都是x86架构,应该不会有什么差别,但我想,如果拿到一些嵌入式设别上,这部分会代码就不同了,具体的汇编语言没有体系的学过,想深入的同学可以找找相关资料。可以看到代码量也不大。只有20行,因为这里的15行是 call printf,也隐藏了 printf 的细节。

    0107

    第三步,assembly(汇编):

    这一步将汇编代码 hello.s 编译为二进制的对象文件 hello.ogcc 中的选项是 –c , 完整的命令为 gcc -c hello.s -o hello.o ,执行之后会生成二进制机器代码写成的 hello.o (如下图),可以看到这个文件打开时乱码,说明这是人类不可读的二进制文件。每一个没有错误的 .c 文件经过这三部编译之后,都是生成一个.o 文件,o是object的缩写,而把很多的 .o 文件集合在一起就形成了库文件,库文件有分为静态库(.a)和动态库(.so)。

    注:关于静态库和共享库的内容我会放到下一篇博客里,因为解释这整个过程足够写一篇的了。

    0108

    第四步,linking(链接):

    这一步负责形成最后的可执行文件,链接不是一个很好理解的过程,我们想象一下,如果我们编写一个稍微大一点儿的项目,肯定不会只有一个 .c 文件,而按照上一步所说,每一个 .c 文件编译过后都会形成一个 .o 文件,而链接的任务就是把这些 .o 文件链接在一起并找到程序的主入口。有人会说这有什么难理解的,但试想一下我们现在这种情况,我们只有一个 hello.c 文件,那还需要链接这步么?答案是当然需要,还记得让人操心的 printf 函数么? 对,这个函数是系统帮我们实现的,他的 .o 文件就在其他地方,这个地方就叫做动态库,一个操作系统帮我们维护的工具库,所以我们这里虽然只有一个 .c 文件,一样需要链接。那有人还会说,如果我连 printf 也不用呢?,虽然这有些抬杠的嫌疑,但也不是不可以出现这样的情况,但事实是不可以的。我猜想链接过程肯定还负责一些其他的工作,比如说最明显的 .o 文件时没有执行权限的,而链接后的文件时可执行的,感兴趣的同学可以再深入探究下, 我在这就先扯到这里。关于共享库的内容我下一篇再展开。

    链接过程不需要选项,完整的 gcc 命令为: gcc hello.o –o hello , 生成的无后缀名的 hello 文件就是最后的可执行文件。这里如果有很多个 .o 文件,要全部加到命令行里,例如 gcc hello1.o hello2.o hello3.o –o hello。

    gcc 命令

      下面简单说一下我问刚才用到的四个 gcc 选项: –E 、–S 、–c 、–o 。先看一下linux下的man手册的解释。

    0109

      可以看到前三个选项其实解释从三个级别上截断编译过程,而并非是向我们上面用到那样,看上去像是一个截断一个选项,其实你用 –c 直接把 .c 文件编译成 .o 也是可以的,而且正常情况也是这样做的,我这里只是为了演示编译的每一个过程,在现实中,前连个选项应该是调试和观察编译过程是用的比较多。

    总结

      这篇文章简单介绍了一下C语言的历史和产生过程,并重点描述了整个编译过程,而关于库的相关内容我会在下篇文章里详细讨论。


    文章中大部分概念都是我个人的收集、思考和总结,难免有些地方可能有出入,也欢迎各路朋友发现问题指点一二。

    志同道合的朋友可以在微信中搜索公众账号:DarkSir 扫描博客公告栏中的二维码

    博客文章将同步更新到公众微信账号

  • 相关阅读:
    react 性能优化
    JS获取当前网页大小以及屏幕分辨率等
    创建对象的6种方式总结
    版本号规则
    JS事件模型
    浅谈虚拟DOM
    浏览器的回流与重绘
    JavaScript预编译
    canvas学习笔记
    java、tomcat安装
  • 原文地址:https://www.cnblogs.com/darksir/p/3802622.html
Copyright © 2011-2022 走看看