C 语言重点总结
参考资料
- 本文
指针
就 C 语言的数组与函数, 我有一定理解, 总结如下
a[i]==i[a]==*(a+i)()为根据地址执行指令
| type&action | object | function | array |
|---|---|---|---|
| 值/name | 值 | 函数地址 | 首元素地址 |
& |
变量地址 | 函数地址 | 数组地址 |
* |
值所代表的地址上的值 | 函数地址 | 首元素 |
sizeof |
type'size | no | (sizeof type)*length |
C 与 C++
- C++ 认为
int fun()==int fun(void), C 认为只是省略了 - C 的
char常量'a'是int类型, 因此'ABCD'不会报错 - C++的
const变量默认是内部链接, 可以用extern改为外部链接 - C++ 的
const变量可以初始化const, C 不行 - C++ 严格使用枚举, C 将之认定为整型
- C++ 原生宽字符, C 原生复数
- C++11 没有初始化器,
restrict, VLA, 伸缩型数组成员, 可变数量参数的宏 - C++ 标记名 (自定义类型名或标签名) 与变量名可以重复, C 不行
molloc在 C++ 中必须强制类型转换- C 警告将
const指针赋给普通指针, C++ error - C++ 可以用
const整型声明数组大小 - C++ 可以
type(name)强制类型转换 - C++ 不可调用
main()现在 C 也不行了
以上受时间影响, 仅供参考
声明
- 构成: type name+ 修饰符
- 不讨论 const extern 等
- 修饰符有三种:
- 后置有
[]与(), 优先级相同, 自左向右结合 - 当然, 这是对规律的总结, 实际上它们不是运算符, 没有优先级与结合律
- 前置
*优先级小于后置, 因此我们声明数组与函数的指针必须使用() - 将修饰符与 name 结合, 理解它的类型
- 最后你会发现, 如果你将以上三种符号认为是运算符形式, 那么你得到的是一个表达式, 而前面的 type 就是表达式值的类型
- 后置有
链接数组
- 在链接阶段跨文件使用一个全局数组的时候
- 不能
extern一个指针 - 因为链接变量是指出 a 的地址 (由另一个文件定义), 当你把一个数组声明为对应元素的指针, 会认为数组地址存储的那个值是指针的值, 本质原因是数组的值虽然是首元素的指针, 但没有使用额外的空间存储, 程序会访问未知的内存
增补
对齐
- 内存对齐是指数据在内存中存储的起始地址必须是某个特定值的整数倍
- 内存对齐的好处
- CPU 和内存之间的数据总线是有宽度的, 例如 64 位 CPU, 数据总线通常是 64 位(8 字节)
- 内存子系统也是按照块或字来存取的
- x86/x64 要求数据对齐, 否则会导致性能下降 (多次取, 位移合并), 另外许多架构干脆不支持未对齐访问
- 另外写入一定是对齐的, 读如果不对齐, 会撕裂, 对齐还会简化如 SIMD 等指令的实现
- C 与 C++ 的结构体对齐处理 (插入无意义的字节 (Padding), 原生类型肯定不必管)
- 每个成员的偏移量必须是该成员自身对齐值的倍数
- 整个结构体的大小必须是该结构体中最大成员对齐值的倍数 (为了支持结构体数组这一场景, 保证数组第二个元素也是对齐的)
- 特殊情况
- 空结构体的大小
- C 语言中未定义
- C++ 规定至少为 1
- 虚函数与继承的影响
vptr会改变类的布局和对齐要求 (按平台指针大小)- 派生类会复用基类的填充字节
- 空结构体的大小
- 对齐操纵
alignas(N), 可以指定对齐值alignof(T), 可以获取类型 T 的对齐值#pragma pack(N), 可以指定对齐值, 范围 1-16, 默认为 8- 一定要小心使用 pointers 指向 packed struct 的成员
- 有时我们利用
alignas(N)强制过度对齐- 可以避免伪共享 (多线程中, 两个线程频繁修改的变量如果处于同一个缓存行, 会导致缓存一致性协议频繁在核心间缓存行, 造成抖动)
- 标准的
malloc在 64 位系统上通常保证 16 字节对齐 (为了适配long double或 SSE 指令 (SIMD 拓展) 的基础要求) - 如果你需要 64 字节对齐(为了 AVX-512), 标准
new可能做不到- 需要使用
_mm_malloc(Windows/Intel) 或posix_memalign(Linux), 或者 C++17 的std::aligned_alloc
- 需要使用
C 标准中的概念
1byte >= 8bit- 函数最少支持 31 个参数
- 一条代码行里至少可以有 509 个字符
- 表达式中, 至少支持 32 层嵌套
() - 至少 257 个
case - 对象指一个或多个字节, 这些字节的二进制值为对象表示, 对象表示中可能含有填充
对象类型
- 大小与对齐要求
- 算术类型, 标准有符号整数类型大概率没有填充
_BitInt(n)类似 n 位有符号整数类型, 但可能有填充- 无符号去掉符号位
- 派生类型: 数组 / 函数 (不是对象类型) / 指针 / 结构 / 联合 / 原子
- 数组类型,
T[n], 要求T是完全对象类型,T[]为不完全对象类型, 若n为整数常量 / 整数常量表达式, 则T[n]为普通数组类型, 否则为变长度数组类型 - 指针类型
- 限定类型,
const/volatile/restrict(只能修饰指针 / 指针多维数组, 含义仅我所有) /_Atomic - 算术类型与指针类型(包括
nullptr_t)及其限定类型为标量类型 - 初始化:
name={};name=值;name=除表达式外的任何 16 种表达式 - 具名对象与匿名对象
- 对象属性: 地址 / 类型 / 对齐要求 / 大小 / 表示值 / 表示类型 / 对象名称
- 分配对象的方式: 声明 / 字符串 / 内存管理 / 复合字面量