C 语言进阶基础之一

语言发展历程

机器语言

计算机的大脑或者说心脏就是 CPU,它控制着整个计算机的运作。每种 CPU,都有自己的指令系统。这个指令系统,就是该 CPU 的机器语言。机器语言是一组由 0 和 1 系列组成的指令码,这些指令码,是 CPU 制造厂商规定出来的,然后发布出来,要求程序员遵守。要让计算机干活,就得用机器语言(二级制数)去命令它。这样的命令,不是一条两条,而是上百条。不同型号的计算机其机器语言是不相通的,也就是使用某种计算机的机器指令编制的程序,不能在另一种计算机上执行。

汇编语言

机器语言编程很令人烦恼,因此终于出现了汇编语言,就是一些标识符取代 0 与 1。汇编语言是一门人类可以比较轻松认识的编程语言。只是这门语言计算机并不认识,所以人类还不能用这门语言命令计算机做事情。所以,有一类专门的程序,既认识机器语言,又认识汇编语言,也就是编译器,将标识符换成 0 与 1,知道怎么把汇编语言翻译成机器语言。

高级语言

汇编语言和机器语言都是面向机器的,机器不同,语言也不同。既然有办法让汇编语言翻译成机器语言,难道就不能把其他更人性化的语言翻译成机器语言?1954 年,Fortran 语言出现了,其后相继出现了其他的类似语言。这批语言,使程序员摆脱了计算机硬件的限制,把主要精力放在了程序设计上,不在关注低层的计算机硬件。这类语言,称为高级语言。同样的,高级语言要被计算机执行,也需要一个翻译程序将其翻译成机器语言,这就是编译程序,简称 “编译器”。这类高级语言解决问题的方法是分析出解决问题所需要的步骤,把程序看作是数据被加工的过程。基于这类方法的程序设计语言,成为了面向过程的语言。

语言的层次

c-lang-1

语言的进化史

c-lang-2

c-lang-3

为什么要学习 C 语言

C 语言的特点

优点:

  • 代码量小
  • 功能强大
  • 编程自由
  • 执行速度快

缺点:

  • 可移植性较差
  • 对平台库依赖较多
  • 写代码实现周期长
  • 过于自由,经验不足易出错

学习 C 语言理由

c-lang-4

C 语言的应用领域

C 语言的应用极其广泛,从网站后台,到底层操作系统,从多媒体应用到大型网络游戏,均可使用 C 语言来开发:

  • C 语言可以写网站后台程序
  • C 语言可以专门针对某个领域写出功能强大的程序库
  • C 语言可以写出大型游戏的引擎
  • C 语言可以写出另一个语言来,例如:PHP 纯 C 语言开发的
  • C 语言可以写操作系统和驱动程序,并且一般只能用 C 语言编写
  • 任何设备只要配置了微处理器,就都支持 C 语言。从微波炉到手机,都是由 C 语言技术来推动的
  • 详见:各类语言的应用领域图解分析

第一个 C 语言程序

编写代码

1
2
3
4
5
6
#include <stdio.h>

int main(int argc, char *argv[]) {
printf("Hello World!\n");
return 0;
}

编译代码

  • GCC 编译命令常用选项说明
选项含义
-o 指定生成的输出文件名
-E 只进行预处理
-S 只进行预处理和编译
-c 只进行预处理、编译和汇编
  • 编译代码,生成可以执行文件
1
$ gcc hello.c -o hello

运行代码

  • 运行可执行文件
1
2
3
$ ./hello
Hello World!

