最近在看windows相关的一些书,不禁感慨,之前学习过程中经历的很多困难实质上是缺乏一些基础知识,而且在国内互联网论坛上少有关于这类基础知识的讨论和讲解。即使有,也大多从老旧的帖子中抄袭而成,多有错误和过时的地方。因此我决心写一个成系统的扫盲文章。第一个短期的计划是关于C语言的。我假设我的大部分读者应该是正在上,或者刚刚修过"高级语言程序设计(C语言)"这门课程的大一,大二学生。我希望先简单解释一些基础概念,常见的名词。然后会用几篇文章讲解编写C语言程序的几种工具,我会把重点放在微软的Visual Studio 2017,完全跳过VC6.0。再之后会有一两篇文章解释常见的编译期错误和运行时错误,讲解如何查错,如何调试一个程序。最后可能有几篇面向希望能进一步提高自己水平的读者的文章。大体就是这样了。
0.什么是C语言
C语言是一种高级程序语言,所谓高级程序语言,"In computer science,a high-level programming language is a programming language with strong abstraction from the details of the computer"一种高级语言是抽象的,不涉及计算机硬件细节的语言。这就使得高级语言有两个特点,一是通过抽象减少了程序员需要思考的范围,二是通过改变在不同平台上的实现,使得编写的程序可以不加修改地在不同的平台上运行。
我们更关心第二个特点,这表示,同样的一个helloworld程序,无论在linux还是windows系统,在amd或intel的CPU上运行,都应该在屏幕上打印一个"hello world"。在C语言刚出生时,这其实已经是一个惊人的进步,在那之前,程序员们使用汇编语言写程序,然而汇编语言完全依赖于你使用的机器,也就是说,你在一台机器上运行的程序,几乎不可能在另外一台使用了不同的硬件的电脑上运行。
疲于奔命的程序员说,要有高级语言,就有了C语言。
------- Genesis 1:3
1.C语言其实有好几种
这里我们来讲一点历史。
在20世纪70年代末,Dennis MacAlistair Ritchie与Kenneth Lane Thompson在贝尔实验室设计,开发了C语言。然而后来的几年里,很多公司和个人开发者都不是直接使用最开始的这一版本,而是各自为方便使用而对C语言进行了修改。
1989年,为了避免各开发厂商用的C语言语法产生差异,由美国国家标准局为C语言订定了一套完整的国际标准语法,称为ANSI C(又称 C89),作为C语言的标准。二十世纪八十年代至今的各种程序开发工具(编译器,调试器,文本编辑器等)一般都支持 ANSI C。这是被广泛接受的一个版本,国内大部分教科书和老师的教学都基于ANSI C。
C语言还有不同的版本,但比较重要的只有三个。
ANSI C (C89) 这是被最广泛接受的一个
C99 这个版本支持了很多新的语法,很多编译器也实现了这个标准(例如GCC),大部分情况下使用C99新增的特性是没问题的
C11 这个版本。。。实际上还没有编译器完全实现,鉴于笔者寿命有限,不再提及。
2.hello world, hello assembly, hello CST
我想读者们已经运行过一个hello world程序,大概像这样
#include<stdio.h>
int main()
{
printf("hello world\n");
return 0;
}
你可能已经单步调试过这个程序,在第三行打一个断点,然后一行行执行,直到第五行,程序结束。
但是,你的程序其实并不是你看到的这么简单,它实际上长这样。(源代码的部分标为黑体)
1 #include<stdio.h>
2 int main()
3 {
4 00007FF6B7BA0590 push rbp
5 00007FF6B7BA0592 push rdi
6 00007FF6B7BA0593 sub rsp,0E8h
7 00007FF6B7BA059A lea rbp,[rsp+20h]
8 00007FF6B7BA059F mov rdi,rsp
9 00007FF6B7BA05A2 mov ecx,3Ah
10 00007FF6B7BA05A7 mov eax,0CCCCCCCCh
11 00007FF6B7BA05AC rep stos dword ptr [rdi]
12 printf("hello world\n");
13 00007FF6B7BA05AE lea rcx,[string "hello world\n" (07FF6B7BB03E8h)]
14 00007FF6B7BA05B5 call printf (07FF6B7B91528h)
15 return 0;
16 00007FF6B7BA05BA xor eax,eax
17 }
18 00007FF6B7BA05BC lea rsp,[rbp+0C8h]
19 00007FF6B7BA05C3 pop rdi
20 00007FF6B7BA05C4 pop rbp
21 00007FF6B7BA05C5 ret
怎么样,是不是仿佛看到了克苏鲁一样,san值狂降?
这其实是helloworld在VS2017中,经编译产生的汇编代码。
这段代码做了三件事,
一,保存程序上下文(context)
二,调用printf函数(14行)
三,恢复程序上下文(contxet)
有些比较聪明的读者可能已经立刻明白过来,举手回答说,代码的编译就是把代码转换成汇编代码,汇编代码可以轻易地转换成机器代码,直接运行。这种想法已经相当接近实际情况,但如果只是单纯翻译源代码,4-10行和18-21行的程序又是从哪儿来的呢?这段代码其实是编译器悄悄塞进来的,没有这些额外的工作,程序是无法正确运行的,涉及到的知识点太多,本文中不再深入了。但在接下来的第一章,我将解释编译器对你的代码做了什么,ide和编译器有何不同。
但更重要的是,这个printf函数是打哪来的呢?我们都知道在C语言里,函数要先声明,并在某处定义出来,才能调用。
输入输出的printf,scanf一族(还有sscanf,sprintf.....)被定义在stdio.h(standard input/output)中,
关注数学运算的pow(),sqrt(),fabs()被定义在math.h中
关于时间的time()等函数,tm等结构体被定义在time.h中
这些头文件属于C语言标准,包含了具体实现的文件被称作C语言标准库(C Standard Library)
在之后的第二章,将注重于说明,lib,dll,exe等文件的关系,说明C语言标准库在我们的程序中起什么作用。
这只是一篇开坑的说明,所以先到此为止了。
亲爱的读者朋友们,明年再见,啊不,下周再见。