首页 > 其他分享 >学习Cortex-M:结构体 vs 分散变量

学习Cortex-M:结构体 vs 分散变量

时间:2022-11-20 19:31:23浏览次数:35  
标签:r4 变量 int 指令 vs Cortex str test


学习Cortex-M:结构体 vs 分散变量

静态变量使用结构体表示的代码无论在space上还是speed上都要优于分散变量,应当尽量使用结构体。对于局部变量采用结构体还是分散变量并没有什么区别。

​​ mingdu.zheng at gmail dot com

结构体

创建一段测试代码,定义一个结构体test_t,包含4个成员变量,函数func给结构体的4个成员变量赋值。

// struct1.c
struct test_t
{
int b;
int a;
int c;
int d;
};

struct test_t test;

void func(int a, int b, int c, int d)
{
test.a = a;
test.b = b;
test.c = c;
test.d = d;
}

编译,然后反汇编。

arm-none-eabi-gcc -o struct1.o struct1.c -c -O2 -Wall -mthumb -march=armv7-m
arm-none-eabi-objdump -d struct1.o

观察编译器生成的代码,除去函数调用的开销,函数体的实现用了5条指令,从第5行到第9行。第1条ldr指令加载结构体的地址,接下来的4条str指令进行结构体成员的赋值。str指令的地址偏移常量也就是各个成员变量在结构体中的偏移。