代码分析

  • #include 头文件包含:
    • #include 的意思是头文件包含,#include <stdio.h> 表示包含 stdio.h 这个头文件
    • 使用 C 语言库函数时,需要提前包含库函数对应的头文件,如这里使用了 printf() 函数,则需要包含 stdio.h 头文件
  • #include <>#include "" 的区别:
    • <> 表示编译器直接按系统指定的目录(/usr/include)检索头文件
    • "" 表示系统先在 "" 指定的路径(没写路径则默认使用当前路径)查找头文件,如果找不到,再按系统指定的目录检索
  • main() 函数
    • 一个完整的 C 语言程序,是由一个、且只能有一个 main() 函数(又称主函数,必须有)和若干个其他函数结合而成(可选)
    • main() 函数是 C 语言程序的入口,程序是从 main() 函数开始执行的
  • printf() 函数
    • printf() 是 C 语言库函数,功能是向标准输出设备输出一个字符串
    • printf() 函数在 stdio.h 头文件里定义
    • \n 表示回车换行
  • return 语句
    • return 代表函数执行完毕
    • 如果 main() 函数定义的时候前面是 int,那么 return 后面就需要写一个整数
    • 如果 main() 函数定义的时候前面是 void,那么 return 后面什么也不需要写
    • main() 函数中 return 0 代表程序执行成功,return -1 代表程序执行失败
    • int main()void main() 在 C 语言中都是支持的,但 C++ 只支持 int main() 这种定义方式

system 函数

system 函数的定义

1
2
3
4
5
6
7
头文件:#include <stdlib.h>
声明:int system(const char *command);
功能:在已经运行的程序中执行另外一个外部程序
参数:外部可执行程序名字
返回值:
成功:不同系统返回值不一样
失败:通常是 -1

system 函数的调用

1
2
3
4
5
6
7
8
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
system("ls"); // Linux 平台
// system("calc"); // Windows 平台
return 0;
}

system 函数的返回值

在 Linux 和 Windows 系统下分别调用 system() 函数,若调用成功返回值是不一样的,若调动失败返回值一般为 -1。C 语言所有的库函数调用,只能保证语法是一致的,但不能保证执行结果是一致的;同样的库函数在不同的操作系统下执行结果可能是一样的,也可能是不一样的。Linux 的发展离不开 POSIX 标准,只要符合这个标准的函数,在不同的系统下执行的结果就可以一致。Unix 和 Linux 很多库函数都是支持 POSIX 标准的,但 Windows 支持的比较差。如果将 Unix 代码移植到 Linux 一般代价很小,如果把 Windows 代码移植到 Unix 或者 Linux 就比较麻烦。

C 语言的编译过程

C 语言的编译步骤

C 语言编译成可执行程序需要经过以下 4 个步骤,详见 编译流程图

  • 预处理:宏定义展开、头文件展开、条件编译等,同时将代码中的注释删除,这里并不会检查语法
  • 编译:检查语法,将预处理后文件编译生成汇编文件
  • 汇编:将汇编文件生成目标文件(二进制文件)
  • 链接:C 语言编写的程序是需要依赖各种库的,所以编译之后还需要把库链接到最终的可执行程序中去

GCC 的编译过程

GCC 的编译步骤

步骤命令
1. 预处理 gcc -E hello.c -o hello.i
2. 编译到汇编代码 gcc -S hello.c -o hello.s
3. 汇编到目标代码(二进制文件)gcc -c hello.s -o hello.o
4. 链接,生成可执行文件 gcc hello.o -o hello

值得一提的是,以上四个步骤,可以合成一个步骤,直接编译链接成可执行目标文件,命令是 gcc hello.c -o hello

文件后缀的不同含义

文件后缀含义
.cC 语言文件
.i 预处理后的 C 语言文件
.s 编译后的汇编文件
.o 编译后的目标文件

查找程序所依赖的动态库

1
2
3
4
5
6
7
8
# GCC编译
$ gcc hello.c -o hello

# 查看所依赖的动态库
$ ldd hello
linux-vdso.so.1 => (0x00007f152053a000)
libc.so.6 => /lib64/libc.so.6 (0x00007f151ff4d000)
/lib64/ld-linux-x86-64.so.2 (0x00007f152031b000)

在 Windows 系统里,可以使用 Dependency Walker 工具查看程序所依赖的动态库(DLL),如下图所示:

c-lang-7

VS 中 C 语言嵌套汇编代码

在 Visual Studio 中,由于下述代码使用了 eax 寄存器,因此程序需要运行在 32 位(x86)的平台。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>

int main() {
int a;
int b;
int c;

__asm
{
mov a, 3 // 3的值放在a对应内存的位置
mov b, 4 // 4的值放在a对应内存的位置
mov eax, a // 把a内存的值放在eax寄存器
add eax, b // eax和b相加,结果放在eax
mov c, eax // eax的值放在c中
}

printf("c = %d\n", c);
return 0;
}