运用结构体优化Cortex-M0/M0+程序

将全局变量组合成结构体,结构体成员变量的数目不超过32个,并按照大小排放,如此可以利用Cortex-M0/M0+的指令集生成最优的代码。

mingdu.zheng at gmail dot com
http://blog.csdn.net/zoomdy/article/details/79304324

将全局变量组合成结构体

来看一段很简单的实例代码。

// 前半部分使用3个分散的全局变量,将他们初始化成0
int a, b, c;
void init1(void)
{
    a = 0;
    b = 0;
    c = 0;
}
// 后半部分使用结构体将他们组合,同样将他们初始化成0
struct {
    int a;
    int b;
    int c;
}t;

void init2(void)
{
    t.a = 0;
    t.b = 0;
    t.c = 0;
}

转化成汇编

arm-none-eabi-gcc -S -O2 -Wall init.c 

汇编输出如下

// 前半部分的汇编输出
	.global	init1
	.type	init1, %function
init1:
	ldr	r0, .L2    // 加载a变量的地址
	ldr	r1, .L2+4  // 加载b变量的地址
	ldr	r2, .L2+8  // 加载c变量的地址
	mov	r3, #0
	str	r3, [r0]   // 设置a变量为0
	str	r3, [r1]   // 设置b变量为0
	str	r3, [r2]   // 设置c变量为0
	bx	lr
.L3:
	.align	2
.L2:
	.word	a          // 常量池存储a变量的地址
	.word	b          // 常量池存储b变量的地址
	.word	c          // 常量池存储c变量的地址
// 后半部分的汇编输出
	.global	init2
	.type	init2, %function
init2:
	ldr	r3, .L5       // 加载结构体首地址
	mov	r2, #0
	str	r2, [r3]      // 设置a变量为0
	str	r2, [r3, #4]  // 设置b变量为0
	str	r2, [r3, #8]  // 设置c变量为0
	bx	lr
.L6:
	.align	2
.L5:
	.word	t             // 常量池存储结构体首地址

两部分的汇编输出对比一下,就很清楚使用结构体的好处了。

使用分散变量,会占用更多的常量池空间,每个全局变量的地址都要存储在常量池,而使用结构体只需要存储结构体首地址,这是因为全局变量存储在什么位置是由链接器决定的,虽然代码中这几个变量是挨着定义的,但是编译器不能假设他们在内存中也是挨着,而结构体作为一个整体,其成员变量一定是挨着的,所以只需要结构体首地址加偏移就可以了。

	.word	a          // 常量池存储a变量的地址
	.word	b          // 常量池存储b变量的地址
	.word	c          // 常量池存储c变量的地址

vs

	.word	t          // 常量池存储结构体首地址

使用分散变量,会使用更多的指令,每个全局变量都需要单独加载其存储地址,上面的例子中使用了3个全局变量,就有3条LDR指令分别加载他们的地址,而使用了结构体只需要1条LDR指令加载结构体首地址。使用更多的指令意味着需要更多的代码空间以及执行时间。

	ldr	r0, .L2    // 加载a变量的地址
	ldr	r1, .L2+4  // 加载b变量的地址
	ldr	r2, .L2+8  // 加载c变量的地址

vs

	ldr	r3, .L5    // 加载结构体首地址

总结一下,使用结构体组织分散的全局变量既可以节省存储空间,又可以获得更快的执行速度。

结构体的大小和排放次序

Cortex-M0/M0+只有16位的LDR/STR指令,其指令形式如下:

LDR  <Rt>,[<Rn>, <Rm>]    // Rt = memory[Rn + Rm]
STR  <Rt>,[<Rn>, <Rm>]    // memory[Rn + Rm] = Rt
LDRH <Rt>,[<Rn>, <Rm>]    // Rt = memory[Rn + Rm]
STRH <Rt>,[<Rn>, <Rm>]    // memory[Rn + Rm] = Rt
LDRB <Rt>,[<Rn>, <Rm>]    // Rt = memory[Rn + Rm]
STRB <Rt>,[<Rn>, <Rm>]    // memory[Rn + Rm] = Rt

LDR  <Rt>,[<Rn>, #immed5] // Rt = memory[Rn + ZeroExtend (#immed5<<2)]
STR  <Rt>,[<Rn>, #immed5] // memory[Rn + ZeroExtend(#immed5<<2)] = Rt
LDRH <Rt>,[<Rn>, #immed5] // Rt = memory[Rn + ZeroExtend (#immed5<<1)]
STRH <Rt>,[<Rn>, #immed5] // memory[Rn + ZeroExtend(#immed5<<1)] = Rt
LDRB <Rt>,[<Rn>, #immed5] // Rt = memory[Rn + ZeroExtend (#immed5)]
STRB <Rt>,[<Rn>, #immed5] // memory[Rn + ZeroExtend(#immed5)] = Rt

为了获得最好的效果,应该尽量生成 LDR <Rt>,[<Rn>, #immed5] 类型的指令,这种类型的指令直接将偏移地址编码进了指令,不需要额外的指令去处理偏移,倘若编译出 LDR <Rt>,[<Rn>, <Rm>] 类型的指令,还需要指令来修改Rm的值来修改偏移。

LDR <Rt>,[<Rn>, #immed5] 的指令只有5位的立即数作为偏移值,LDR/STR最多可以偏移31个字(0124字节偏移范围),LDRH/STRH最多可以偏移31个半字(062字节偏移范围),LDRB/STRB最多可以偏移31个字节(0~31字节偏移范围),由此可以得出应该将8位长的变量放在结构体的最前面,16位长的变量次之,32位长的变量放在最后。倘若结构的大小超过32字节,又将8位长的变量放后面,那么生成的就是 LDRB <Rt>,[<Rn>, <Rm>] 指令了,那结构体的优势就不明显了。

结构体的大小应尽量控制在只产生 LDR <Rt>,[<Rn>, #immed5] 类型的指令,不会产生 LDR <Rt>,[<Rn>, <Rm>] 为宜。纯32位成员变量不宜超过128字节,纯16位变量不宜超过64字节,纯8位变量不宜超过31字节。基本上 结构体成员变量的数目不超过32个,并按照大小排放 ,可以做到最好的效果。

临时变量需要使用结构体吗?

不需要,临时变量由编译器决定存储在寄存器或堆栈内。首先编译器会尽量让临时变量存储在寄存器,寄存器不够用的情况下存储在堆栈中,在堆栈中的偏移是编译器控制的,所以编译知道其偏移地址,会自动产生相应的最优指令。Cortex-M0/M0+用于访问堆栈内临时变量的指令如下:

LDR  <Rt>,[SP, #immed8]   // Rt = memory[SP + ZeroExtend(#immed8<<2)]
STR  <Rt>,[SP, #immed8]   // memory[SP + ZeroExtend(#immed8<<2)] = Rt

只有字访问指令,没有半字或字节访问指令,Cortex-M的压栈是以字为单位的,即使程序定义的是半字或字节,压入堆栈的仍然是字。

这两条指令的立即数有8位,可以最多访问256个字。比常规的存储指令访问范围大得多。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值