struct1.o:     file format elf32-littlearm
Disassembly of section .text:
00000000 <func>:
0: b410 push {r4}
2: 4c04 ldr r4, [pc, #16] ;加载结构体地址
4: 6060 str r0, [r4, #4] ;赋值a
6: 6021 str r1, [r4, #0] ;赋值b
8: 60a2 str r2, [r4, #8] ;赋值c
a: 60e3 str r3, [r4, #12] ;赋值d
c: f85d 4b04 ldr.w r4, [sp], #4
10: 4770 bx lr
12: bf00 nop
14: 00000000 .word 0x00000000

如果仔细观察,可以发现上面那段代码的赋值次序与成员变量在结构体中的排放次序是不一样的,现在来调整一下结构体成员的次序,让赋值次序与成员变量的排放次序相同。

// struct2.c
struct test_t
{
int a;
int b;
int c;
int d;
};

struct test_t test;

void func(int a, int b, int c, int d)
{
test.a = a;
test.b = b;
test.c = c;
test.d = d;
}

重新编译和反汇编。

arm-none-eabi-gcc -o struct2.o struct2.c -c -O2 -Wall -mthumb -march=armv7-m
arm-none-eabi-objdump -d struct2.o

再来观察编译器的输出,这次生成的指令数更少了,只有2条指令,第5行和第6行。一条ldr指令加载结构体地址,这与上面的输出是一致的,另外一条是stmia指令,stmia指令厉害了,一条指令就把4个赋值全给搞定了。这就是赋值次序和成员变量次序一致的结果,又快又省。当然,现实中赋值次序和排列次序一致的情况还真不多见。所以还是struct1例子比较现实。

struct2.o:     file format elf32-littlearm
Disassembly of section .text:
00000000 <func>:
0: b410 push {r4}
2: 4c03 ldr r4, [pc, #12] ; (10 <func+0x10>)
4: e884 000f stmia.w r4, {r0, r1, r2, r3}
8: f85d 4b04 ldr.w r4, [sp], #4
c: 4770 bx lr
e: bf00 nop
10: 00000000 .word 0x00000000

分散变量

现在把结构体内的变量打散。

// discrete.c
int ga;
int gb;
int gc;
int gd;

void func(int a, int b, int c, int d)
{
ga = a;
gb = b;
gc = c;
gd = d;
}

用同样的编译选项进行编译。

arm-none-eabi-gcc -o discrete.o -c discrete.c -Wall -mthumb -march=armv7-m -O2
arm-none-eabi-objdump -d discrete.o

是8条指令!从第5行到第12行。前面4条ldr指令分别加载4个变量的地址,后面4条str指令执行赋值。比struct1例子多了3条ldr指令。为什么会多了3条ldr指令呢?这4个变量也是按次序摆放的,是不是应该像struct2例子一样用STMIA实现?这是因为变量的存储位置不是编译器说了算的,而是链接器说了算的,在编译的时候,编译器不知道变量会被安排在什么位置上,只能假设各个变量的位置是没有关系的,所以要分别取4个变量的地址,然后才能赋值。而结构体成员的排列次序和偏移则是编译器说了算的,所以给出结构体时,编译器可以确定结构体成员的相对位置,只要取结构体首地址再加上各个成员的偏移就可以确定所有成员的地址了。链接器安排结构体时,是将其作为一个整体处理的,不会打散它。

discrete.o:     file format elf32-littlearm
Disassembly of section .text:
00000000 <func>:
0: b4f0 push {r4, r5, r6, r7}
2: 4f05 ldr r7, [pc, #20] ; (18 <func+0x18>)
4: 4e05 ldr r6, [pc, #20] ; (1c <func+0x1c>)
6: 4d06 ldr r5, [pc, #24] ; (20 <func+0x20>)
8: 4c06 ldr r4, [pc, #24] ; (24 <func+0x24>)
a: 6038 str r0, [r7, #0]
c: 6031 str r1, [r6, #0]
e: 602a str r2, [r5, #0]
10: 6023 str r3, [r4, #0]
12: bcf0 pop {r4, r5, r6, r7}
14: 4770 bx lr

优先使用结构体

从上面的分析可以看出使用结构体的代码无论在space上还是speed上都要优于分散变量,那还有什么理由不用结构体呢。应该尽量地将相关的变量组合成结构体,而不是以分散变量的形式。使用结构体还可以采用面向对象方法做设计,当需要多个实例的时候非常简单,再定义一个结构体实例就可以了,如果使用分散变量,那就惨了。提到了面向对象,难道当年ARM设计指令集的时候就是为面向对象编程优化的吗?

局部变量

上面的例子适用于静态变量(包括全局变量、文件域静态变量和函数域静态变量),再来看看局部变量的情况。

注意local例子的变量声明加上了volatile关键字,这个例子太简单,如果不加volatile关键字,那就等价于空函数,什么指令都不会生成的。

// local.c
void func(int a, int b, int c, int d)
{
volatile int la;
volatile int lb;
volatile int lc;
volatile int ld;

la = a;
lb = b;
lc = c;
ld = d;
}

用同样的参数进行编译和反汇编。

arm-none-eabi-gcc -o local.o -c local.c  -Wall -mthumb -march=armv7-m -O2
arm-none-eabi-objdump -d local.o

4条str指令搞定,从第5行到第8行,ldr指令都省了。这是因为局部变量被安排在堆栈的函数调用帧中,基地址就是调用帧首地址,也就是sp寄存器的值。局部变量在调用帧中的位置是编译器决定的,所有局部变量都会被连续地存放。从这里可以得出结论,结构体优先原则不适用于局部变量

local.o:     file format elf32-littlearm
Disassembly of section .text:
00000000 <func>:
0: b084 sub sp, #16
2: 9000 str r0, [sp, #0]
4: 9101 str r1, [sp, #4]
6: 9202 str r2, [sp, #8]
8: 9303 str r3, [sp, #12]
a: b004 add sp, #16
c: 4770 bx lr

ARMv6-M

ARMv6-M,即Cortex-M0和Cortex-M0+同样也支持STM指令,因此生成的代码和ARMv7-M差不多,细节上会稍有差别,毕竟ARMv6-M的大部分指令是16位的,而ARMv7-M则有大量的32位指令。


标签:r4,变量,int,指令,vs,Cortex,str,test
From: https://blog.51cto.com/zoomdy/5871681

相关文章

  • vscode连接云服务器开发
    前言选的腾讯的云服务器,非双11,当时60多一年,还挺划算的,系统选的centos7.9-docker版(已经预装docker了)安装打开VSCode软件,点击最左侧活动栏内的“扩展”小图标,然后搜索......
  • js(变量名提升)
    问:1.什么叫变量名提升答:在js里var和function声明的变量或者函数可以在我们声明之前去使用他们,这种现象就叫变量名提升。 问:2.提升的原理是什么答:js会把代码分为可......
  • 解决VSCode无法显示Unity代码提示和源代码
     1,先删除项目目录下的配置文件,也可以理解为除文件夹外的其他文件 2,先把vscode选中,下拉框中没有vscode的找到文件就可以导进来再选中。然后红框里的不要勾选,因为我是这......
  • Tomcat官网下载tomcat教程及环境变量的配置
    Tomcat官网下载tomcat教程1、进入tomcat官网:​​tomcat.apache.org​​,在右侧导航栏找到:Tomcat各种版本;2、点击其中一个版本,进入选择界面,在QuickNavigation找到Archives,点......
  • make.exe打印出一个变量和修改追加变量的方法
    目标:makefile中的变量可能是这样:CPU=-mcpu=cortex-m4#fpuFPU=-mfpu=fpv4-sp-d16#float-abiFLOAT-ABI=-mfloat-abi=hard#mcuMCU=$(CPU)-mthumb$(FPU)$(FLOAT-A......
  • EasyX图形库安装,以及使用样例(vc6.0,vs2013,其他类同)
    ①​​官网下载​​②解压安装(由于自己电脑安装了vc6.0和vs2013以该两个为例,其他都是一样的安装方法)③图形库测试利用图形库画星空(l编译器vs2013)#include<stdafx.h>#......
  • 2.4 变量定义与变量类型
    变量的基本概念程序中经过操作其值可以改变的量称为变量变量在使用前必须加以声明每一个变量要有一个与其它变量不相同的合法的名字。第一个字符必须是字母或下划......
  • LVS+Keepalived 高可用群集
    一、LVS+Keepalived高可用群集在这个高度信息化的IT时代,企业的生产系统、业务运营、销售和支持,以及日常管理等环节越来越依赖于计算机信息和服务,对高可用(HA)技术的应......
  • Ansible变量
    1ansible变量的作用主要用于保存Ansible运维操作中所需使用的信息通过对变量值的更改或者变量值的读取,可以对Ansible运维操作进行灵活的管控一方面能提升运维的自动化......
  • 变量和数据类型
    Python零基礎新手入門#02變數與資料型態intfloatstring姓名=input("请输入你的姓名:")出生年=input("请输入你的出生年:")print(f"{姓名}你好,你出生于{出生......