C语言系统级编程
C语言系统级编程
荣老师太帅了aaaa!!(成熟男人的魅力谁懂😭)
为什么开这门课?
”基础不牢,地动山摇“
这门课的目标
能够自己理解C语言标准中的内容
- 属于规则的一般化
- 概念组织的系统化
介绍C语言标准背后的设计思想
引例:C语言中有没有变量(variable)
1 | int a = 0; //芝士什么? 变量 |
引例2:常量 vs. 常量表达式
形式 | 名称 |
---|---|
10 | 整数常量 |
+10 | 常量表达式 |
现有学习资料和标准的概念体系有一定差异
- 核心概念介绍缺失
对象、lvalue、rvalue等核心概念介绍不详细或者不涉及 - 部分概念混淆有误
变量、常量的划分方法,常量和常量表达式的混淆等 - 存在许多自创概念
常变量、只读变量、行指针、列指针、数组指针、指针数组、多级指针、模拟引用传递
标准一般化为什么重要?
都是语法糖,没有意义?会用就行了?
如果使用变量术语,只有int a = 0;
是变量,其它定义均不知所谓
实际上,它们都是“变量标识符”
标准中的逻辑规则强调的就是一般化
一般化的基础:以对象为核心构建概念体系
- 对象的基本概念(对象的各种属性)
- 如何分配一个对象(4种分配方式)
- 如何定位一个对象(17类表达式)
- 如何获得lvalue和non-lvalue表达式的相关信息
- 如何释放对象占用的内存
本课程内容简介
C语言知识类别
- 语法类知识
- 概念类知识
- 策略类知识
学习周期和考核方法
学习时长1-9周(16学时,1学分)
考核方法:考试(开卷,带什么资料都行)+随堂测验(上课的时候带张草稿纸)
(正片开始)
内存基本概念简单回顾
1byte=8bits?
<limits.h>定义了一个宏CHAR_BIT
C语言规定一个byte等于CHAR_BIT个bit
CHAR_BIT的限制是>=8(即不一定是8位)
地址是字节在内存中的编号,只有字节才有地址
C语言最重要的概念:对象
- 对象的概念
- 如何刻画对象的属性
- 理解对象该类别
- 理解指针和数组也是一种普通的对象类型
对象表示(Object Representation)
- 一个对象各个bit组成的二进制串就是这个对象的对象表示
- 对象表示$\leftarrow$对象类型$\rightarrow$对象的值
对象表示 vs. 对象的值
- 对象表示一样,对象的值一定一样
- 对象的值一样,对象表示不一定一样
对象类型T
C语言任何一个对象类型都可以形式化定义为一个T
- sizeof(T)
规定了任何一个对象包含的字节个数,n为size,sizeof(T)=n * CHAR_BIT - alignof(T)
对象地址(Address):对象所占用的连续字节中lowest字节的编号
对齐要求(Alignment):要求对象的地址能被对齐要求整除
所有的算数类型和指针类型统称为标量类型,只有标量类型支持加减操作
对象分类——算术类型(Arithmetic Type)
C23正式增加bool类型
算术类型都是完全对象类型(即size确定)
char、signed char、unsigned char是三种不同的类型,编译器会指定char和另外两种中的一个表现相同
- 标准有符号整数类型对象
1个sign bit 若干value bit 若干padding bit
signed char、signed short int、signed int、signed long int、signed long long int
- 标准无符号整数类型对象
若干value bit 若干padding bit
unsigned char、unsigned short int、unsigned int、unsigned long int、unsigned long long int、unsigned bool
typedef
tepedef是一个能够为类型设置别名的关键字
1 | typedef T alias |
typedef可以提高系统移植能力
1 |
|
用法:
1 | typedef T alias; //非数组非指针 |
对齐要求
对象对齐要求一定是$2^n$
n越小越weak,越大约strong
枚举类型
-
在枚举定义时指定类型
1
enum Season
-
不指定类型,自动指定为char、标准有符号类型、标准无符号类型、扩展有符号证书类型、扩展无符号整数类型的一种
对象分类——派生类型(Derived Type)
重点介绍数组类型和指针类型
数组类型
形式化定义:T[N]
- T:元素类型,必须是完全对象类型
- N:元素类型的个数
N为表达式:
- 无N,不完全对象类型
- 有N,N为整数常量或整数常量表达式,普通数组类型
- 有N,不为整数常量或整数常量表达式,变长数组类型
数组类型也是一个合法的对象类型,有大小和对齐要求
typeof
数组类型可以视为一个整体,即:
1 | typedef int AINT[5]; |
由于写法过于恶臭(确实),C23提供了一个关键字
1 | typedef typeof(int[5]) AINT; |
从数组类型T继续派生
由于数组类型也为完全对象类型,所以仍可继续派生
于是出现了二维数组!
1 | int a[10][5]; |
数组都是“一维”的
指针类型
形式化定义:T*
- T:Referenced Type:对象类型或函数类型
- *:指针类型标志
T*为从T派生的类型
注意:T包括完全函数类型,也包括不完全对象类型,还包括函数类型
指针类型是完全对象类型
sizeof(T*)
alignof(T*)
指针类型再派生
指针类型也能用于构造数组类型,指针类型也有自己的指针类型
类型 | 派生数组 | 派生指针 |
---|---|---|
T | T[N] | T* |
T[M] | T[N][M] | T(*)[M] |
T* | T*[N] | T** |
你已经学会了,现在为int*(**[])[4][5]
派生指针类型吧
答案:int*(**(*)[])[4][5]
限定类型/限定符(Qualifier)
const volatile restrict
若T为类型,Q为限定符。当T为一个整体的时候,限定符在T的左边右边都一样(T Q = Q T)
1 | Q T = T Q //非数组非指针 |
理解const int* const
-
const int
,限定类型,限定int -
const int*
,指针类型,Referenced Type为const int -
const int* const
,限定类型,限定const int*
Q typeof(T[N]) = Q typeof(T)[N]
- 多个不同限定符限定同一个类型等价
- 多个同样限定符限定同一个类型等价于一个
认识对象的属性
{A, Obj_T, N, S, V, V_T, Align}
- 对象名称 T
有名称的对象被称为具名对象(Named Object) - 大小 Size
sizeof(T) - 地址
最低位地址 - 对齐要求
对齐要求:alignof(T) - 对象值和对象表示
之前讲过了喵(善用ctrl+f
) - 表示值 Value
- 表示类型 Value Type
<表示值,表示值类型>
非数组对象类型:<object value,typeof_unqual(object Type)>
数组对象类型:<第一个元素对象的地址,元素类型对应的指针类型>
分配对象:通过对象声明
分配对象的方法
rwg:建议一次只声明一个对象🤓
对象声明
- 基本形式:
T O = initalizer;
- T应当看作一个整体,即typeof(T)
int(*d)[10]; = typeof(int(*)[10]) d;
String Literal
String Literal是由编码前缀和双引号引导的字符序列构成的
编码前缀包括:
编码前缀 | 字符类型 | 编码 |
---|---|---|
无编码 | \ | \ |
u8 | char8_t | UTF-8 |
u | char16_t | UTF-16 |
U | char32_t | UTF-32 |
L | wchar_t | 实现定义 |
字符序列中的字符包括:
- 所有char,除了" \ 实际的回车
- \引导的转义字符
步骤(以初始化“hello”为例):
- 在内存中分配一个大小合适的char类型数组对象
- 用’h’,‘e’,‘l’,‘l’,‘o’,'\0’初始化数组的每一个元素
- 该对象为匿名对象
- 当第二次初始化“hello”时,不一定需要分配一个新的地址空间(如"hello"和L"hello")
String Literal vs. String
双引号内不包含"\0"的String Literal叫做String,String Literal包含String
Compound Literal
内存管理函数
malloc
- 分配一个匿名对象,该对象无对象类型
- 大小为size
- 对象表示为indeterminate
- 返回值为空指针(未成功)或者这个对象的首地址
- 该对象的Storage Duration是allocated
- 对齐要求为Fundamental Alignment,保证了返回的void*指针可以强转成任意类型
initalizier
初始化对象的对象表示
标量类型initiallizer形式:
- {},空初始化列表
- 将对象的值设置为该类型的缺省值
- 除逗号表达式之外的任意表达式epx或{exp}
数组类型initiallizer形式:
- {},空初始化列表
- 将对象的值递归设置为该类型的缺省值
- {sub-initalizer1,…}
- 设置前n个对象,剩下设置为缺省值
当对变长数组初始化时,只能使用{}进行初始化
没有initializer时,对象值初始化为indeterminate representation(不确定值)
对象值、对象表示一一对应
表达式
C语言中,定位一个对象,只能通过lvalue(locate value)
- 表达式(Exp)由一些列符号和操作数组成
- 共有17种表达式
Evaluation of Expression
- Evaluate的过程包括
- Value Computation(计算值)
- Initiation of Side Effect(确定副作用)
- Value Computation得到表达式的rvalue,包含值和类型
基础表达式(Primary Expression)
标识符:包括对象标识符和函数标识符
- 对象声明 T O = Initializer,其中O就是对象标识符
- 对象标识符是合法的lvalue
- Value Computation:非数组——对象值,数组——第一个元素首地址
- Side Effect:无
常量
-
非左值,无法定位对象
-
整数常量、浮点数常量、枚举常量、字符常量、预定义常量
-
整数常量:进制前缀+数字序列+类型后缀
-
前缀
前缀 含义 0b/0B 二进制 无 十进制 0 八进制 0x/0X 十六进制 -
后缀(重点)
敲不动了,放个图
从上至下为类型匹配顺序
不考虑BitInt系列,整数常量的类型,最小是int(涉及到整型提升的概念) -
数字分隔符不能出现在第一个数字前,不能出现在最后一个数字后
-
-
浮点数常量:进制前缀+符号序列+后缀
- 十进制符号序列形式:1.1e-3
- 科学计数部分不一定有
- 十六进制符号序列形式:0x100.5p2
- 幂的底数为2,指数部分为十进制
- 十进制符号序列形式:1.1e-3
-
枚举常量,比较简单
-
字符常量:编码前缀+单引号引导的有效字符
-
前缀
当没有给定前缀时,系统会默认类型,所以中文字符在一些系统会变成乱码字符常量 rvalue类型 ‘a’ int u8’a’ char8_t u’a’ char16_t U’a’ char32_t L’a’ wchar_t -
有效字符
- 基本源代码字符集
- 基本执行字符集
- 扩展源代码字符集、扩展执行字符集
-
‘\x31’ :以十六进制整数表示的字符,十进制值是49,依然是字符’1’
-
-
预定义常量:false、true、nullptr
- false和true分别为0和1,rvalue类型为bool
- nullptr是一个空指针类型,rvalue类型为在<stddef.h>中定义的nullptr_t类型
字符串
字符串不是常量
- 字符串可以用来分配(或重用)一个对象
- 同时,字符串是一个lvalue,能够定位分配出来(或重用)的那个对象
字符串分配(或重用)的对象类型是字符数组类型
如果lvalue定位的对象是一个数组类型对象,则lvalue表达式Evaluate之后的rvalue就是该对象第一个元素的首地址,rvalue类型是元素对象类型对应的指针类型
括号表达式
evaluate的优先级最高,rvalue和类型等于括号内表达式的值
后缀表达式
给定一个表达式exp,与后缀操作符结合构成的仍然是一个表达式
后缀操作符:[]、.、->、++/–、(type-name){Initializer-list}、()
一元表达式
给定一个表达式exp,与一元操作符结构构成的仍然是一个表达式
一元操作符:++/–、&、*、+/-、~/!、sizeof、_Alignof
evaluate
定位非数组对象lvalue的evaluate
对形如如下形式的lvalue不做evaluate,表达式整体evaluate由特殊规则确定
- sizeof(a),编译时确定
- typeof(a),编译时确定
- &a,在main外时称为地址常量表达式
- a++/a–,rvalue为a的rvalue加1或减1,同时将a的value改变(有副作用,可能两个不同时发生)
- ++a/–a,rvalue为a的rvalue加1或减1,同时将a的value改变(有副作用,可能两个不同时发生)
- a=2,rvalue为等号右边表达式的rvalue,同时将左值的value改变(有副作用,可能两个不同时发生)
常量不可修改的原因是左值为不可修改左值