C语言系统编程笔记

\(\mathcal{Author:gpf}\)

一些宏定义

1个byte等于 CHAR_BIT 个bit,C语言规定至少为8

Fundamental Alignment:基础对齐要求,alignof(max_align_t)。课程假设为16

对象概念

  • Address:这一段内存lowest字节的编号
  • Object_Type:对象类型T(可以没有)
  • Name:对象名称(可以没有)
  • Size:内存大小sizeof(T)(字节数量),不完全类型没有大小
  • Object Value:对象的值通过对象类型映射对象表示(各个bit组成的二进制串,包括paddingbits),值一样,表示不一定一样
  • Alignment:对齐要求alignof(T)(地址被对齐要求整除),一定是2^n
  • Value:对象的表示值
  • Value Type:表示值类型

表示值与表示值类型获取规则:

  1. 非数组对象类型:表示值就是对象值,表示值类型是对象类型的非限定类型
  2. 数组对象类型:表示值是第一个元组对象的地址,表示值类型是元素类型对应的指针类型

对象七元组去掉了Object Value

不完全对象类型如void,int[]没有size和alignment

signed char/unsigned char对齐要求是weakest,不一定是1

各种类型

类型:

  • 算术类型
    • 整数类型
      • char
      • usigned integer type
      • signed integer type
      • enumerated type(枚举)
    • 浮点类型
      • real floating type
      • complex type(复数)
  • 派生类型
    • Array Type
    • Pointer Type
    • Struct Type
    • Union Type
    • Atomic Type
    • Function Type
  • 限定类型
    • _Atomic
    • const
    • volatile
    • restrict

标量类型:算术类型、指针类型、nullptr_t

非标量类型:数组类型、结构体类型、联合体类型、限定类型

基本类型

Basic Type:

  • char(编译器指定与signed char或unsigned char之一行为一致)
  • unsigned integer type
  • signed integer type
  • floating type

整数与枚举

有符号整数的宽度要求:

sign bit(1)+value bit+padding bit,宽度为sign+value=N,精度为N-1

其中,signed char不允许有padding

无符号整数的宽度要求:

value bit+padding bit,宽度为value=N,精度为N

bool类型就一定有padding了

类型 宽度
signed char CHAR_BIT
short int >=8
int >=16
long int >=32
long long int >=64
unsigned char CHAR_BIT
unsigned short int >=8
unsigned int >=16
unsigned long int >=32
unsigned long long int >=64
bool 1

课程中假设char=1,short=2,int=4,float=4,double=8.pointer type=4,对齐要求同数值

枚举类型需要指定实现类型,如果不指定则由编译器设置为以下5中类型之一:char、标准有符号整数类型、标准无符号整数类型、扩展有符号整数类型、以及扩展无符号整数类型

数组、指针类型

T[N],T必须是完全对象类型,N缺失则是不完全对象类型,N是整数常量或整数常量表达式则为普通数组类型,其他N是变长数组类型

T*,T是对象类型,指针类型本身是完全对象类型

类型 派生数组类型 派生指针类型
T T[N] T*
T[M] T[N][M] T(*)[M]
T* T*[N] T**

变长数组variable length array(VLA)

变长数组类型T[M],元素类型为T,元素个数是M。T[M]是一个变长数组类型,如果满足:

  • M不是一个整数常量或整数常量表达式
  • T是一个变长数组

限定类型

类型T,限定词Q

类型 Q在T左边 Q在T右边
T Q T与右边等价 T Q
T[N] Q T[N],修饰元素类型 T[N] Q,不合法
T* Q T*,修饰reference的类型 T* Q,修饰T*类型

限定词无法直接限定数组对象类型

Q typeof(T)[N]=Q typeof(T(N))

多个同样限定符等价于一个限定符

函数类型和函数指针类型

1
2
3
4
int func(int a,int b)
{
return 0;
}

声明了一个函数func,类型为 int(int,int) 。对应的函数指针为 int(*)(int,int)

function designator:能定位函数对象的表达式,共以下两种:

  1. 函数标识符
  2. 若一个表达式的rvalue是函数指针类型,则*exp是一个合法的function designator

func的rvalue和&func的结果相同,func和*func定位的对象一样

函数传参时,实参需要evaluate

若fun()返回结构体类型,fun().a是表达式,但不是左值

对象分配

(type-name) braced-initializer

(int){1}(int[2]){1,2} 等等,分配了一个匿名对象

malloc

void* malloc(size_t size);

分配一个匿名对象,该对象无类型;对象大小是size;对象表示不确定;返回值为空指针或对象的首地址;存储周期是allocated;对齐要求是基础对齐要求(保证可以强制转换成任何合法的对象指针类型)

aligned_alloc

aligned_alloc(align,size)

