C++20--modules特性
头文件的缺陷
长期以来,#include 指令一直是我们从C++库中导入功能和组织更大项目的主要方式。然而,这是一种过于粗糙的方法,实际上是将所有代码从一个源文件复制并粘贴到另一个源文件中。正如我们之前讨论的,这有一些问题:
一个源文件可以被多次包含进同一个目标文件,这促使我们总是要实现像 #pragma once 这样的头文件保护。
巨大的代码量可以被粘贴到我们的文件中,导致编译时间比必要的时间更长。在更大的项目中,这迫使我们采取更多的手动解决方法来保持编译时间的降低,比如维护预编译头文件。
预处理器的粗糙性可能导致难以追踪的微妙错误,例如我们的程序根据我们的 #include 指令的顺序表现出不同的行为。
减少对预处理器的依赖是C++社区的持续使命。在C++20规范中,引入了 #include 指令的替代品。它们被称为模块。
在C++20中,模块是一种新的代码组织方式,旨在解决传统头文件(使用 #include 指令包含的文件)所带来的一些问题。模块提供了一种更高效、更安全的方式来包含和使用代码,它们通过使用新的 module 和 import 关键字来实现。使用模块可以减少编 ...
编译原理第二章
这一章主要介绍文法,并且快速游览一下下图所示的编译器前端模型。
上下文无关文法
用于描述程序设计语言语法的表示方法——“上下文无关文法“,简称“文法”。文法被用于组织编译器前端。
产生式
文法自然地描述了大多数程序设计语言构造的层次化语法结构。例如,java中的if-else语句通常有如下形式
1if(expression) statement else statement
我们用变量expr表示表达式,用变量stmt表示语句,那么这个构造规则可以表示为
1stmt -> if(expr) stmt else stmt
其中->,可以读作“可以具有如下形式”。这样的规则称为产生式(production)。在一个产生式中,像if和括号这样的词法元素称为终结符号(可以简单理解成token,组成语句的基本单位)。像expr和stmt这样的变量表示终结符号的序列,它们称为非终结符号(nonterminal)(可以简单认为多个token组成的语句/表达式)。
文法定义
一个上下文无关文法由四个元素组成:
一个终结符号集合,也称为“词法单元”。终结符号是该文法所定义的语言的基本符号 ...
CSAPP-note-6
CPU只能直接理解机器码。所有的编程语言必须经历下面其中一步:
解释:通过软件执行。编译:通过软件翻译成机器码。
源代码被编译成可执行文件的过程中发生了什么?
预处理器:将源代码的头文件进行展开,并且进行宏替换。生成.i文件。
编译器:将.i文件编译成汇编文件.s
汇编器:将汇编文件编译为目标机器码(.o,.obj)
链接器:链接动态库/静态库和目标文件,组成可执行文件.exe
基础的编译器优化
目标
最小化指令条数
不要多次执行计算
不进行不必要的计算
避免使用开销较大的指令
避免访问内存
尽可能使用寄存器
以缓存友好的方式访问内存
尽早且一次从内存加载数据
避免分支
在任何时候,不做不必要的判断
使CPU更容易预测分支目的
循环展开
局限性
通常不能提升算法的复杂度
只能改善常数因子,但是也能提升10倍甚至更多
不能引起程序行为的任何改变
程序员可能不关心边界情况,但是编译器不知道
例外:编程语言可能声明某些改变是可以接受的
通常只分析一个函数
分析整个程序的成本太高
不能预测运行时输入
“最坏情况“的性能和“正常”性能一样重要
特别是对暴露于恶意输入的代 ...
Attack Lab
前置知识
汇编,过程调用,栈帧等。
phase_1
在第一问中,无需注入新的代码,我们只用直接调用已经存在的函数touch1()即可。如果看懂了Machine-Level Programming V: Advanced Topics这将会非常容易实现,我们只需要几个简单的步骤即可。
首先,我们需要知道getbuf函数栈帧的位置。
1234567Dump of assembler code for function getbuf: 0x00000000004017a8 <+0>: sub $0x28,%rsp 0x00000000004017ac <+4>: mov %rsp,%rdi 0x00000000004017af <+7>: call 0x401a40 <Gets> 0x00000000004017b4 <+12>: mov $0x1,%eax 0x00000000004017b9 <+17>: add $0x28,%rsp ...
CSAPP-note-5
内存布局
x86-64 Linux 内存布局
栈:Runtime stack(8MB limit),用来存储局部变量等。
堆:根据需要动态进行分配。在调用malloc(),calloc(),new()时,在堆上进行内存分配。
数据段(data):静态地分配数据。如:全局变量,静态变量,字符串常量。
文本段/共享存储区(text/shared Libraries):可执行的机器指令(只读)。
让我们看一个例子
12345678910111213char big_array[1L<<24]; /* 16MB */char huge_array[1L<<31]; /* 2GB */int gobal = 0;int useless() { return 0; }int main() { void *phuge1, *psmall2, *phuge3, *psmall4; int local = 0; phuge1 = malloc(1L<<28); psmall2 = malloc(1L<< ...
CSAPP-note-4
Arrays
Array Allocation
Basic Principle:T A[L];
Array of data type T and length L
Contiguously allocated region of L*sizeof(T) bytes in memory
Array Access
1int val[5] = {1, 5, 2, 1, 3};
表达式
类型
值或含义
val[4]
int
3
val
int *
x
val+1
int *
x+4
&val[2]
int *
x+8
val[5]
int
??
*(val+1)
int
5
val + i
int *
x+4*i
Example
12345#define ZLEN 5typedef int zip_dig[ZLEN];zip_dig cmu = {1,5,2,1,3};zip_dig mit = {0,2,1,3,9};zip_dig ucb = {9,4,7,2,0 ...
Bomb Lab
前置知识
常见的汇编指令,特别是各种跳转指令的区别。
http://unixwiz.net/techtips/x86-jumps.html
基础的gdb调试操作,汇编调试(tui)。
phase_1
1234567891011(gdb) disassemble phase_1Dump of assembler code for function phase_1: 0x0000000000400ee0 <+0>: sub $0x8,%rsp 0x0000000000400ee4 <+4>: mov $0x402400,%esi 0x0000000000400ee9 <+9>: call 0x401338 <strings_not_equal> 0x0000000000400eee <+14>: test %eax,%eax 0x0000000000400ef0 <+16>: je 0x400ef7 <phase_1+23> ...
CSAPP-note-3
x86-64 Stack
Region of memory managed with stack discipline.Grows toward lower address.Register %rsp contains lowest stack address.(address of “top” element)
Push
pushq Src
Fetch operand at Src
Decrement %rsp by 8
Write operand at address given by %rsp
Pop
popq Dest
Read Value at address given by %rsp
Increment %rsp by 8
Store value at Dest (must be register)
Calling Conventions
Passing control
在下面的C程序中,我们在main函数调用了multsotre,在multsotre中调用了mult2。我们通过这个例子来观察函数调用的过程。
12345678910111213141516#inc ...
CSAPP-note-2
Control: Condition codes
Processor State(x86-64,Partial)
Information about currently executing program
Temporary data(%rax, …)
Location of runtime stack(%rsp)
Location of current code control point(%rip,…)
Status of recent tests(CF,ZF,SF,OF)
Condition Codes(Implicit Setting)
Single bit registers
Example: addq Src,Dest <-> t = a + b
CF Carry Flag(for unsigned): if carry out from most significant bit(unsigned overflow)
ZF Zero Flag: set if t == 0
SF Sign Flag(for signed): set if t < 0 ...
类型转换
隐式转换
C++语言不会直接将两个不同类型的值相加,而是先根据类型转换规则设法将运算对象的类型统一后再求值。上述的类型转换是自动执行的,无须程序员的主动介入,因此,它们被称作隐式转换。
1int ival = 3.5 + 3 // 编译器警告,精度丢失
算术类型之间的隐式转换被设计得尽可能避免损失精度。比如在表达式中既有int,也有double时,int会转化为double,然后执行浮点数的加法,最后得到double。将double用于初始化int时,加法运算得到的double类型转化成int类型的值,这个值被用来初始化ival。
何时发生?
在大多数表达式中,小的整型值首先提示为较大的整数类型。
在条件中,非布尔值转换为布尔类型。
初始化过程中,初始值转换成变量的类型;在赋值语句中,右侧运算对象转换成左侧运算对象的类型。
如果算术运算或关系运算的运算对象有多种类型,需要转换成同一中类型。
函数调用时也会发生类型转换。
算术转换
算术转换的含义是把一种算术类型转换成另外一种算术类型。算术转换的规则定义了一套类型转换的层次,其中运算符的运算对象将转换成最宽的类型。当表达式中既有浮点类 ...