资讯详情

CSPP学习笔记-Ch3.4 访问、传送、转换数据

目录

  • 3.4 访问信息
    • 3.4.1 两大数据类型
      • 1、整型
      • 2、浮点型
    • 3.4.2 操作数指示符
      • 操作数(operand)
        • 1、立即数(immediate)
        • 2、寄存器(register)
        • 3、内存引用
    • 3.4.3 数据传送 MOV 和类型转换
      • 1、MOV 类
      • 2、MOVZ 类 和MOVS 类
      • 3、浮点传送 VMOV
      • 4.浮点型和整形转换
    • 3.4.4 数据传输示例
    • 3.4.5 压入(push)和弹出(pop)栈数据

3.4 访问信息

3.4.1 两大数据类型

1、整型

一个 x86-64 的 CPU 一组包含 存储 64 位值的,用来

image-20211010150910681

指令是对的 16 不同大小的数据存储在寄存器的低字节中进行操作。

  • 字节级操作可访问最低字节;
  • 16位操作可访问最低值 2 个字节;
  • 32位操作可访问最低值 4 个字节;
  • 64个操作可以访问整个操作 8 大字节寄存器。

那么会发生什么?对此有

,从上图也可以看出,上图中有6个参数可以直接支持函数,超过6个需要堆栈传输函数,影响调用函数的速度。

2、浮点型

x86-64 浮点数是基于的 SSE 或 AVX 包括传输过程参数和返回值的规则。 SSE 中的寄存器称为“XMM”; AVX 中间的寄存器称为YMM”。

AVX 浮点系统结构允许数据存储 16 个 YMM 在寄存器中,它们的名字是 %ymm0 ~ %ymm15 ,每个 YMM 寄存器都是 256 位(32 字节)的。

,而且只用低 32 位(对于 float)或低 64 位(对于 double)。一般用于汇编代码 SSE XMM 寄存器名字 %xmm0 ~ %xmm15 引用它们,每一个 XMM 寄存器对应 YMM 寄存器的低 128 位(16 如图,字节)。

算术类型(Arithmetic Type):算术运算的类型。包括整形和浮点。算术类型可以表示为 0 和非 作为控制表达式。

标量类型(Scalar Type):可参与逻辑操作(和或非),或控制表达式类型。包括算术类型和指针类型。

3.4.2 操作数指示符

操作数(operand)

指示执行操作中应使用的指示,以及放置结果

  • 源数据值:常数直接给出,或从寄存器读出,或从内存读出;
  • 目的地:寄存器,或内存。

1、立即数(immediate)

表示常数值。

【格式】‘$$$’ 后面是一个标准 C 表示法的整数,如 $$-577,{\space}$0x2F$

汇编器会自动选择最紧凑的方式来编码不同指令允许的即时数量范围。

2、寄存器(register)

表示寄存器的内容,16个寄存器的低1字节、2字节、4字节或8字节中的一个作为操作数,对应8、16、32、64。

符号表示 r a r_a ra 表示寄存器 a a a ,用引用 R [ r a ] R[r_a] R[ra​]​ 表示其值。(将寄存器集合看成一个数组 R ,用寄存器标识符作为索引)

3、内存引用

根据地址访问某个内存位置。内存被视为一个很大的字节数组(虚拟内存)。

【符号表示】 M b [ A d d r ] M_b[Addr] Mb​[Addr]​ ​表示对存储在内存中从地址 A d d r Addr Addr 开始的 b b b 个字节值的引用( b b b 通常省略)。