可以指定对齐要求,但超过FUN……可能返回NULL,小于FUN……可能引起指针强制转换问题

表达式

左值:能定位对象的表达式

  1. 对象表示符
  2. 数组下标运算表达式
  3. *exp
  4. String Literal
  5. Compound literal
  6. 指向结构体、联合体的lvalue.member
  7. 指向结构体、联合体指针表达式->member

若左值定位的一个对象类型T满足:T是数组类型/const限定类型/不完全对象类型/结构体或联合体类型,其成员对象有const限制,则被称为不可修改左值

表达式:由一系列operator和operand组成的序列

右值:表达式经evaluate得到的值

evaluate:计算值+确定副作用

lvalue conversion:左值转换,即非数组类型对象evaluate的过程(得到表示值和表示值类型)

decay:退化,即数组类型对象evaluate的过程(得到表示值和表示值类型)

表达式类别

注意typeof(T)不是表达式

image-20241123151010486

基础表达式:

  • 标识符:包括对象标识符和函数标识符
  • 常量
  • 字符串
  • 括号表达式
  • 泛型选择

后缀表达式

a[]node.namenode->namea++/a--、(type-name){Initializer-list}、函数调用的小括号

一元表达式

1
2
3
4
5
6
7
//一元表达式
a++;a--;
&a;
*p;
+a;-a;
~a;!a;
sizeof(a);_Alignof(a);

常量

  • 整数常量10
  • 浮点数常量10.1
  • 枚举常量enum DAYS{a,b,c}
  • 字符常量'a'
  • 预定义常量false,true,nullptr
整数常量

组成:进制前缀+数字序列+类型后缀

数字序列可以加分隔符 ' ,但只能在中间加,不能在首尾加

进制前缀 进制 类型后缀 类型
0x/0X 16进制 u、U usigned int等
十进制 l、L long int等
0 八进制 ll、LL long long int
0b/0B 二进制 wb、WB
浮点数常量

进制前缀+符号序列+后缀

有十进制(无前缀)和十六进制(0x前缀)两种,也可使用分隔符

十进制的符号序列:由整数部分、小数点和小数部分构成的一个十进制实数+一个字符e+一个由+或-引导的表示幂的指数部分,没有+号默认为正数

十六进制:前缀为0x,实数部分是以十六进制来表示,字符e替换成p,幂的值还是十进制表示,表示是2的幂次方,十六进制浮点数常量,p和指数部分不能省略

字符常量

'a'

预定义常量

false,true,nullptr

常量表达式

不包含赋值、自增自减、函数调用、逗号运算符,除非他们被包含在不被evaluate的子表达式中

常量表达式evaluate之后的rvalue的取值范围,应该在表达式rvalue类型的表征范围之内

整数常量表达式:表达式rvalue类型为整数类型

constexpr关键字修饰的也是常量

整数常量表达式需要满足的条件:

  1. 表达式rvalue是整数类型
  2. operand是整数常量
  3. 字符常量
  4. 类型为整数的named constant(用constexpr修饰)
  5. 类型为整数的compound literal constant((constexpr int){1})
  6. alignof表达式
  7. cast表达式,其中cast的子表达式是浮点常量、类型为算术类型的named constant和compound literal constant,除非该cast表达式时typeof、sizeof、alignof操作符的operand

sizeof返回值

sizeof后跟type-name或exp

  1. type-name
    • 若type-name不是变长数组类型,则整体不做evaluate,而且返回值是整数常量
    • 若是VLA,则子表达式需要evaluate,返回值不是整数常量
  2. exp是lvalue,则与type-name规则近似
  3. exp不是lvalue,则返回exp做evaluate之后rvalue类型的大小,但并不会真的做evaluate

typeof对变长数组的处理和sizeof一样

evaluate规则

定位非数组对象的lvalue

若该表达式

  1. 与sizeof结合
  2. 与typeof结合
  3. 与&结合
  4. 与一元运算符++--和后缀运算符++--结合
  5. 定位对象类型是结构体/联合体,且跟.结合
  6. 出现在赋值运算符左侧

除以上6种情况,表达式都要做evaluate

定位数组对象的lvalue

除非该表达式

  1. 与sizeof结合
  2. 与typeof结合
  3. 与&结合
  4. 定位的是一个string literal,且用于初始化一个字符数粗

否则都要做evaluate

字符串

1
2
3
4
5
"hello";	//evaluate,以下四种都不做
sizeof("hello");
typeof("hello");
&"hello";
char str[]="hello";

Compound Literal

分配一个指定类型的对象,定位刚刚分配的那个对象

与非数组对象的evaluate规则相同

side effect

