C 专家编程
- C 专家编程
C: 穿越时空的迷雾
- C 语言的发展与历史
- 除了位段与掩码, 不要使用无符号整型
这不是 BUG, 而是语言特性
PS: 重复内容比较多
Switch标签打错了, 编译器也不一定会发现, 因为还有goto
分析 C 语言的声明
不重复
令人震惊的事实: 数组与指针并不相同
不重复
对链接的思考
- 驱动器统筹, 预处理器 -> 前端 (语法与语义分析) -> 后端 (代码生成器) -> 汇编程序 -> 链接 -> 载入器, 并在任意时优化, 大多在前后端之间
- 静态链接与动态链接 (更具优势) 静态库 (archive .a) 与动态库 (ld .so)
- ABI (应用程序二进制接口) 是动态链接的优势之一, 减少更新库函数后对程序的维护
- 多个进程使用同一个函数在内存空间当中只会有一个副本
- 因为每个使用共享库的进程, 一般会把它映射到不同的虚拟地址, 所以使用与位置无关的代码, 对于共享库来讲要更快
-l选项永远在最后 (linker 要先看到未定义的符号然后再去库里找, 否则不知道要找啥)interpositioning会覆盖库函数, 不仅仅是在你的程序里, 所以若无必须, 请像 C++ 默认的一样声明static
运动的诗章: 运行时数据结构
a.outassembler output 但事实是链接器的输出- UNIX 系统常见 ELF 文件格式, 其中以 segment (段) 划分, 一个 segment 包含多个 section (把 UNIX 的段与 x86 架构的段区分开)
a.out包含 text, data, bss 段(记录没初始化的变量)- gcc 编译的程序, 就是这三部分
- text (指令与字符串常量)
- data (初始化的全局变量)
- bss (未初始化, 如果你尝试初始化为 0, 会优化为不初始化)
- 以下是运行时分配的
- heap (动态内存分配的堆内存, 与同名数据结构没有任何关系)
- stack (函数参数与局部变量, 与同名数据结构有一些关系)
- 但有时, 共享库与链接器也在该进程的内存中
- stack 中, sp 指针维护栈顶, 栈中保存函数参数, 局部变量, 堆栈结构 / 过程活动记录 (返回地址, 函数调用地址, 不适合装入寄存器的参数以及寄存器原有值的保存)
alloca函数可以申请到栈的空间 - stack 为递归而独立存在, 绝大多数 cpu, stack 往低内存生长
setjump与longjump, 在 C++变成了异常- unix 的栈会生长, dos 需要固定好 (8086 中最大 64kb)
对内存的思考
- 8086 选择重叠两个 16 位 (偏移量与段) 变成 20 位以访问 1gb 内存的同时兼容原本的 16 位程序, 8086 有 4 个段寄存器,
CS,DS,SS,ES(附加) - 虚拟模式 / 保护模式将内存控制器移到芯片外, 段寄存器不与偏移量相加, 而为存放实际段地址的表提供索引
- 虚拟内存以页的形式组织, 进程尝试操纵不在物理内存当中的页会发生页错误, 经过判定是否有效而选择将页载入或者返回段违规
- 当数据从内存读入, 整块被装入
cache的行的数据部分, 该行的标签记录物理地址, 以加速连续读取 - 堆内存的末端由
break指针维护 - 未对齐的读写会引发总线错误, 如要读 4 字节整形, 但指定的地址不是 4 的倍数
为什么程序员无法分清万圣节和圣诞节
031==25,10==012- 所有长度不到
int和double的变量, 在表达式中会自动类型提升, 以保证在运算过程中不会溢出, 但是如果编译器确定提升与否不影响结果, 可以不提升 (适当的函数原形能阻止该过程) (float)3与(float)3.0的行为是不一样的
PS: 这部分以实际程序经验为主
再论数组
已经让我们玩透了不是吗?
再论指针
同上
你懂得 C, 所以 C++不在话下
不记录