类型 格式 操作数值 寻址模式
立即数 $$Imm$ I m m Imm Imm 立即数寻址, I m m Imm Imm 表示的是常数值
寄存器 r a r_a ra​ R [ r a ] R[r_a ] R[ra​] 寄存器寻址
存储器 I m m Imm Imm M [ I m m ] M[Imm] M[Imm]​ 绝对寻址, I m m Imm Imm 表示的是地址值
存储器 ( r a ) (r_a) (ra​) M [ R [ r a ] ] M[R[r_a]] M[R[ra​]] 间接寻址,加圆括号表示这是地址
存储器 I m m ( r b ) Imm(r_b) Imm(rb​) M [ I m m + R [ r b ] ] M[Imm+R[r_b]] M[Imm+R[rb​]] (基址+偏移量)寻址
存储器 ( r b , r i ) (r_b,r_i) (rb​,ri​)​ M [ R [ r b ] + R [ r i ] ] M[R[r_b]+R[r_i]] M[R[rb​]+R[ri​]] 变址寻址
存储器 I m m ( r b , r i ) Imm(r_b,r_i) Imm(rb​,ri​)​ M [ I m m + R [ r b ] + R [ r i ] ] M[Imm+R[r_b]+R[r_i]] M[Imm+R[rb​]+R[ri​]] 变址寻址
存储器 ( , r i , s ) (,r_i,s) (,ri​,s)​​ M [ R [ r i ] ⋅ s ] M[R[r_i]·s] M[R[ri​]⋅s]​ 比例变址寻址
存储器 I m m ( , r i , s ) Imm(,r_i,s) Imm(,ri​,s)​​ M [ I m m + R [ r i ] ⋅ s ] M[Imm+R[r_i]·s] M[Imm+R[ri​]⋅s]​ 比例变址寻址
存储器 ( r b , r i , s ) (r_b,r_i,s) (rb​,ri​,s)​ M [ R [ r b ] + R [ r i ] ⋅ s ] M[R[r_b]+R[r_i]·s] M[R[rb​]+R[ri​]⋅s]​ 比例变址寻址
存储器 I m m ( r b , r a , s ) Imm(r_b,r_a,s) Imm(rb​,ra​,s) M [ I m m + R [ r b ] + R [ r i ] ⋅ s ] M[Imm+R[r_b]+R[r_i]·s] M[Imm+R[rb​]+R[ri​]⋅s] 比例变址寻址

【注意】比例因子 s s s 必须是1、2、4或者8.

3.4.3 数据传送 MOV 和类型转换

把数据从源位置到目的位置,只会更新指定寄存器中的字长度或内存位置,其他不做任何变化(除了 4 字节时,会将寄存器高位 4 字节设置为 0,这是个例外。)。

  • 传送指令的,即从一个内存位置复制到另一个内存位置,需要两条指令:内存1到寄存器,寄存器再到内存2。
  • x86-64 的的。
  • MOV 后缀取决于寄存器的字长。(?取决于哪个寄存器?)
  • ,不可以作为目的操作数
  • MOV 指令的寄存器操作数可以是 16 个寄存器有标号部分中的任意一个,寄存器部分的大小必须与指令最后一个字符(‘b’,‘w’,‘l’ 或 ‘q’)指定的大小匹配。
  • 源操作数和目的操作数都是寄存器时,其宽度必须相同,否则必须通过内存,两次使用 MOV。

1、MOV 类

指令 效果 描述
MOV S, D D ← S D←S D←S 传送
movb 传送字节
movw 传送字
movl 传送 2 字,若目的位置是寄存器,则高位 2 字设置为 0
movq 传送 4 字
movabsq I, R R ← I R←I R←I 传送绝对的 4 字
# MOV指令五种可能的组合👇
movl  $0x4050, %eax			# 立即数->寄存器,2字
movw  %bp, %sp				# 寄存器->寄存器,1字
movb  (%rdi, %rcx) %al		# 内 存->寄存器,半字
movb  $-17, (%rsp)			# 立即数->内 存,半字
movq  %rax, -12(%rbp)		# 寄存器->内 存,4字

【注意】 常规的 movq 指令只能以 32 位补码数字的立即数作为源操作数,然后通过符号扩展得到 64 位的值,放到目的位置。 movabsq 指令能以任意 64 位的立即数值作为源操作数,并且只能以寄存器作为目的操作数。

2、MOVZ 类 和MOVS 类

【应用】 将较小的源值复制到较大的目的时使用。

MOVZ,零扩展:把目的中剩余的所有字节填充为 0。 MOVS,符号扩展:把目的中剩余的所有字节用源操作数的最高位进行填充。

  • MOVS/Z 指令以寄存器或内存地址作为源操作数,不接受以立即数作为源操作数;
  • MOVS/Z 指令只以寄存器作为目的操作数。
指令 描述 指令 描述
MOVZ S, R R ← R← R←零扩展 ( S ) (S) (S) MOVS S, R R ← R← R←符号扩展 ( S ) (S) (S)
movzbw 将做了零扩展的字节传送到 1 字 movsbw 将做了符号扩展的字节传送到 1 字
movzbl 将做了零扩展的字节传送到 2 字 movsbl 将做了符号扩展的字节传送到 2 字
movzwl 将做了零扩展的 1 字传送到 2 字 movswl 将做了符号扩展的 1 字传送到 2 字
movzbq 将做了零扩展的字节传送到 4 字 movsbq 将做了符号扩展的字节传送到 4 字
movzwq 将做了零扩展的 1 字传送到 4 字 movswq 将做了符号扩展的 1 字传送到 4 字
没有movzlq movl实现了对高4位零扩展 movslq 将做了符号扩展的 2 字传送到 4 字
cltq %rax←​​ 符号扩展 %eax​​​