状态的改变。A sequence point B保证A sequenced before B

  1. Function Designator和实参的evaluation,和实际函数调用执行之间

  2. 在&&、||、逗号运算符分隔的前后两个表达式之间

  3. ?:三目运算表达式中,?之前表达式以及之后执行的表达式之间

  4. 两个full expression之间,full expression包括例如:

    表达式语句(分号)、if、while、do、for,return等控制条件表达式等

  5. 库函数调用之前

  6. printf/scanf、fpritnf/fscanf、sprint/sscanf等一系列输入输出中按转换说明符执行完转换动作之后

  7. bsearch,qsort等比较函数调用之前和之后以及调用比较函数和对象移动之间

*exp

设对表达式进行evaluate,rvalue为<Value,Value_Type>,如果Value_Type是一个对象指针类型,则可以用*exp的方式来定位一个对象M

  1. 该对象的address为Value
  2. 该对象的Object Type为Value_Type对应的referenceType

仅定位对象,不是获得表达式的值

int *p;p+n的value是p的value+sizeof(*p)*n

存储周期、作用域、?

scope与space

scope:function、function prototype、file、block

space:label nametagmember of structures or unions、standard attributes and attribute prefixes、trailing identifier in an attribute prefixed token、ordinary identifiers

分别对应goto的标签、类型名、结构体等的属性、普通变量名

Linkage

如果一个对象标识符拥有file scope,且被static或constexpr修饰,则是internal linkage

internal linkage的标识符无法被其他编译单元使用

如果没有static声明,则被external修饰的标识符的linkage是external linkage

static

thread

automatic

allocated

杂七杂八

初始化

标量类型初始化

{},exp(除逗号表达式外),{exp}

整数对象缺省值是+0或无符号0,十进制浮点对象缺省值是+0,其他浮点数缺省值是+0或无符号0,指针类型缺省值是空指针

数组类型对象初始化

{},{sub-initializer1,...,sub-initializern},每个sub-initializer1用于初始化数组中的一个元素对象,若元素对象依然是一个数组对象,则可以再次使用数组对象初始化的形式

当数组类型T[N]的N不存在时:

  • 不能使用{}进行初始化
  • 使用{exp1,...,expn}时,n即数组对象的元素个数

当数组类型为变长数组时:

  • 只能使用{}进行初始化

String Literal

构成字符串的字符:

  • 所有源字符集中的字符,除了双引号、反斜杠、实际的回车
  • 反斜杠引导的转义字符如 \",\\,\n

编码前缀:

编码前缀 元素字符对象类型 编码规则
u8 char8_t UTF-8
u char16_t UTF-16
U char32_t UTF-32
L wchar_t 实现定义

匿名对象,具有静态存储周期

多个"hello"字符串可能分配一个对象,也可能分配多个对象

双引号中不显示包含\0字符的String Literal被称为String

相邻的多个字符串可合并成一个,如 "aa""bb" 等价于 "aabb"

地址常量

包括:

  1. 空指针

  2. 指向一个static storage duration的对象的指针(编译期间可知)

  3. 指向Function Designator的指针

获得方法:

  1. &得到的rvalue
  2. 显示将整数常量强制转换成指针类型获得的rvalue
  3. 隐式使用数组类型表达式获得的rvalue
  4. 隐式使用函数类型表达式获得的rvalue

不属于常量!

限定词

volatile

int volatile a = 10;

volatile修饰的内存的值可能会以某种未知方式发生变化,如

  1. MMIO端口,通过内存地址映射IO
  2. 异步中断函数访问的对象

volatile对象如果要evaluate,必须访问对应的内存,不可通过缓存等其他方式

如果推断出表达式无效,也可以不evaluate

赋值表达式的值是表达式执行完成后等号左边对象的值,但不强制要求通过访问对象来获得值

restrict

使用:T* restrict O;

给定指针表达式E,如果Obj值修改,用于定位一个新的数组(即便这个新的数组对象各元素对象是Obj原始值可以定位的数组各元素对象的拷贝),表达式E的值也会被修改,则E Based on Obj

如果有一个左值表达式L,其地址&L是Based on一个对象P。该左值表达式L其定位的对象假设为X,如果X对象的值被修改。有另一个左值表达式M能访问X,则M的地址也必须based on对象P

_Alignof和_Alignas

_Alignas可用于修改对齐要求,对齐超过fundamental slignment时,由编译器决定支不支持

_Alignas(64) int a

结构体类型T,成员对象分别是Ei,则 _Alignof(T)=max{_Alignof(Ei)}

#pragma pack(n)可用于调整结构体对象的对齐,使用后,_Alignof(Ei)=main{_Alignof(Ei),n}


C语言系统编程笔记
https://solor-wind.github.io/2025/01/01/C语言系统编程笔记/
作者
gpf
发布于
2025年1月1日
许可协议