概述
- for 循环和 while 循环本质上是等价的
- 通过地址找到一个值, 通过类型解释一个值, 这就是为什么数组名的地址同数组的第一个元素的地址, 但使用 sizeof 和 & 访问到的则是整个数组的长度和地址
- 类对象是基于结构的 c++ primer plus 7.8 p236
C 标准
- C89
- C95
- C99
- C11
常量
- const 修饰的常变量, 本质上仍旧是变量, 字面值才是真正的常量, 只要能从外部取址都不是常量, 能取址的可以是常变量
字符 Character
- 二元字符
- 三字母词 trigrph
- 转义字符 character escape
- 转义序列 escape sequence
- 字符串结束符 ‘\0’ 空字符( null character)
- 文件结束符 EOF -1 0xffff
字符集 Character Set
- source character set
- execution character set
翻译阶段 Phases of translation
- 转换成元字符集
- 处理换行符
- 将源文件分解为注释, 空白字符序列, 预处理标记
- 预处理器执行
- 字符常量和字符串字面值中的所有字符和转义序列从源字符集转换为执行字符集
- 连接相邻的字符串字面值
- 编译发生, 标记被语法语义分析并翻译成编译单元
- 链接发生, 编译单元和需要满足外部引用的库组件被收集成为程序镜像,包含在其执行环境执行所需要的信息
数据对象 Objects
- C程序创建, 销毁, 访问和操作对象, 对象是指在执行环境中数据存储的区域, 对象具有确定类型时的内容就是值
- 对象由声明, 内存分配函数, 字符串字面值, 复合字面值, 返回结构或具有数组成员的联合的非左值表达式创建
每个对象具有:
属性 | 说明 |
---|---|
大小 | 可由 sizeof 确定 |
对齐要求 | 可由 alignof 确定 |
存储期 | automatic, static, allocated, thread-local |
生命周期 | lifetime, equal to storage duration, temporary |
有效的类型 | |
值 | |
标识符 |
对齐 Alignment
- 每个完整的对象类型具有称为对齐要求的属性, size_t 类型的整型表征连续地址的字节数, 在这个地址上该类型的对象可以被分配
- 有效的对齐值是2的非负整次方
- 对齐要求的类型可使用 alignof 查询
- 为了满足一个结构体中所有成员的对齐要求, 某些成员后可能被插入填充
作用域 Scope
作用域 Scope | 说明 |
---|---|
代码块作用域 block scope | 代码块内 |
文件作用域 file scope | 声明处到文件尾 |
原型作用域 prototype scope | 只适用于函数原型中的参数名 |
函数作用域 function scope | 只适用于语句标签(goto语句) |
链接属性 Linkage
链接指的是文件之间的链接, 链接属性只针对于文件作用域的标识符, 代码块内部的变量无法具有外部链接属性
链接属性 Linkage | 说明 | 举例 |
---|---|---|
外部 external | 所有同名标识符都表示同一实体 | 函数和全局变量 |
内部 internal | 同一文件内的同名标识符表示同一实体 | 用 static 限定符修饰的变量 |
无 none | 同名标识符总是不同,这些符号本身不参与链接 | 局部变量,静态局部变量 |
存储类型 Storage class
声明位置 | 存储类型 | 内存结构 |
---|---|---|
代码块外 | 静态变量 static | 静态内存中 |
代码块内 | 自动变量 auto | 运行时堆栈中 |
数据类型 Data Type
- 联合类型
- 浮点数在内存中如何表示, 不同实现的有效位数不同
- 若数组溢出则会修改未知的内存区域,变量溢出则由于用补码表示, 数值会连续变化
- 指针类型本身的宽度同操作系统, 32位系统就是32位地址,64就是64, 指向的数据的类型决定访问其内容的方式
- 指针的内容,即存储的地址负责找到那个字节所在的内存位置, 从这个位置访问连续几个字节由指向的数据类型决定
- 除浮点类型外, 该数据类型的宽度往往决定它能表示的数据范围
- constant 表示值不变的量, 即用标识符表示的常量, literal 表示字面值, 即没有标识符以表示名字的常量
- 函数中的静态局部变量不会在每次函数调用时不断的重新初始化, 而自动局部变量每次都会被重新初始化
枚举类型
- 枚举列表中的常量值总是递增的
- 使用枚举名定义枚举变量取得枚举列表中的值
- 枚举在编译阶段将名字替换成对应的值, 可以理解为编译阶段的宏(普通的宏在预处理阶段执行宏替换)
- 枚举值不占用数据区(常量区, 全局数据区, 栈区和堆区)的内存,而是直接被编译到命令里面,放到代码区, 所以不能用”&”操作符取得它们的地址
变量声明
- 定义和声明是不同的。定义只能出现一次,声明可以出现多次。
- const 变量默认为文件的局部变量。而 const 变量如果想在其他文件里被访问,必须在定义时显式地指定它为 extern。
- 只有全局变量并且没有被 static 声明的变量才能声明为 extern。
- C标准规定头文件不能定义变量,会造成重复定义。
- C++标准并没有规定头文件中不能定义变量,但造成变量的重新定义。
- static 变量一般放在源文件中。
- 使用条件编译可保证头文件只被包含一次。
三个例外:
- 值在编译时就已知的 const 变量的定义可放在头文件中。
- 类的定义可放在头文件中。
- inline 函数可以定义在头文件中。
const int a = 10; // 可以放在头文件中
extern int a = 10; // 定义
double a; // 定义
extern int a; // 声明
void f(); // 函数声明, 并未产生实际目标代码
void f(){ }; // 函数定义
位字段 Bit fields
- 用于创建与某个硬件设备上的寄存器对应的数据结构
- 使用位表示的显式的宽度声明一个结构体成员
- 相邻的位域成员可能被打包以共享和跨越单字节限制。
- 位域声明是结构或联合的成员声明
语法:
type identifier : width
- 标识符 - 要被声明的位域的名字(可选)
- 宽度 - 整型常量表达式,其值大于等于0且小于等于底层类型的位数
类型:
- 4种系统类型(可被 const 或 volatile 限定)
- 实现定义的额外的类型(是否可以有atomic类型也是由实现定义的), 位域的宽度设置了它能表示的值的范围(C11)
- int 类型可能有符号也可能无符号, 注意普通变量声明为int默认是有符号
type | example | range |
---|---|---|
unsigned int | unsigned int a : 3 | 0~7 |
signed int | signed int a : 3 | -4~3 |
int | int a : 3 | |
bool | bool a : 1 | 0~1 |
操作符 Operator
- 自增自减运算符的表达式的结果来自于操作数的拷贝
- 当两个运算符被用于同一个操作数时, 优先级和结合性规则才有效
- 模运算符号规则: ( a / b ) * b + a % b = a
存储器模型 Memory model
- 存储器模型(Memory model)定义了计算机内存存储方式的语义, C程序可用的数据存储方式是一个或多个连续的字节。内存中每个字节具有唯一地址
- 字节是内存中最小的可寻址单元。字节被定义成连续的位,能够存储基本执行字符集(96个单字节字符)中的任意成员。C 支持8位或更多位组成的字节。
- 字符类型(char, unsigned char, 和 signed char)使用一个字节同时用于存储字符对象和表示对象的值。一个字节有多少位可由 CHAR_BIT 获取。
- 内存位置(Memory location)是可以一个标量类型(算术类型, 指针类型, 枚举类型)的对象或是最大的连续非零位域
struct S {
char a; // memory location #1
int b : 5; // memory location #2
int c : 11, // memory location #2 (continued)
: 0,
d : 8; // memory location #3
struct {
int ee : 8; // memory location #4
} e;
} obj; // The object 'obj' consists of 4 separate memory locations
线程 Threads
- 线程是指程序中使用 thrd_create 函数或其他方式调用顶级函数(top-level function)而开始的一个控制流
- 任何线程都可能访问程序中的任何对象(而 automatic 和 thread-local 存储期的对象可以在线程执行到这个点时被访问)
- 程序的不同线程总是被允许并发地访问(读取和修改)不同的内存位置,没有干扰阻碍也不须同步请求。
- 注意,并发的更新同一个结构体中的两个非自动存储期的位域是不安全的,如果在它们之间的所有被声明的成员也是位域(非零长度),不论这些中间的位域大小是多少
数据竞争 Data Races
- 当一个表达式的赋值操作向内存位置写操作并且另一个赋值操作读取和修改相同的内存位置,此表达式被称为冲突的。具有两个冲突的赋值操作的程序存在数据竞争,除非冲突的赋值操作都是自动类型的操作或者两个冲突操作先后发生
- 若果发生数据竞争, 那么程序行为是未定义的
- 互斥锁可以防止数据竞争
内存顺序 Memory order
- 当一个线程从一个内存位置读取一个值时, 该线程看到的可能是其初始值, 同一线程写入的值, 或者另一个线程写入的值
- 可通过查看内存顺序(memory_order)获取线程的写入顺序对另一个线程可见的细节.
动态内存分配
- 在栈中分配内存则能够自动释放, 在堆中分配内存不能够自动释放, 后者若不释放会发生内存泄露(leakage)
- 用 sbrk(2) 系统调用实现扩充或减小进程的堆
- 与动态分配内存相对的概念是编译时分配内存, 即 data 段等
NULL
object | meaning | describe |
---|---|---|
NULL | 标准头文件中定义的宏 | 用于表示什么也不指向的指针,也就是空指针((void *)0), 除了指针, 其他变量使用 NULL可能导致任何问题 |
0 | 整型常量 | 可以被用于任何地方,它是表示各种类型零值的通用符号并且编译器会识别出它 |
‘\0’ | 字符常量 | 字符串结束字符, 只应用于字符上下文 |
参考
- Phases of translation
- Memory model
- 《UNIX环境高级编程》中7.8节介绍了动态内存分配
- 《C和指针》中介绍了变量的存储类型, 生命周期, 作用域, 链接属性
- C语言枚举类型(Enum)
- C99灵活数组-incompleted array