【注意】cltq 指令没有操作数,总是以寄存器 %rax 作为源操作数,以 %eax 作为符号扩展结果的目的操作数。

3、浮点传送 VMOV

【应用】 从内存传送到一个 XMM 寄存器(可改变类型,即 float → double 或 double → float); 从一个 XMM 寄存器传送到内存(可改变类型,即 float → double 或 double → float); 从一个 XMM 寄存器传送到另一个 XMM 寄存器(不改变类型)。

指令 源操作数(S) 目的操作数(D) 描述(S→D)
vmovss M 32 M_{32} M32​​ X 传送单精度数
vmovss X M 32 M_{32} M32​ 传送单精度数
vmovsd M 64 M_{64} M64​ X 传送双精度数
vmovsd X M 64 M_{64} M64​ 传送双精度数
vmovaps X X 传送对齐的封装好的单精度数
vmovapd X X 传送对齐的封装好的双精度数

【注意】 涉及到引用内存的指令,均是标量指令——其只对相应的低字节长度进行操作; 在两个 XMM 寄存器之间传送数据,GCC 会使用 vmovaps 传送单精度数,使用 vmovapd 传送双精度数; 程序复制整个寄存器,亦或是只复制低位值,并不会影响程序功能和执行速度。 vmovapsvmovapd 中的字母 a 表示 aligned,表示对齐的:在两个寄存器之间传送数据,绝对不会出现错误对齐的状况。

🌰不同浮点数传送操作

float float_mov(float v1, float *src, float *dst) { 
        
    float v2 = *src;
    *dst = v1;
    return v2;
}
# float float_mov(float v1, float *src, float *dst)
# v1 in %xmm0, src in %rdi, dst in %rsi
float_mov:
	vmovaps %xmm0, %xmm1 	# Copy v1
	vmovss (%rdi), %xmm0 	# Read v2 from src
	vmovss %xmm1, (%rsi) 	# Write v1 to dst
	ret 					# Return v2 in %xmm0

4、浮点型与整型转换

都是标量指令。 R:表示通用寄存器;X:表示 XMM 寄存器;M:表示内存。

指令 源(S) 目的(D) 描述(S→D)
vcvttss2si X X X 或 M 32 M_{32} M32​​​ R 32 R_{32} R32​ 用截断的方法把精度转换成
vcvttsd2si X X X 或 M 64 M_{64} M64​​ R 32 R_{32} R32​ 用截断的方法把精度转换成
vcvttss2siq X X X 或 M 32 M_{32} M32​​ R 64 R_{64} R64​ 用截断的方法把精度转换成
vcvttsd2siq X X X 或 M 64 M_{64} M64​​ R 64 R_{64} R64​ 用截断的方法把精度转换成

指令 源(S1) 源(S2) 目的(D) 描述(S1→D)
vcvtsi2ss M 32 M_{32} M32​ 或 R 32 R_{32} R32​ X X X X X X 把整数转换成单精度数
vcvtsi2sd M 32 M_{32} M32​ 或 R 32 R_{32} R32​ X X X X X X 把整数转换成双精度数
vcvtsi2ssq M 64 M_{64} M64​ 或 R 64 R_{64} R64​ X X X X X X 把四字整数转换成单精度数
vcvtsi2sdq M 64 M_{64} M64​ 或 R 64 R_{64} R64​ X X X X X X 把四字整数转换成双精度数

第一个源操作数 S1 读取自内存或一个通用寄存器; 目标操作数 D 必须是 XMM 寄存器; 因为这个转换指令是标量指令,第二个源操作数 S2 可忽略,其只会影响结果的高位字节(128位以上的位),因此在常见的使用场景中,第二个源操作数 S2 和目的操作数都是一样的:就像:vcvtsi2sdq %rax, %xmm1, %xmm1

vmovddup %xmm0, %xmm0 			# Replicate first vector element
vcvtpd2psx %xmm0, %xmm0 		# Convert two vector elements to single

假设指令开始执行前:寄存器 %xmm0 的值为 [ x 1 , x 0 ] 8 B [x_1,x_0]_{8B} [x1​,x0​]8B​; vmovddup 指令把 %xmm0 设置为 [ x 0 , x 0 ] 8 B [x_0,x_0]_{8B} [x0​,x0​]8B​; vcvtpd2psx 指令把这两个值转换成单精度,再存放到该寄存器的低位一半中,并将高位一半设置为 0,得到结果 [ 0.0 , 0.0 , x 0 , x 0 ] 4 B [0.0,0.0,x_0,x_0]_{4B} [0.0,0.0,x0​,x0​]4B​ (0.0 是由位模式全 0 表示的)。

