[TOC]
# 一切都是地址
我们知道,数据是保存在内存中的,对于计算机硬件来说,必须知道它的地址才能使用。变量名、函数名等仅仅是地址的一种助记符,目的是在编程时更加方便地使用数据,当源文件被编译成可执行文件后,这些标识符都不存在了,它们被替换成了数据的地址。
假设变量 a、b、c 的地址分别为 `0X1000`、`0X1004`、`0X1008`,加法运算的机器指令为 1010,赋值运算的机器指令为 1110,那么在C语言中实现加法运算的代码为:
~~~
c = a + b;
~~~
生成可执行文件后的机器码为:
~~~
1010 0X1000 0X1004 //将两个数据相加的值保存在一个临时区域
1110 0X1008 //将临时区域中的数据复制到地址为0X1008的内存中
~~~
**编译器和链接器的一项重要任务就是将助记符替换成地址。**
# 汇编语言的诞生
任何程序的执行,最终都要依靠计算机硬件来完成。现代计算机硬件都是大规模集成电路,它只认识高低两个电平(电压),高电平一般为 5V,用`1`表示,低电平一般为 0V,用`0`表示。也就是说,在计算机底层,没有文字、数字、图像、视频等丰富多彩的可视化元素,只有 0 和 1 两个二进制数字,这就是机器语言。
计算机刚刚诞生的时候没有编程语言,人们直接使用机器语言(二进制)编程。现在假设有一种跳转指令,它的二进制形式为 0001,如果需要执行地址为 1010 的代码,那么可以这样写:
~~~
0001 1010
~~~
> 所谓跳转,就是在执行当前代码块时转而执行其他的代码块。从本质上讲,C语言中的函数就是一个代码块,当发生函数调用时,就会执行其他的代码块,这个过程就是通过跳转指令来完成的。
那么现在问题来了,程序并不是一写好就永远不变化的,它可能会经常被修改。比如我们在地址 1010 之前插入了其他指令,那么原来的代码就得往后移动,上面的跳转指令的跳转地址也得相应地调整。
在这个过程中,程序员需要人工重新计算每个子程序或者跳转的目标地址,这种重新计算各个目标地址的过程叫做重定位(Relocation)。每次程序修改时,这些位置都要重新计算,十分繁琐又耗时,并且很容易出错。
如果程序包含了多个源文件,就很可能会有跨文件的跳转,这种人工重定位的方式在程序拥有多个模块时会导致更加严重的问题。
没办法,这种黑暗的程序员生活是没办法容忍的,于是先驱们发明了汇编语言(Assembly),这相比机器语言来说是个很大的进步。
汇编语言使用接近人类的各种符号和标记来帮助记忆,比如用`jmp`表示跳转指令,用`func`表示一个子程序(C语言中的函数就是一个子程序)的起始地址,这种符号的方法使得人们从具体的机器指令和二进制地址中解放出来。
将上面的机器指令使用汇编代码来书写:
~~~
jmp func
~~~
这样,不管在 func 之前增加或者减少了多少条指令导致 func 的地址发生了变化,汇编器在每次汇编程序的时候会重新计算 func 这个符号的地址,然后把所有使用到 func 的地方修正为新的地址,整个过程不需要人工参与。对于一个有成千上百个类似的符号的程序,人们终于摆脱了这种低级的繁琐的计算地址的工作,用一句政治口号来说就是“极大地解放了生产力”。
符号(Symbol)这个概念随着汇编语言的普及被广泛接受,它用来表示一个地址,这个地址可能是一段子程序(后来发展为函数)的起始地址,也可以是一个变量的地址。
# C语言的诞生
汇编语言的主要作用是为机器指令提供了助记符,大部分汇编代码和机器指令是一一对应的,这在汇编被发明的初期确实令程序员非常欣喜。
后来随着软件规模的日渐庞大,代码量开始疯长,汇编语言的缺点逐渐暴露出来。汇编虽然提供了多种符号,但它依然非常接近计算机硬件,程序员要考虑很多细节问题和边界问题,并且不利于模块化开发,所以后来人们发明了C语言。
C语言是比汇编更加高级的编程语言,极大地提高了开发效率,以加法为例,C语言只需要一条语句,汇编却需要四五条。
# 模块化开发
现代软件的规模往往都很大,动辄数百万行代码,程序员需要把它们分散到成百上千个模块中。这些模块之间相互依赖又相互独立,原则上每个模块都可以单独开发、编译、测试,改变一个模块中的代码不需要编译整个程序。
> 在C语言中,一个模块可以认为是一个源文件(.c 文件)。
在程序被分隔成多个模块后,需要解决的一个重要问题是如何将这些模块组合成一个单一的可执行程序。在C语言中,模块之间的依赖关系主要有两种:一种是模块间的函数调用,另外一种是模块间的变量访问。
函数调用需要知道函数的首地址,变量访问需要知道变量的地址,所以这两种方式可以归结为一种,那就是模块间的符号引用。
模块间依靠符号来“通信”类似于拼图版,定义符号的模块多出一个区域,引用符号的模块刚好少了那一块区域,两者刚好完美组合。如下图所示:

