期中复习
按键 |
作用 |
Ctrl+d |
键盘输入结束或退出终端 |
Ctrl+s |
暂定当前程序,暂停后按下任意键恢复运行 |
Ctrl+z |
将当前程序放到后台运行,恢复到前台为命令fg |
Ctrl+a |
将光标移至输入行头,相当于Home键 |
Ctrl+e |
将光标移至输入行末,相当于End键 |
Ctrl+k |
删除从光标所在位置到行末 |
Alt+Backspace |
向前删除一个单词 |
Shift+PgUp |
将终端显示向上滚动 |
Shift+PgDn |
将终端显示向下滚动 |
Shell 常用通配符:
字符 |
含义 |
* |
匹配 0 或多个字符 |
? |
匹配任意一个字符 |
[list] |
匹配 list 中的任意单一字符 |
[!list] |
匹配 除list 中的任意单一字符以外的字符 |
[c1-c2] |
匹配 c1-c2 中的任意单一字符 如:[0-9] [a-z] |
{string1,string2,...} |
匹配 sring1 或 string2 (或更多)其一字符串 |
{c2..c2} |
匹配 c1-c2 中全部字符 如{1..10} |
who 命令其它常用参数
参数 |
说明 |
-a |
打印能打印的全部 |
-d |
打印死掉的进程 |
-m |
同am i,mom likes |
-q |
打印当前登录用户数及用户名 |
-u |
打印当前登录用户登录信息 |
-r |
打印运行等级 |
使用groups命令
查看/etc/group文件
删除用户
查看文件权限
我们之前已经很多次用到 ls 命令了,如你所见,我们用它来列出并显示当前目录下的文件,当然这是在不带任何参数的情况下,它能做的当然不止这么多,现在我们就要用它来查看文件权限。
变更文件所有者
vim基本操作
1.六种基本模式:
(1)普通模式(Normal mode)
- 在普通模式中,用的编辑器命令,比如移动光标,删除文本等等。这也是Vim启动后的默认模式。这正好和许多新用户期待的操作方式相反(大多数编辑器默认模式为插入模式)。
- 在普通模式中,有很多方法可以进入插入模式。比较普通的方式是按a(append/追加)键或者i(insert/插入)键。
(2)插入模式(Insert mode)
- 在这个模式中,大多数按键都会向文本缓冲中插入文本。大多数新用户希望文本编辑器编辑过程中一直保持这个模式。
- 在插入模式中,可以按ESC键回到普通模式。
(3)可视模式(Visual mode)
- 这个模式与普通模式比较相似。但是移动命令会扩大高亮的文本区域。高亮区域可以是字符、行或者是一块文本。当执行一个非移动命令时,命令会被执行到这块高亮的区域上。Vim的"文本对象"也能和移动命令一样用在这个模式中。
(4)选择模式(Select mode)
- 这个模式和无模式编辑器的行为比较相似(Windows标准文本控件的方式)。这个模式中,可以用鼠标或者光标键高亮选择文本,不过输入任何字符的话,Vim会用这个字符替换选择的高亮文本块,并且自动进入插入模式。
(5)命令行模式(Command line mode)
- 在命令行模式中可以输入会被解释成并执行的文本。例如执行命令(:键),搜索(/和?键)或者过滤命令(!键)。在命令执行之后,Vim返回到命令行模式之前的模式,通常是普通模式。
(6)Ex模式(Ex mode)
- 这和命令行模式比较相似,在使用:visual命令离开Ex模式前,可以一次执行多条命令。
2.常用命令总结
(1)插入
i 在当前光标处进行编辑
I 在行首插入
A 在行末插入
a 在光标后插入编辑
o 在当前行后插入一个新行
O 在当前行前插入一个新行
cw 替换从光标所在位置后到一个单词结尾的字符
(2)退出
:q! 强制退出,不保存
:q 退出
:wq! 强制保存并退出
:w <文件路径> 另存为
:saveas 文件路径 另存为
:x 保存并退出
:wq 保存并退出
(3)删除
x 删除游标所在的字符
X 删除游标所在前一个字符
Delete 同x
dd 删除整行
dw 删除一个单词(不适用中文)
d$或D 删除至行尾
d^ 删除至行首
dG 删除到文档结尾处
d1G 删至文档首部
(4)行间跳转
nG(n Shift+g) 游标移动到第 n 行
(如果默认没有显示行号,请先进入命令模式,输入:set nu以显示行号)
gg 游标移动到到第一行
G(Shift+g) 到最后一行
(5)行内跳转
w 到下一个单词的开头
e 到下一个单词的结尾
b 到前一个单词的开头
ge 到前一个单词的结尾
0或^ 到行头
$ 到行尾
f<字母> 向后搜索<字母>并跳转到第一个匹配的位置(非常实用)
F<字母> 向前搜索<字母>并跳转到第一个匹配的位置
t<字母> 向后搜索<字母>并跳转到第一个匹配位置之前的一个字母(不常用)
T<字母> 向前搜索<字母>并跳转到第一个匹配位置之后的一个字母(不常用)
(6)复制与粘贴
- 普通模式中使用y复制
yy 复制游标所在的整行(3yy表示复制3行)
y^ 复制至行首,或y0。不含光标所在处字符。
y$ 复制至行尾。含光所在处字符。
yw 复制一个单词。
y2w 复制两个单词。
yG 复制至文本末。
y1G 复制至文本开头。
普通模式中使用p粘贴
p(小写) 代表粘贴至光标后(下)
P(大写) 代表粘贴至光标前(上)
(7)替换和撤销
r+<待替换字母> 将游标所在字母替换为指定字母
R 连续替换,直到按下Esc
cc 替换整行,即删除游标所在行,并进入插入模式
cw 替换一个单词,即删除一个单词,并进入插入模式
C(大写) 替换游标以后至行末
~ 反转游标所在字母大小写
u{n} 撤销一次或n次操作
U(大写) 撤销当前行的所有修改
Ctrl+r redo,即撤销undo的操作
(8)快速缩进与文本位置
>> 整行将向右缩进
<< 整行向左回退
:set shiftwidth? 获取目前的设定值
:set shiftwidth=10 设置缩进为10个字符
:ce 本行内容居中
:ri(right) 本行文本靠右
:le(left) 本行内容靠左
(9)查找
/icmp 查找字符串icmp
n 查找下一个icmp
?tcp 向上查找字符串tcp
N 查找上一个出现的tcp
\* 寻找游标所在处的单词
\# 同上,但\* 是向前(上)找,#则是向后(下)找
g\* 同\* ,但部分符合该单词即可
g\# 同\# ,但部分符合该单词即可
(10)多文件编辑
vim 1.txt 2.txt
:n 编辑2.txt文件,可以加!即:n!强制切换
:N 编辑1.txt文件,可以加!即:N!强制切换,
注:之前文件内的输入没有保存,仅仅是切换到另一个文件
进入vim后打开新文件
:e 3.txt 打开新文件3.txt
:e# 回到前一个文件
:ls 可以列出以前编辑过的文档
:b 2.txt(或编号) 可以直接进入文件2.txt编辑
:bd 2.txt(或编号) 可以删除以前编辑过的列表中的文件项目
:e! 4.txt 新打开文件4.txt,放弃正在编辑的文件
:f 显示正在编辑的文件名
:f new.txt 改变正在编辑的文件名字为new.txt
(11)视窗操作
:sp 1.txt 打开新的横向视窗来编辑1.txt
:vsp 2.txt 打开新的纵向视窗来编辑1.txt
Ctrl-w s 将当前窗口分割成两个水平的窗口
Ctrl-w v 将当前窗口分割成两个垂直的窗口
Ctrl-w q 即 :q 结束分割出来的视窗。如果在新视窗中有输入需要使用强制符!即:q!
Ctrl-w o 打开一个视窗并且隐藏之前的所有视窗
Ctrl-w j 移至下面视窗
Ctrl-w k 移至上面视窗
Ctrl-w h 移至左边视窗
Ctrl-w l 移至右边视窗
Ctrl-w J 将当前视窗移至下面
Ctrl-w K 将当前视窗移至上面
Ctrl-w H 将当前视窗移至左边
Ctrl-w L 将当前视窗移至右边
Ctrl-w 减小视窗的高度
Ctrl-w + 增加视窗的高度
(12)功能设定
:set或者:se 显示所有修改过的配置
:set all 显示所有的设定值
:set option? 显示option的设定值
:set nooption 取消当期设定值
:set autoindent(ai) 设置自动缩进
:set autowrite(aw) 设置自动存档,默认未打开
:set background=dark或light 设置背景风格
:set backup(bk) 设置自动备份,默认未打开
: set cindent(cin) 设置C语言风格缩进
(13)创建加密文档
$ vim -x file1
3.vimtutor练习教程实践
(1)文本简单操作
光标在屏幕文本中的移动既可以用箭头键,也可以使用 hjkl 字母键。
h (左移) j (下行) k (上行) l (右移)
欲进入vim编辑器(从命令行提示符),请输入∶vim 文件名 <回车>
欲退出vim编辑器输入以下命令放弃所有修改∶:q!<回车>
输入以下命令保存所有修改∶ :wq <回车>
在正常模式下删除光标所在位置的字符,请按∶ x
在光标所在位置开始插入文本,请按∶i 输入必要文本
(2)删除与撤销
欲从当前光标删除至单字/单词末尾,请输入∶dw
欲从当前光标删除至当前行末尾,请输入∶d$
欲删除整行,请输入∶dd
在正常模式下一个命令的格式是∶[number] command object 或 command [number] object
number - 代表的是命令执行的次数
command - 代表要做的事情,比如 d 代表删除
object - 代表要操作的对象,比如 w 代表单字/单词,$ 代表到行末等等。
欲撤消以前的操作,请输入∶u (小写的u)
欲撤消在一行中所做的改动,请输入∶U (大写的U)
欲撤消以前的撤消命令,恢复以前的操作结果,请输入∶CTRL-R
(3)置入、替换和更改
要重新置入已经删除的文本内容,请输入小写字母 p。该操作可以将已删除的文本内容置于光标之后。如果最后一次删除的是一个整行,那么该行将置于当前光标所在行的下一行。
要替换光标所在位置的字符,请输入小写的 r 和要替换掉原位置字符的新字符即可。
更改类命令允许您改变指定的对象,从当前光标所在位置直到对象的末尾。比如输入 cw 可以替换当前光标到单词的末尾的内容;输入 c$ 可以替换当前光标到行末的内容。
更改类命令的格式是∶
[number] c object 或者 c [number] object
(4)定位与搜索
Ctrl-g 用于显示当前光标所在位置和文件状态信息。Shift-G 用于将光标跳转至文件最后一行。先敲入一个行号然后按 Shift-G 则是将光标移动至该行号代表的行。
输入 / 然后紧随一个字符串是则是在当前所编辑的文档中向后查找该字符串。
输入问号 ? 然后紧随一个字符串是则是在当前所编辑的文档中向前查找该字符串。完成一次查找之后按 n 键则是重复上一次的命令,可在同一方向上查找下一个字符串所在;或者按 Shift-N 向相反方向查找下该字符串所在。
如果光标当前位置是括号(、)、[、]、{、},按 % 可以将光标移动到配对的
括号上。
在一行内替换头一个字符串 old 为新的字符串 new,请输入 :s/old/new
在一行内替换所有的字符串 old 为新的字符串 new,请输入 :s/old/new/g
在两行内替换所有的字符串 old 为新的字符串 new,请输入 :#,#s/old/new/g
在文件内替换所有的字符串 old 为新的字符串 new,请输入 :%s/old/new/g
进行全文替换时询问用户确认每个替换需添加 c 选项,请输入 :%s/old/new/gc
(5)外部命令与文件
:!command 用于执行一个外部命令 command。
例子∶
:!dir - 用于显示当前目录的内容。
:!rm FILENAME - 用于删除名为 FILENAME 的文件
:w FILENAME 可将当前 VIM 中正在编辑的文件保存到名为FILENAME 的文件中。
:#,#w FILENAME 可将当前编辑文件第 # 行至第 # 行的内容保存到文件FILENAME 中。
:r FILENAME 可提取磁盘文件 FILENAME 并将其插入到当前文件的光标位置后面。
(6)插入
输入小写的 o 可以在光标下方打开新的一行并将光标置于新开的行首,进入插入模式。
输入大写的 O 可以在光标上方打开新的一行并将光标置于新开的行首,进入插入模式。
输入小写的 a 可以在光标所在位置之后插入文本。
输入大写的 A 可以在光标所在行的行末之后插入文本。
输入大写的 R 将进入替换模式,直至按 键退出替换模式而进入正常模式。
输入 :set xxx 可以设置 xxx 选项。
(7)在线帮助
按下 键 (如果键盘上有的话)
按下 键 (如果键盘上有的话)
输入 :help <回车>
提供一个正确的参数给":help"命令,您可以找到关于该主题的帮助。
(8)vim功能
开始编辑vimrc文件,这取决于您所使用的操作系统∶
:edit ~/.vimrc 这是Unix系统所使用的命令
:edit $VIM/_vimrc 这是Windows系统所使用的命令
接着导入vimrc范例文件∶:read $VIMRUNTIME/vimrc_example.vim
保存文件,命令为∶:write
(9)宏录制
qa 操作序列 q, @a, @@
qa 把你的操作记录在寄存器 a。于是 @a 会replay被录制的宏。
@@ 是一个快捷键用来replay最新录制的宏。
(10)分屏
:split → 创建分屏 (:vsplit创建垂直分屏)
dir:dir就是方向,可以是 hjkl 或是 ←↓↑→ 中的一个,其用来切换分屏。
_ (或 |) : 最大化尺寸 (| 垂直分屏)
+ (或 -) : 增加尺寸
三、GCC编译器
1.gcc支持编译的一些源文件后缀名
.c C语言源文件
.C .cc .cxx C++源文件
.m Object-C源文件
.i 经过预处理后的C源文件
.ii 经过预处理后的C++源文件
.s .S 汇编语言源文件
.h 预处理文件(头文件)
.o 目标文件
.a 存档文件
2.gcc编译流程
原文件->预处理->编译->汇编->连接->可执行文件
预处理:gcc –E hello.c –o hello.i
- gcc –E调用cpp 生成中间文件,可以让gcc在预处理结束后停止编译过程。
- 编 译:gcc –S hello.i –o hello.s
- gcc –S调用ccl翻译成汇编文件.
- 汇 编:gcc –c hello.s –o hello.o
- gcc -c 调用as 翻译成可重定位目标文件
- 链 接:gcc hello.o –o hello
- gcc -o 调用ld** 创建可执行目标文件
- ./name.out
- 运行这个可执行目标文件需要输入它的名字
注:请记住,gcc预处理源文件的时候(第一步),不会进行语法错误的检查。
语法检查会在第二步进行,比如花括号不匹配、行末尾没有分号、关键字错误等。
五、静态链接库与动态链接库
1.概念
通常情况下,对函数库的链接是放在编译时期(compile time)完成的。所有相关的对象文件(object file)与牵涉到的函数库(library)被链接合成一个可执行文件(executable file)。
静态链接库
- 程序在运行时,与函数库无关,因为所有需要的函数已拷贝到自己门下,所以这些函数库被成为静态库(static libaray)
- 通常文件名为“libxxx.a”的形式。
动态链接库
- 把对一些库函数的链接载入推迟到程序运行时期(runtime),这就是动态链接库(dynamic link library)技术
- 动态链接库的名字形式为 “libxxx.so” 后缀名为 “.so”
2.创建与主程序运行
静态链接库
- 使用归档工具ar将目标文件集成在一起:
- gcc –c file1.c file2.c
ar rcsv libxxx.a file1.o file2.o
- 编译主程序main.c,链接到生成的静态库libxxx.a 并运行
- gcc -o main main.c -L. -lxxx
./main
动态链接库
- 使用-fPIC选项为动态库构造一个目标文件
- gcc -fPIC -Wall -c file1.c
gcc -fPIC -c file2.c
- 使用-shared和和已创建的位置无关目标代码,生成一个动态库libxxx.so
gcc -shared -o libxxx.so file1.o file2.o
- 编译主程序main.c,链接到生成的动态库libxxx.a
gcc -o main main.c -L. -lxxx
- 注意:在运行可执行程序前需要注册动态库的路径名。例:将库文件直接复制到/lib目录下,然后运行
- cp libxxx.so /lib
./main
3.编译参数
- -c 只编译,不连接成为可执行文件。即把.c文件编译成.o文件
- -o 命名生成文件
- ar -r 在库中插入模块(替换)
- -c 创建一个库
- -s 写入一个目录文件索引到库中,即把两个.o文件归档成静态库存档文件.a并且写入目录文件索引到库中。
- -static 告诉编译器驱动程序,链接器应该构建一个完全链接的可执行目标文件,进行静态编译,即链接静态库,禁止使用动态库
- -shared 可以生成动态库文件,进行动态编译,尽可能的链接动态库,没有动态库时才会链接同名静态库
- -Lpath 表示在path目录中搜索库文件,,如-L.则表示在当前目录。
- -L dir 在库文件的搜索路径列表中添加dir目录
- -lname 链接称为libname.a或者libname.so的库文件。如果两个库文件都存在,根据编译方式是static还是shared进行链接。
- -fPIC 生成使用相对地址的位置无关的目标代码,然后通常使用gcc的-static选项从该pic目标文件生成动态库文件。不用此选项的话编译后的代码是位置相关的。
- -Ipaht 表示在path目录中搜索头文件。
- -ltest 编译器查找动态连接库时有隐含命名规则,名字前加lib后加.so确定库的名称。
- -Wall 允许发出gcc提供的所有有用的报警信息。
四、GDB调试技术
1.gdb主要功能
- (1)启动程序,可以按照用户自定义的要求随心所欲的运行程序。
- (2)可让被调试的程序在用户所指定的调试的断点处停住 (断点可以是条件表达式)。
- (3)当程序停住时,可以检查此时程序中所发生的事。
- (4)动态地改变程序的执行环境。
2.基本命令
(1)进入GDB
gcc -g test.c -o test
gdb test
- test是要调试的程序,进入后提示符变为(gdb) 。
(2)查看源码
(gdb) l
- 源码会进行行号提示。
- 如果需要查看在其他文件中定义的函数,在l后加上函数名即可定位到这个函数的定义及查看附近的其他源码。或者:使用断点或单步运行,到某个函数处使用s进入这个函数。
(3)设置断点
(gdb) b 6
- 这样会在运行到源码第6行时停止,可以查看变量的值、堆栈情况等;这个行号是gdb的行号。
(4)查看断点处情况
(gdb) info b
- 可以键入"info b"来查看断点处情况,可以设置多个断点;
(5)运行代码
(gdb) r
(6)显示变量值
(gdb) p n
- 在程序暂停时,键入"p 变量名"(print)即可;
- GDB在显示变量值时都会在对应值之前加上"$N"标记,它是当前变量值的引用标记,以后若想再次引用此变量,就可以直接写作"$N",而无需写冗长的变量名;
(7)观察变量
(gdb) watch n
- 在某一循环处,往往希望能够观察一个变量的变化情况,这时就可以键入命令"watch"来观察变量的变化情况,GDB在"n"设置了观察点;
(8)单步运行
(gdb) n
(9)程序继续运行
(gdb) c
- 使程序继续往下运行,直到再次遇到断点或程序结束;
(10)退出GDB
(gdb) q
3.四种断点
(1)函数断点
b 函数名 条件表达式
(2)行断点
b 行数或函数名 条件表达式
(3)条件断点
b 行数或函数名 if表达式
(4)临时断点
tbreak 行数或函数名 条件表达式
五、make与makefile
1.Makefile 介绍
make命令执行时,需要一个 Makefile 文件,以告诉make命令需要怎么样的去编译和链接程序。
- 首先,我们用一个示例来说明Makefile的书写规则(这个示例来源于GNU的make使用手册):我们的工程有若干个.C文件和.h文件,我们要写一个Makefile来告诉make命令如何编译和链接这几个文件。我们的规则是:
1)如果这个工程没有编译过,那么我们的所有C文件都要编译并被链接。
2)如果这个工程的某几个C文件被修改,那么我们只编译被修改的C文件,并链接目标程序。
3)如果这个工程的头文件被改变了,那么我们需要编译引用了这几个头文件的C文件,并链接目标程序。
- makefile带来的好处就是——“自动化编译”。只要我们的Makefile写得够好,所有的这一切,我们只用一个make命令就可以完成,make命令会自动智能地根据当前的文件修改的情况来确定哪些文件需要重编译,从而自己编译所需要的文件和链接目标程序。只需要一个make命令,整个工程完全编译,极大的提高了软件的开发效率。
2.Makefile的规则
target ... : prerequisites ...
command
...
...
- target也就是一个目标文件,可以是Object File,也可以是执行文件。还可以是一个标签(Label)。
- prerequisites就是,要生成那个target所需要的文件或是目标。
- command也就是make需要执行的命令。(任意的Shell命令)
这是一个文件的依赖关系,也就是说,target这一个或多个的目标文件依赖于prerequisites中的文件,其生成规则定义在command中。说白一点就是说,prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。这就是Makefile的规则,也是Makefile中最核心的内容。
3.make工作方式
- 在默认的方式下,也就是我们只输入make命令。那么,
- make会在当前目录下找名字叫“Makefile”或“makefile”的文件。
- 如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,它会找到“edit”这个文件,并把这个文件作为最终的目标文件。
- 如果edit文件不存在,或是edit所依赖的后面的 .o 文件的文件修改时间要比edit这个文件新,那么,他就会执行后面所定义的命令来生成edit这个文件。
- 如果edit所依赖的.o文件也不存在,那么make会在当前文件中找目标为.o文件的依赖性,如果找到则再根据那一个规则生成.o文件。(这有点像一个堆栈的过程)
- 当然,你的C文件和H文件是存在的,于是make会生成 .o 文件,然后再用 .o 文件生命make的终极任务,也就是执行文件edit了。
这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make根本不理。make只管文件的依赖性,即如果在找了依赖关系之后,冒号后面的文件还是不在,那么就停止工作。
执行步骤:
(1)读入所有的Makefile。
(2)读入被include的其它Makefile。
(3)初始化文件中的变量。
(4)推导隐晦规则,并分析所有规则。
(5)为所有的目标文件创建依赖关系链。
(6)根据依赖关系,决定哪些目标要重新生成。
(7)执行生成命令。
4.带宏的 Makefile
- Makefile中的宏,也称作变量。变量是在makefile中定义的名字,用来代替一个文本字符串,该文本字符串称为该变量的值。
- 定义变量的两种方式:
(1)递归展开方式
VAR=var
(2)简单方式
VAR:=var
使用变量的格式为:
$(VAR)
- 变量的分类:
- 用户自定义变量
- 预定义变量
- 自动变量
- 环境变量
5.自动变量
- 这些变量在规则每次执行时都基于目标和依赖产生新值。例如: 可以使用变量‘$@’代替目标文件名,变量‘$<’代替依赖文件名。
- 自动变量列表
- $@ 规则的目标文件名。如果目标是一个档案成员,则变量‘$@’ 档案文件的文件名。对于有多个目标的格式规则(参阅格式规则简介),变量‘$@’是那个导致规则命令运行的目标文件名
- $% 当目标是档案成员时,该变量是目标成员名,参阅使用make更新档案文件。例如,如果目标是‘foo.a(bar.o)',则‘$%'的值是‘bar.o',‘$@'的值是‘foo.a'。如果目标不是档案成员,则‘$%'是空值。
- $< 第一个依赖的文件名。如果目标更新命令来源于隐含规则,该变量的值是隐含规则添加的第一个依赖。参阅使用隐含规则。
- $? 所有比目标‘新’的依赖名,名字之间用空格隔开。对于为档案成员的依赖,只能使用已命名的成员。参阅使用make更新档案文件。
- $^ 所有依赖的名字,名字之间用空格隔开。对于为档案成员的依赖,只能使用已命名的成员。参阅使用make更新档案文件。对同一个目标来说,一个文件只能作为一个依赖,不管该文件的文件名在依赖列表中出现多少次。所以,如果在依赖列表中,同一个文件名出现多次,变量‘$^’的值仍然仅包含该文件名一次。
- $+ 该变量象‘$^',但是,超过一次列出的依赖将按照它们在makefile文件中出现的次序复制。这主要的用途是对于在按照特定顺序重复库文件名很有意义的地方使用连接命令。
$* 和隐含规则匹配的stem(径),参阅格式匹配。如果一个目标为‘dir/a.foo.b',目标格式规则为:‘a.%.b' ,则stem为‘dir/foo'。在构建相关文件名时stem 十分有用。在静态格式规则中,stem是匹配目标格式中字符‘%’的文件名中那一部分。在一个没有stem具体规则中;变量‘$*' 不能以该方法设置。如果目标名以一种推荐的后缀结尾,变量‘$*'设置为目标去掉该后缀后的部分。例如,如果目标名是‘foo.c',则变量‘$*' 设置为‘foo', 因为‘.c' 是一个后缀。在隐含规则和静态格式规则以外,应该尽量避免使用变量‘$*'。在具体规则中如果目标名不以推荐的后缀结尾,则变量‘$*’在该规则中设置为空值。
三种重要的数字表示
(1)无符号数、有符号数、浮点数
- 正数的原码、反码以及补码是其本身。
- 负数的原码是其本身,反码是对原码除符号位之外的各位取反,补码则是反码加1。
(2)为什么用补码表示
- 能够统一+0和-0的表示
- 采用原码表示,+0的二进制表示形式为0 000 0000,而-0的二进制表示形式为1 000 0000;
- 采用反码表示,+0的二进制表示形式为0 000 0000,而-0的二进制表示形式为1 111 1111;
- 采用补码表示,+0的二进制表示形式为0 000 0000,而-0的二进制表示形式为1 111 1111+1=1 0000 0000,因为计算机会进行截断,只取低8位,所以-0的补码表示形式为0000 0000。
- 补码的表示范围比原码和反码表示的范围都要大。用补码能够表示的范围为-128~127,0~127分别用00000000~01111111来表示,而-127~-1则用10000001~11111111来表示,多出的10000000则用来表示-128。
- 对于有符号整数的运算能够把符号位同数值位为一起处理
如果把符号位单独考虑的话,CPU指令还要特意对最高位进行判断,使计算机的最底层实现变得复杂。
(3)整数溢出漏洞
参考资料:整数溢出与程序安全
- 一个整数是一个固定的长度,它能存储的最大值是固定的,当尝试去存储一个大于这个固定的最大值时,将会导致一个整数溢出。
- 整数溢出将会导致"不能确定的行为"。大多数编译器会忽略整数溢出,致使整数溢出没有立即察觉,因此没有办法去用一个应用程序来判断先前计算的结果在实际上是否也是正确的,可导致某些类型的bugs,缓冲区溢出等。
有符号整数的另外两种标准表示方法
二进制反码形式:与二进制补码的表示方法类似,区别在于最高有效位的权值不同。
原码:最高有效位是符号位,确定剩下的位取负权值还是正权值。
- 这两种表示都有一个奇怪的属性,即对于数字0存在两种编码。对于两种方法,[00..0]都被解释成+0 ,而-0在二进制反码中表示为[11..1] ,在原码中表示为[10..0]。
- 虽然曾经有过基于二进制反码表示法的机器,但几乎所有现代机器都使用二进制补码。
- 符号数值编码方式使用在浮点数的表示中。
符号数与无符号数之间的转换
ANSI C规定在无符号整数和有符号整数之间进行强制类型转换时,并未改变对象的位模式,改变的是位模式的解释方式。处理同样字长的有符号数和无符号数之间相互转换的一般规则是:数值可能会改变,位模式不变。
T2U:补码到无符号数的转换
U2T:无符号数到补码的转换
- 有符号数转换为无符号数时,负数转换为大的正数(可以理解为原值加上2的n次方),而正数保持不变。
- x<0 T2Uw(x)=x+2^w
- x>0 T2Uw(x)=x+2^w
(*w表示数据类型的位数)
- 无符号数转换为有符号数时,对于小的数将保持原值,对于大的数则转换为负数(可以理解为原值减去2的n次方)
- u<2^(w-1) U2Tw(u)=u
- u>=2^(w-1) U2Tw(u)=u-2^w
(*w表示数据类型的位数)
C中有符号和无符号数
- 通常认为生命的常量是有符号的,要创建一个无符号常量,必须加上后缀字符'U'或者'u'。
- C语言允许无符号数和有符号数之间的转换,转换的原则是底层的位保持不变。
- 显式转换:强制类型转换。
隐式转换:一种类型的表达式被赋值给另外一种类型的变量时。
- C语言在处理同时包含无符号数和有符号数的表达式时,会隐式的将有符号数转换为无符号数,并假设这两个操作数都是非负的,然后执行运算。这一特性对于标准的算术运算来说无多大差异,但对于像"<"和">"这样的关系运算符,有时会导致和直觉不相符的结果。
例:当一个有符号数“-1”与无符号数“0”用关系运算符连接时,会自动的将-1隐式转换为无符号数4294967295(假设是一个使用补码的32位机器),负数变成了正数,“-1<0u”的结果值就是0。
3.1历史观点
8086—〉80286—〉i386—〉i486—〉Pentium—〉PentiumPro—〉Pentium—〉Pentium—〉Pentium4—〉Pentium4e—〉Core 2 Duo —〉Core i7
3.2程序编码
1.gcc -01 –o p p1.c p2.c 使用第一级优化
2.程序计数器(%eip)指示将要执行的下一条指令在存储器中的地址。
3.寄存器文件
4.-S:C语言编译器产生的汇编代码
例:gcc -01 –S code.c 会产生一个汇编文件code.c
3.3数据格式
char |
字节 |
b |
1 |
short |
字 |
w |
2 |
int |
双字 |
1 |
4 |
long int |
双字 |
1 |
4 |
long long int |
— |
— |
4 |
char* |
双字 |
1 |
4 |
float |
单精度 |
s |
4 |
Double |
双精度 |
l |
8 |
long double |
扩展精度 |
t |
10/12 |
3.4访问信息
1.操作数指示符类型:立即数、寄存器、寄存器
2.数据传送指令
指令 |
效果 |
描述 |
MOV S,D |
S<-D |
传送 |
movb movw movl |
传送字节 传送字 传送双字 |
|
MOVS S,D |
D<-符号扩展(S) |
传送符号扩展的字节 |
MOVZ S,D |
D<-零扩展(S) |
传送零扩展的字节 |
3.5算术和逻辑操作(20135315韩玉琪的博客)
1.加载有效地址:leal实际上是movl的变形,为存储器引用产生指针
2.一元操作和二院操作:1)++,- -;2)+=
- 一元操作
- - INC 加1
- - DEC 减1
- - NEG 取负
- NOT 取补
- 只有一个操作数,既是源又是目的,可以是一个寄存器,或者存储器位置。
- 二元操作
- - ADD 加
- - SUB 减
- - IMUL 乘
- - XOR 异或
- - OR 或
- AND 与
- 第一个操作数可以是立即数、寄存器或者存储器位置
- 第二个操作数既是源也是又是目的。可以是寄存器或者存储器位置,但是不能同时是存储器位置。
- 注意操作的顺序:
第二个操作数 操作符 第一个操作数
3.移位操作:>>,<<
- 先给出移位量,第二项给出要移位的数值。
- - SAL 左移
- - SHL 左移(等同于SAL)
- - SAR 算术右移
- SHR 逻辑右移
- 源操作数(移位量):立即数或者放在单字节寄存器元素%cl中。
- 目的操作数:一个寄存器或是一个存储器位置。
4.特殊算术操作
- 乘法
- 乘积截断
- imull 双操作数
- 从两个32位操作数产生一个32位的乘积。
- 乘积不截断
- mull 无符号数乘法
- imull 有符号数乘法
- - 要求一个参数必须在寄存器%eax中,另一个作为指令的源操作数给出。
- 乘积的高32位在%edx中,低32位在%eax中。
- 除法
- 有符号除法
- idivl 操作数
- - 将DX:AX中的64位数作为被除数,操作数中为除数
- 结果:商在AX中,余数在DX中。
- 无符号除法
- divl指令
- 通常会事先设定寄存器%edx为0.
3.6控制
1.条件码:
CF:进位标志 ZF:零标志 SF:符号标志 OF:溢出标志
2.访问条件码
- SET指令:执行比较指令,根据计算t=a-b的结果设置条件码
3.跳转指令及其编码:jmp *%eax
- 无条件跳转
- 直接跳转:跳转目标是作为指令的一部分编码的。
- 间接跳转:跳转目标是从寄存器或存储器位置中读出的。
4.条件传送指令(参考资料20135202闫佳歆博客)
- 将条件表达式和语句从c语言翻译成机器语言,最常用的方式就是结合有条件和无条件跳转。
- if-else 的汇编结构
- 通用形式模板
- if(test-expr)
- then-statement
- else
- else-statement
(注:test-expr 整数表达式[假/真])
- 汇编实现形式
- t = test-expr;
- if (!t)
- goto false;
- then-statement
- goto done;
- false:
- else-statement
done:
5.switch语句
3.7过程
1.栈帧结构:机器用栈帧来传递过程参数、存储返回信息、保存寄存器用于以后的回复以及本地存储。为单个过程分配的那部分栈称为栈帧
2.帧指针:%ebp,栈指针:%%esp
3.转移控制
call Label 过程调用
call *Operand 过程调用
leave 为返回准备栈
ret 从过程调用中返回
4.寄存器使用惯例
1).%eax、%edx、%ecx 调用者保存
2).%ebx、%esi、%edi 被调用者保存
5.递归过程:递归调用一个函数本身与调用其他函数是一样的。相互调用更为复杂
问题:
1.比较指令cmp和减法指令sub有何不同?
sub d,s 是d-s,结果送回d中,即送回目的操作数中。
cmp d,s 也是d-s,但结果不送回目的操作数中,是利用减法进行两个数值的比较。
Y86指令集体系结构
定义一个指令集体系结构,包括定义各种状态元素、指令集和它们的编码、一组编程规范和异常事件处理。
程序员可见状态
Y86处理器状态类似于I32。可以访问和修改程序寄存器、条件码、程序计数器和存储器,状态码指明程序是否运行正常。
RF:程序寄存器 %eax,%ecx,%edx,%ebx,%esi,%edi,%esp(出栈、入栈、调用和返回指令作为栈指针),%ebp
CC:条件码 ZF、SF、OF(都是一位条件码,用来保存最近的算术或逻辑指令所造成影响的有关信息。)
PC:程序计数器 存放当前正在执行的指令
DMEM:存储器 很大的字节数组,保存着程序和数据。Y86程序用虚拟地址来引用存储器位置。
Stat:程序状态码 它表明程序执行的总体状态。它会指示是正常运行还是出现了某种异常。
Y86指令
IA32指令集的一个子集,只包括四字节整数操作。寻址方式比较少,操作也比较少。
IA32的movl指令分成了4个不同的指令:irmovl、rrmovl、mrmovl和rmmovl。分别显示地指明源和目的的格式。
源操作数: 立即数i、寄存器r、存储器m
目的操作数: 寄存器r、存储器m
两个存储器传送指令中的存储器引用方式是简单的基址和偏移量形式。
在地址计算中,不支持第二变址寄存器和任何寄存器值的伸缩。
不允许从一个存储器地址直接传送到另一个存储器地址。也不允许将立即数传送到存储器。
4个整数操作指令
addl、subl、andl、xorl
7个跳转指令(jXX)
jmp、jle、jl、je、jne、jge、jg
有6个条件传送指令(cmovXX)
cmovle、cmovl、cmove、cmovne、cmovge、cmovg
只有当条件码满足所需要的约束时,才会更新目的寄存器的值。
call指令将返回地址入栈,然后跳到目的地址。ret指令从这样的过程调用中返回。
pushl和popl指令实现了入栈和出栈。
halt指令停止指令的执行。对于Y86来说,执行halt指令会导致处理器停止,并将状态码设置为HLT。
指令编码
每条指令的第一个字节表明指令的类型。这个字节分为两个部分,每部分4位:高4位是代码部分,低4位是功能部分。功能值只有在一组相关指令共用一个代码时才有用。
整数操作里代码部分均为6,功能部分区分addl,subl,andl,xorl
分支指令里代码部分均为7
传送指令里代码部分均为2
8个程序寄存器中每个都有相应的0~7的寄存器标识符。
程序寄存器存在一个寄存器文件中,这个寄存器文件就是一个小的、以寄存器ID作为地址的随机访问存储器。当需要指明不应访问任何寄存器时,用ID值0xF表示。
没有寄存器操作数(分支指令和call指令),就没有寄存器指示符字节。
只需要一个寄存器操作数的指令(irmovl、pushl、popl),将另一个寄存器指示符设为0xF。
一个附加的4字节常数字,可作为:irmovl的立即数数据,rmmovl和mrmovl的地址指示符偏移量,分支指令和调用指令的目的地址。
注意:
分支指令和调用指令的目的地址是一个相对地址,而不是相对寻址方式。
所有整数采用小端法编码。当指令按反汇编格式书写时这些字节就以相反的顺序出现。
Y86异常
Y86状态码
出现异常时Y86处理器停止运行指令。可以调用一个异常处理程序使其更完整。
Y86程序
Y86代码与IA32代码的主要区别:
Y86可能需要多条指令来执行一条IA32指令所完成的功能。
Y86没有伸缩寻址模式。
命令指明应该将代码或数据放在什么位置,以及如何对齐。这个程序详细说明了栈的放置、数据初始化、程序初始化和程序结束等问题。
以“.”开头的词是汇编命令,他们告诉汇编器调整地址,以便在那儿产生代码或插入一些数据。
创建Y86代码的唯一工具是汇编器。
指令集模拟器YIS
二. 逻辑设计和硬件控制语言HCL
要实现一个数字系统需要三个主要的组成部分:
计算对位进行操作的函数的组合逻辑
存储位的存储器元素
控制存储器元素更新的时钟信号
逻辑门
AND &&
OR ||
NOT !
逻辑门只对单个位的数进行操作,而不是整个字。
组合电路和HCL布尔表达式
储器来存储程序数据。
处理器还包括另外一个只读存储器,用来读指令。
在大多数实际系统中,这两个存储器被合并为一个具有双端口的存储器:一个用来读指令,一个用来读或写数据。
Y86的顺序实现
SEQ 顺序处理器
每个时钟周期上,SEQ执行一条完整指令所需的所有步骤
六个基本阶段
- 取指 - 译码 - 执行 - 访存 - 写回 - 更新PC
SEQ
-SEQ抽象视图
程序计数器放在寄存器中
信息沿线流动
各个阶段相关的硬件单元负责执行这些处理
反馈线路包括要写到存储器文件的更新值,以及更新的程序计数器值
在SEQ中,所有硬件单元的处理都在一个时钟周期内完成
画图惯例
浅灰色方块表示硬件单元
控制逻辑块是用灰色圆角矩形表示的
线路的名字在白色椭圆中说明
宽度为字长的数据连接用中等粗度的线表示
宽度为字节或更窄的数据连接用细线表示
单个位的连接用虚线
SEQ的时序
SEQ的实现包括组合逻辑和两种存储器设备
时钟寄存器 程序计数器和条件码寄存器
随机访问存储器 寄存器文件、指令存储器和数据存储器
组合逻辑不需要任何时序或控制
由于指令存储器只用来读指令,我们可以将这个单元看成是组合逻辑
剩下四个(程序计数器、条件码寄存器、数据存储器和寄存器文件)需要对他们的时序进行明确的控制。
条件码寄存器 只在执行整数运算指令时装载
数据存储器 只在执行rmmovl、pushl或call时写入
寄存器文件 两个写端口允许每个时钟周期更新两个程序寄存器。(特殊寄存器ID 0xF表明此端口不应执行写操作)
组织计算原则
处理器从来不需要为了完成一条指令的执行而去读由该指令更新了的状态。
用时钟来控制状态元素的更新,值通过组合逻辑传播。
取指阶段
以PC为第一个字节的地址,一次读6个字节。
icode 控制逻辑块计算指令
ifun 功能码
三个一位的信号(根据icode值计算)
instr_valid 发现不合法的指令
need_regids 包含寄存器指示符字节吗
need_valC 包括常数字吗
后五个字节是寄存器指示符字节和常数字的组合编码。
译码和写回阶段
都需要访问寄存器文件,根据四个端口的情况,判断应该读哪个寄存器产生信号valA、valB。
寄存器文件,支持同时进行两个读和两个写,每个端口有一个地址连接(寄存器ID)和一个数据连接(32根线路),既可以作为寄存器文件的输出字,又可以作为他的输入字。
执行阶段
包括算数/逻辑单元(ALU),输出为valE信号。
ALU通常作为加法器使用
包括条件码寄存器
每次运行产生:
符号、溢出、产生信号set_cc
访存阶段
读或者写程序数据。
两个数据块产生存储器地址和存储器输入证据的值,两个产生控制信号表明应该是读还是写。当执行读操作时,数据存储器产生valM。
根据icode,imem_error,instr_valid,dmem_error,从指令执行的结果计算状态码Stat。
更新PC阶段
产生程序计数器的新值,依据指令的类型和是否要选择分支,新的PC可能是valC、valM或者valP。
存储器系统是一个线性的字节数组。是一个具有不同容量、成本和访问时间的存储设备的层次结构。
分类:1.靠近CPU的高速缓存存储器; 2.相对慢速的主存储器
存储器层次结构:指令制定期间——0周期;
高速缓存中——1-30个周期;
主存中——50-200个周期;
磁盘上——几千万个周期。
基本属性——局部性:多次访问相同数据项集合。
存储技术:1.SRAM存储器
2.DRAM存储器
3.ROM存储器
4.旋转的个固态的硬盘
6.1 存储技术
- 随机访问存储器(RAM)
分类:静态(SRAM)——双稳态的(摆钟的左右两边状态)
动态(DRAM)——对干扰敏感
传统DRAM——分成d个超单元
存储器模块:1.双列(DIMM) 2.单列(SIMM)
增强的DRAM:快页模式
扩展数据输出
双倍数据速率通过不
非易失性存储器
访问主存——总线事务、读事务、写事务
- 磁盘存储
盘片:表面*2+磁道+扇区(512bety)+间隙+主轴
旋转速率(5400-15000r/min)
容量=字节数*平均扇区数*磁道数*表面数*盘片数
扇区的访问时间=寻道时间+旋转时间+传送时间
- 逻辑磁盘块三元组:盘面、磁道、扇区——唯一表示物理扇区
- 固态硬盘
6.2 局部性
- 对程序数据的引用:步长为k的引用模式、行优先顺序
- 取指令
6.3 存储器层次结构
- 缓存:高速缓存——高速缓存cash是一个小而快速的存储设备,它作为存储在更大、更慢的设备中的数据对象的缓冲区域。使用高速缓存的过程称为缓存。
- 缓存命中:当前存储在第k层的一个块中查找,刚好缓存在第k层中
缓存不命中种类:1).强制性不命中(冷缓存)——放置策略
2).冲突不命中
- 缓存管理
6.4 高速缓存存储器
L1高速缓存:位于CPU寄存器文件和主存之间,访问速度2-4个时钟周期
L2高速缓存:位于L1高速缓存和主存之间,访问速度10个时钟周期
L3高速缓存:位于L2高速缓存和主存之间,访问速度30或40个时钟周期
1. 通用的高速缓存存储器结构
m:每个存储器地址有m位,形成M=2^m个不同的地址
S:这个数组中有S=2^s个高速缓存组
E:每个组包含E个高速缓存行
B:每个行是由一个B=2^b字节的数据块组成的