C语言系统级编程

荣老师太帅了aaaa!!(成熟男人的魅力谁懂😭)

为什么开这门课?

”基础不牢,地动山摇“

这门课的目标

能够自己理解C语言标准中的内容

  1. 属于规则的一般化
  2. 概念组织的系统化

介绍C语言标准背后的设计思想

引例:C语言中有没有变量(variable)

1
2
3
int a = 0; //芝士什么? 变量
const inst b = 0; //芝士什么? 常量?? 常量也是变量???
const volatile int c = 0; //芝士什么? (无法解释)

引例2:常量 vs. 常量表达式

形式 名称
10 整数常量
+10 常量表达式

现有学习资料和标准的概念体系有一定差异

  1. 核心概念介绍缺失
    对象、lvalue、rvalue等核心概念介绍不详细或者不涉及
  2. 部分概念混淆有误
    变量、常量的划分方法,常量和常量表达式的混淆等
  3. 存在许多自创概念
    常变量、只读变量、行指针、列指针、数组指针、指针数组、多级指针、模拟引用传递

标准一般化为什么重要?

都是语法糖,没有意义?会用就行了?

如果使用变量术语,只有int a = 0;是变量,其它定义均不知所谓
实际上,它们都是“变量标识符”

标准中的逻辑规则强调的就是一般化

一般化的基础:以对象为核心构建概念体系

  1. 对象的基本概念(对象的各种属性)
  2. 如何分配一个对象(4种分配方式)
  3. 如何定位一个对象(17类表达式)
  4. 如何获得lvalue和non-lvalue表达式的相关信息
  5. 如何释放对象占用的内存

本课程内容简介

C语言知识类别

  1. 语法类知识
  2. 概念类知识
  3. 策略类知识

学习周期和考核方法

学习时长1-9周(16学时,1学分)

考核方法:考试(开卷,带什么资料都行)+随堂测验(上课的时候带张草稿纸)


(正片开始)

内存基本概念简单回顾

1byte=8bits?

<limits.h>定义了一个宏CHAR_BIT

C语言规定一个byte等于CHAR_BIT个bit

CHAR_BIT的限制是>=8(即不一定是8位)

地址是字节在内存中的编号,只有字节才有地址

C语言最重要的概念:对象

  1. 对象的概念
  2. 如何刻画对象的属性
  3. 理解对象该类别
  4. 理解指针和数组也是一种普通的对象类型

对象表示(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
2
3
4
5
#ifdef ImplementationA
typedef int INT32;
#elifdef ImplementationB
typedef int INT16;
#endif

用法:

1
2
3
typedef T alias; //非数组非指针
typedef T Alias[N]; //数组类型
typedef T* Alias; //指针类型

对齐要求

对象对齐要求一定是$2^n$

n越小越weak,越大约strong

枚举类型

  1. 在枚举定义时指定类型

    1
    enum Season
  2. 不指定类型,自动指定为char、标准有符号类型、标准无符号类型、扩展有符号证书类型、扩展无符号整数类型的一种

对象分类——派生类型(Derived Type)

重点介绍数组类型和指针类型

数组类型

形式化定义:T[N]

  • T:元素类型,必须是完全对象类型
  • N:元素类型的个数

N为表达式:

  1. 无N,不完全对象类型
  2. 有N,N为整数常量或整数常量表达式,普通数组类型
  3. 有N,不为整数常量或整数常量表达式,变长数组类型

数组类型也是一个合法的对象类型,有大小和对齐要求

typeof

数组类型可以视为一个整体,即:

1
typedef int AINT[5];

由于写法过于恶臭(确实),C23提供了一个关键字

1
2
typedef typeof(int[5]) AINT;
// typeof T Alias;

从数组类型T继续派生

由于数组类型也为完全对象类型,所以仍可继续派生

于是出现了二维数组!

1
2
3
4
int a[10][5];
typedef int AINT[5];
typedef AINT AAINT[10][5];
AAINT a;

数组都是“一维”的

指针类型

形式化定义: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
2
3
4
5
Q T = T Q //非数组非指针
Q T[N] //数组类型,元素类型Q T
T[N] Q //不合法
Q T*//类型为Q T
T* Q //Q限制T*类别 <-------推荐的

理解const int* const

  1. const int,限定类型,限定int

  2. const int*,指针类型,Referenced Type为const int

  3. 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 实现定义

字符序列中的字符包括:

  1. 所有char,除了" \ 实际的回车
  2. \引导的转义字符

步骤(以初始化“hello”为例):

  1. 在内存中分配一个大小合适的char类型数组对象
  2. 用’h’,‘e’,‘l’,‘l’,‘o’,'\0’初始化数组的每一个元素
  3. 该对象为匿名对象
  4. 当第二次初始化“hello”时,不一定需要分配一个新的地址空间(如"hello"和L"hello")

String Literal vs. String

双引号内不包含"\0"的String Literal叫做String,String Literal包含String

Compound Literal

initalizier

初始化对象的对象表示

标量类型initiallizer形式:

  • {},空初始化列表
    • 将对象的值设置为该类型的缺省值
  • 除逗号表达式之外的任意表达式epx或{exp}

数组类型initiallizer形式:

  • {},空初始化列表
    • 将对象的值递归设置为该类型的缺省值
  • {sub-initalizer1,…}
    • 设置前n个对象,剩下设置为缺省值

当对变长数组初始化时,只能使用{}进行初始化

没有initializer时,对象值初始化为indeterminate representation(不确定值)

对象值、对象表示一一对应