这种通过符号将多个模块拼接为一个独立的程序的过程就叫做链接(Linking)
- c语言
- 基础知识
- 变量和常量
- 宏定义和预处理
- 随机数
- register变量
- errno全局变量
- 静态变量
- 类型
- 数组
- 类型转换
- vs中c4996错误
- 数据类型和长度
- 二进制数,八进制数和十六进制数
- 位域
- typedef定义类型
- 函数和编译
- 函数调用惯例
- 函数进栈和出栈
- 函数
- 编译
- sizeof
- main函数接收参数
- 宏函数
- 目标文件和可执行文件有什么
- 强符号和弱符号
- 什么是链接
- 符号
- 强引用和弱引用
- 字符串处理函数
- sscanf
- 查找子字符串
- 字符串指针
- qt
- MFC
- 指针
- 简介
- 指针详解
- 案例
- 指针数组
- 偏移量
- 间接赋值
- 易错点
- 二级指针
- 结构体指针
- 字节对齐
- 函数指针
- 指针例子
- main接收用户输入
- 内存布局
- 内存分区
- 空间开辟和释放
- 堆空间操作字符串
- 内存处理函数
- 内存分页
- 内存模型
- 栈
- 栈溢出攻击
- 内存泄露
- 大小端存储法
- 寄存器
- 结构体
- 共用体
- 枚举
- 文件操作
- 文件到底是什么
- 文件打开和关闭
- 文件的顺序读写
- 文件的随机读写
- 文件复制
- FILE和缓冲区
- 文件大小
- 插入,删除,更改文件内容
- typeid
- 内部链接和外部链接
- 动态库
- 调试器
- 调试的概念
- vs调试
- 多文件编程
- extern关键字
- 头文件规范
- 标准库以及标准头文件
- 头文件只包含一次
- static
- 多线程
- 简介
- 创建线程threads.h
- 创建线程pthread
- gdb
- 简介
- mac使用gdb
- setjump和longjump
- 零拷贝
- gc
- 调试器原理
- c++
- c++简介
- c++对c的扩展
- ::作用域运算符
- 名字控制
- cpp对c的增强
- const
- 变量定义数组
- 尽量以const替换#define
- 引用
- 内联函数
- 函数默认参数
- 函数占位参数
- 函数重载
- extern "C"
- 类和对象
- 类封装
- 构造和析构
- 深浅拷贝
- explicit关键字
- 动态对象创建
- 静态成员
- 对象模型
- this
- 友元
- 单例
- 继承
- 多态
- 运算符重载
- 赋值重载
- 指针运算符(*,->)重载
- 前置和后置++
- 左移<<运算符重载
- 函数调用符重载
- 总结
- bool重载
- 模板
- 简介
- 普通函数和模板函数调用
- 模板的局限性
- 类模板
- 复数的模板类
- 类模板作为参数
- 类模板继承
- 类模板类内和类外实现
- 类模板和友元函数
- 类模板实现数组
- 类型转换
- 异常
- 异常基本语法
- 异常的接口声明
- 异常的栈解旋
- 异常的多态
- 标准异常库
- 自定义异常
- io
- 流的概念和类库结构
- 标准io流
- 标准输入流
- 标准输出流
- 文件读写
- STL
- 简介
- string容器
- vector容器
- deque容器
- stack容器
- queue容器
- list容器
- set/multiset容器
- map/multimap容器
- pair对组
- 深浅拷贝问题
- 使用时机
- 常用算法
- 函数对象
- 谓词
- 内建函数对象
- 函数对象适配器
- 空间适配器
- 常用遍历算法
- 查找算法
- 排序算法
- 拷贝和替换算法
- 算术生成算法
- 集合算法
- gcc
- GDB
- makefile
- visualstudio
- VisualAssistX
- 各种插件
- utf8编码
- 制作安装项目
- 编译模式
- 内存对齐
- 快捷键
- 自动补全
- 查看c++类内存布局
- FFmpeg
- ffmpeg架构
- 命令的基本格式
- 分解与复用
- 处理原始数据
- 录屏和音
- 滤镜
- 水印
- 音视频的拼接与裁剪
- 视频图片转换
- 直播
- ffplay
- 常见问题
- 多媒体文件处理
- ffmpeg代码结构
- 日志系统
- 处理流数据
- linux
- 系统调用
- 常用IO函数
- 文件操作函数
- 文件描述符复制
- 目录相关操作
- 时间相关函数
- 进程
- valgrind
- 进程通信
- 信号
- 信号产生函数
- 信号集
- 信号捕捉
- SIGCHLD信号
- 不可重入函数和可重入函数
- 进程组
- 会话
- 守护进程
- 线程
- 线程属性
- 互斥锁
- 读写锁
- 条件变量
- 信号量
- 网络
- 分层模型
- 协议格式
- TCP协议
- socket
- socket概念
- 网络字节序
- ip地址转换函数
- sockaddr数据结构
- 网络套接字函数
- socket模型创建流程图
- socket函数
- bind函数
- listen函数
- accept函数
- connect函数
- C/S模型-TCP
- 出错处理封装函数
- 多进程并发服务器
- 多线程并发服务器
- 多路I/O复用服务器
- select
- poll
- epoll
- epoll事件
- epoll例子
- epoll反应堆思想
- udp
- socket IPC(本地套接字domain)
- 其他常用函数
- libevent
- libevent简介