序言

从今天起,详细说说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。

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的帮助文档里面的阐述(如下图):

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

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

第一步: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是一个需要用到系统调用的函数)。

第二步,compilation(编译):

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

第三步,assembly(汇编):

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

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

第四步,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手册的解释。

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

总结

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


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

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

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

C语言笔记——简介与编译过程初探的更多相关文章

  1. 笔记:C 编译过程

    笔记:C 编译过程 参考了 编译器的工作过程 1 C 编译过程 配置 确定标准库和头文件位置 确定依赖关系 头文件的预编译 预处理 编译 连接 F4NNIU 2018-06-12 编译器的工作过程 h ...

  2. 用gcc编译c语言程序以及其编译过程

    对于初学c语言编程的我们来说,学会如何使用gcc编译器工具,对理解c语言的执行过程,加深对c语言的理解很重要!!! 1.预编译 --> 2.编译 --> 3.汇编 --> 4.链接- ...

  3. C语言简短程序gcc编译过程

    一.建立一个×.c源文件.这里起名:rocks.c 二.编辑源代码,在c源文件内输入如下代码: #include <stdio.h> int main() { puts("C R ...

  4. APM代码学习笔记2:编译过程

    make编译 所有位置的Makefile 引用的都是/mk/apm.mk target.mk 设置CONFIG_HAL_BOARD 例如linux就是HAL_BOARD_LINUX environ.m ...

  5. 转 C语言编译过程简介

    C语言编译过程简介 C语言编译过程简介 刚开始接触编程的时候,只知道照书敲敲代码,一直都不知道为什么在windows平台下代码经过鼠标那样点击几下,程序的结果就会在那个黑色的屏幕上.现在找了个机会将C ...

  6. C语言基础(21)-C语言编译过程及GCC参数简介

    任何C语言的编译过程可分为以下三部分: 一.预编译 在C语言中,以#开头的语句又叫预编译指令.预编译主要做以下两件事情: 1.将#include包含的头文件做简单的文本替换: 2.将代码中的注释删除. ...

  7. 【嵌入式开发】gcc 学习笔记(一) - 编译C程序 及 编译过程

    一. C程序编译过程 编译过程简介 : C语言的源文件 编译成 可执行文件需要四个步骤, 预处理 (Preprocessing) 扩展宏, 编译 (compilation) 得到汇编语言, 汇编 (a ...

  8. C语言编译过程以及gcc编译参数

    1.1       C语言编译过程,gcc参数简介 1.1.1          C语言编译过程 一.gcc - o a a.c -o:指定文件输出名字 二.C语言编译的过程: 1.1.1       ...

  9. gcc 学习笔记(一) - 编译C程序 及 编译过程

    一. C程序编译过程 编译过程简介 : C语言的源文件 编译成 可执行文件需要四个步骤, 预处理 (Preprocessing) 扩展宏, 编译 (compilation) 得到汇编语言, 汇编 (a ...

随机推荐

  1. 设定MS SQL Server 2008定期自动备份

    1.说明 SQL Server2008 本身具有定期自动备份功能,我们只需要通过简单的配置就可以实现非常简单高效的自动备份功能. 2.打开SQL Server代理服务 要实现自动备份功能,首先要保证S ...

  2. apache 设置

    此博客是网站www.beilei123.cn镜像,转载请注明出处. 1.ServerTokens ProdServerTokens Prod 显示“Server: Apache”ServerToken ...

  3. Java API设计CheckList

    API设计原则:正确.好名.易用.易学.够快.够小.但我们从来不缺原则,〜〜〜 Interface 1.The Importance of Being Use Case Oriented,一个接口应当 ...

  4. zmodem协议的使用(SecureCRT)

    SecureCRT可以使用zmodem协议来快速的传送文件,使用非常方便.对于Linux系统,大多数发行版本都自带有rz和sz命令.在SecureCRT中选择Options->Session O ...

  5. php number_format()保留小数点后几位有效数的函数 千位分组来格式化数字(转)

    PHP保留小数点后2位的函数number_format number_format(带小数点的书,小数点后保留的位数) number_format(8.3486,2);  //取得小数点后2位有效数/ ...

  6. java命令行运行带外部jar

    假设:java 代码路径为com.jdw.test,其中调用了外部jar包 则需要将jar包解压后,放入com同级目录 然后再com目录启动命令行 java com.jdw.test.HelloWor ...

  7. nginx上传模块nginx_upload_module和nginx_uploadprogress_module模块进度显示,如何传递GET参数等。

    ownload:http://www.grid.net.ru/nginx/download/nginx_upload_module-2.2.0.tar.gzconfigure and make : . ...

  8. java.lang.NoClassDefFoundError: org/apache/commons/io/output/DeferredFileOutputStream(转)

    java.lang.NoClassDefFoundError: org/apache/commons/io/output/DeferredFileOutputStream 使用Tomcat的Manag ...

  9. linux之SQL语句简明教程---Alias

    接下来,我们讨论 alias (别名) 在 SQL 上的用处.最常用到的别名有两种: 栏位别名及表格别名. 简单地来说,栏位别名的目的是为了让 SQL 产生的结果易读.在之前的例子中,每当我们有营业额 ...

  10. Linux 零碎知识点

    ln -s ../libs/ libs 在当前目录下建立一个符号链接文件libs,使它指向上一层目录的libs文件夹 关于su和su -的区别切换用户是可以使用su tom或者su - tom来实现, ...