vunpcklps %xmm0, %xmm0, %xmm0 	# Replicate firstvector element
vcvtps2pd %xmm0, %xmm0 			# Convert two vector elements to double

vunpcklps 指令通常用来交叉放置来自前两个 XMM 寄存器的值,把它们存储到第三个寄存器中: 如果第一个寄存器为 [ s 3 , s 2 , s 1 , s 0 ] 4 B [s_3,s_2,s_1,s_0]_{4B} [s3​,s2​,s1​,s0​]4B​,第二个寄存器为 [ d 3 , d 2 , d 1 , d 0 ] 4 B [d_3,d_2,d_1,d_0]_{4B} [d3​,d2​,d1​,d0​]4B​,那么第三个寄存器(目的寄存器)的值会是 [ s 1 , d 1 , s 0 , d 0 ] 4 B [s_1,d_1,s_0,d_0]_{4B} [s1​,d1​,s0​,d0​]4B​;上面的代码中,使用三个操作数使用同一个操作数,如果起初寄存器为 [ x 3 , x 2 , x 1 , x 0 ] 4 B [x_3,x_2,x_1,x_0]_{4B} [x3​,x2​,x1​,x0​]4B​,那么指令该指令后,寄存器的值变为 [ x 1 , x 1 , x 0 , x 0 ] 4 B [x_1,x_1,x_0,x_0]_{4B} [x1​,x1​,x0​,x0​]4B​。

vcvtps2pd 指令把源操作数寄存器中的扩展成目的寄存器中的两个双精度值: 对于寄存器的值 [ x 1 , x 1 , x 0 , x 0 ] 4 B [x_1,x_1,x_0,x_0]_{4B} [x1​,x1​,x0​,x0​]4B​,执行完该指令后,得到的结果为 [ d x 1 , d x 0 ] 8 B [dx_1,dx_0]_{8B} [dx1​,dx0​]8B​,这里 d x 0 dx_0 dx0​ 是将 x x x 转换成双精度后的结果。

👆两条指令的最终结果:将原始的 %xmm0 低位 4 字节中的单精度值转换成双精度值,再将其两个副本保存到 %xmm0 中。​​

3.4.4 数据传送示例

long exchange(long *xp, long y){ 
        
    long x = *xp;
    *xp = y;
    return x;
}
exchange:					# xp in %rdi, y in %rsi
	movq	(%rdi), %rax	# long x = *xp;
	movq	%rsi, (%rdi)	# *xp = y;
	ret						# return x;
  • 参数通过寄存器传递给函数。
  • 函数通过把值存储在寄存器 %rax 或该寄存器的某个低位部分中返回。

【过程】这个例子说明了如何用 MOV 指令从内存中读值到寄存器(第2行),如何从寄存器写到内存(第3行)。

  • 过程参数 xpy 分别存储在寄存器 %rdi%rsi 中。
  • 然后,第 2 行指令从内存中读出 x ,把它存放到寄存器 %rax 中,直接实现了 C 程序中的操作x=*xp
  • 稍后,用寄存器 %rax 从这个函数返回一个值,因而返回值就是 x
  • 第 3 行指令将 y 写入到寄存器 %rdi 中的 xp 指向的内存位置,直接实现了操作 *xp=y

【练习】

假设变量 spdp 被声明为类型 src_tdest_t 。使用适当的数据传送指令实现一条强制转换语句:*dp=(dest_t)*sp。假设 spdp 的值分别存储在寄存器 %rdi%rsi 中。

分析:spdp 的值都是地址。

  • 扩展位时,
  • 截取时,

3.4.5 压入(push)和弹出(pop)栈数据

栈:由高地址向低地址方向增长。

指令 效果 描述 等价于
pushq S R[%rsp]←R[%rsp]-8;M[R[%rsp]]←S 将四字压入栈 subq $8, %rsp;movq %rbp, (%rsp)
popq D D←M[R[%rsp]];R[%rsp]←R[%rsp]+8 将四字弹出栈 movq (%rsp), %rax;addq $8, %rsp

movpushq 指令的区别在于:pushq 指令编码为 1 个字节,等价的两条指令一共需要 8 个字节。

  • 将一个四字值压入栈中,首先要将栈指针减 8,然后将值写到新的栈顶地址。
  • 弹出一个四字的操作包括从栈顶位置读出数据,然后将栈指针加 8。

标签: 2sdq固态继电器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台