Cortex-M系列:在C文件中插入汇编

本文深入探讨在Cortex-M系列CPU的C文件中嵌入汇编程序的方法,包括嵌入汇编、内联汇编等技巧,并通过实际C函数示例,如基本运算、数组处理等,展示如何优化程序执行效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文旨在阐述如何在Cortex-M系列CPU的C文件中插入汇编程序。通过观察C语言生成的汇编程序,在必要时刻知道如何写更为复杂的汇编代码。通过学习ARM工具链生成的汇编程序,可实现较高效的汇编指令学习,更重要的在于知道程序有多大的优化空间。特别地,理解数组处理方式,能更深刻地理解数组越界到底会有怎样的影响;理解文本池访问的方式,能更直观地知道常量以及const关键字在汇编中是如何体现的。

本文承接参考链接[3]和[7],故对一些概念没有再具体讲解。

目录

1 Cortex-M7程序生成

1.1 基本概念

1.2 书写规范[6]

1.3 嵌入汇编

1.4 内联汇编

1.5 其他汇编形式

2 C函数例子——基本运算

2.1 加法测试

2.2 乘除减和移位

2.3 浮点测试

2.4 数组处理,SP相关寻址

2.5 多参数调用

2.6 排他访问

2.7文本池访问

3 参考资料


1 Cortex-M7程序生成

编译的整个过程为:预编译、编译、汇编、链接

1.1 基本概念

格式:

[标号] <指令|条件|S> <操作数>;[注释]

注:指令指伪指令或汇编指令;条件和S见“表5.3 Cortex-M 汇编语言的后缀”[1]。

1、在ARM工具链和GNU工具链的伪指令是不同的,即不同的编译器GNU的伪指令可能不同。一般GNU工具链的伪指令以“.”为开头。下面我将在Keil5中使用ARM工具链测试一些汇编指令,都是在.c文件中编写和调用。

2、汇编指令到具体CPU手册找,如[2]

2、操作数可以是下表的寄存器,也可以是立即数“#0x01”,或是定义的常量(在ARM工具链中,用“EQU”定义,用“=常量名”引用),或是指针的本质用法("[R3]")。

寄存器

__asm中的字符串

APSR

"apsr"

BASEPRI

"basepri"

BASEPRI_MAX

"basepri_max"

CONTROL

"control"

EAPSR(EPSR+APSR)

"eapsr"

EPSR

"epsr"

FAULTMASK

"faultmask"

IAPSR(IPSR+APSR)

"iapsr"

IEPSSR(IPSR+EPSR)

"iepsr"

IPSR

"ipsr"

MSP

"msp"

PRIMASK

"primask"

PSP

"psp"

PSR

"psr"

 r0~r12

"r0"~"r12"

 r13

"r13"或“sp”

 r14

"r14"或"lr"

 r15

"r15"或“oc”

XPSR

"xpsr"

1.2 书写规范[6]

a\所有标号必须在一行的顶格书写

b\所有的指令均不能顶格书写

c\标识符大小写敏感

d\ARM指令、伪指令、寄存器名可以全部大写字母,也可以全部小写字母,但不要大小写混合使用。

(如可以"BX lr”,但不能“Bx lr”)

e\语句间可以插入空行,也可以某行只有标号,以使得源代码的可读性更好。

1.3 嵌入汇编

__asm uint8_t myADD( uint8_t lhs,uint8_t rhs )
{
	MOV r2,r0
	ADDS r0,r2,r1
	UXTB r0,r0
	BX lr
}

注:容易出现“表面语法错误”,编译后并不会报错。

嵌入汇编提供一个汇编函数的空壳子,提供个入口,里面的所有操作都有程序员自己写,尤其是lhs和rhs不能在汇编中直接使用。在调用者调用时,lhs存在r0,rhs存在r1[7]。需要返回的值应该写在r0上。函数的返回也需要将LR写入CP寄存器来返回。中间的过程再补充一下就行。

调用者使用的C语言及发生的汇编如下:

remp = testAdd(10,20);

想要在汇编中直接使用lhs和rhs可用下一方法。

1.4 内联汇编

内联汇编的框架有C函数搭建,在内联汇编里即可以使用寄存器名,也可以使用“可见”的C变量。

uint8_t myADD1( uint8_t lhs,uint8_t rhs)
{
	uint8_t rslt;
	__asm
	{
		ADD rslt,lhs,rhs
	}
	return rslt;
}

uint8_t myADD2( uint8_t lhs,uint8_t rhs)
{
	uint8_t rslt;
	__asm("ADDS rslt,lhs,rhs");
	return rslt;
}

两个函数的汇编结果相同,结果如下所示:

 

1.5 其他汇编形式

从汇编中调用C函数

IMPORT my_add_c

BL my_add_c

从C程序中的汇编代码

BL _cpp(my_add_c)

从C中调用汇编函数

EXPORT My_Add

注:详见[1]中的第20章

2 C函数例子——基本运算

记得将优化等级改为最低。下面的代码可能有的复制不完整,特性是BX指令。

2.1 加法测试

 uint64_t和int64_t没有在本文测试。

2.2 乘除减和移位

Void BHWTest2(void)

 

Void S_BHWTest2(void)

 

2.3 浮点测试

注意这里是用BX返回调用者的。

2.4 数组处理,SP相关寻址

a、为了在(向下增长的)栈中给数组留下空间,程序直接移动了栈指针。这个技术成为“SP相关寻址”

b、因为程序调用了子函数,因此使用“PUSH {lr}”和“POP {pc}”组合更高效。

c、调用ARM的标准库函数时,可以发现函数只把memset函数的第一个参数和第三个参数传入了,可能是它内部优化导致的吧。

2.5 多参数调用

调用者部分:

被调用者部分:

a\压入栈的有4个寄存器,每个寄存器4字节,于是栈地址减小了0x10.

b\比较特别的是,调用者知道多余的参数会“破坏”栈,所以提前把R3这个非被调用者保存寄存器保存起来,供第5个参数破坏。

c\显然这个函数效率很低,如果用SIMD中的UADD8,那速率就快多了。这也是汇编加速可操作的地方吧。但不确定3级或2级优化下,这里是否优化,感兴趣的读者可以自己试一下。

2.6 排他访问

自己还没试过,主要是没需求,另外就是[9] 基于ARM的排他访问原理及应用 写的太好了。

2.7文本池访问

实例1:当非零组数定义在函数内

由于里面有数组,所有使用了4.4节的相关寻址的概念,但不是这节的重点。值得一提的是,虽然数组的有效数只有0x4C的大小,sizeof(BASEtable2)==0x4C,但在数组前留了4个字节,或者说多压了4个字节入栈。

1、由于这里的数组时非零数组,于是隐式调用了memcpy(r0,r1,r2)函数。

2、而原始数据存储的地方是在代码段,定义局部数组,所有需要把代码段内的值赋值到栈中。

3、当真正运行到0x08005EEE行时,PC指针的值其实是0x08005EF0 ,即该行指令的下一个指令的地址(这一点需要特别注意)。

0x08005EF0+24=0x08005F08

这个地址正好是函数结束后的内存(考虑4字节对齐),没错这个就是0x081075C8,

实例2:当常量非零数组定义在函数外,

和实例1比,少了隐含数组复制。

实例3:单个常量

有意思,常量不是在Flash中的真常量,而是一个常变量,使用强制转换功能是改用改变这个“常变量”的值的。

3 参考资料

[1] Joseph Yiu. ARM Cortex-M3与Cortex-m4权威指南(第3版)[M]. 吴常玉,曹孟娟,王丽红,译. 北京:清华大学出版社,2015.

[2] ARM® Cortex®-M7 Devices Generic User Guide

[3]Cortex-M系列:ARM架构与汇编指令集 https://blog.youkuaiyun.com/NoDistanceY/article/details/104177163

[6] Thumb指令集及汇编格式 https://wenku.baidu.com/view/49ab91606fdb6f1aff00bed5b9f3f90f77c64d5a.html

[7] Cortex-M系列:非中断、特权模式下的汇编语言 https://blog.youkuaiyun.com/NoDistanceY/article/details/104003831

[8] Cortex-M for Beginners - 2017_EN_v2  https://community.arm.com/developer/ip-products/processors/b/processors-ip-blog/posts/white-paper-cortex-m-for-beginners-an-overview-of-the-arm-cortex-m-processor-family-and-comparison

[9] 基于ARM的排他访问原理及应用 https://blog.youkuaiyun.com/tissar/article/details/83008719

 

<think>好的,我现在需要回答用户关于GHS编译器中.data段使用ALIGN(8)对齐方式对程序编译结果的影响的问题。首先,我需要确认GHS编译器的相关文档或资料,了解ALIGN指令的具体作用。 用户提到的.data ALIGN(8),应该是在汇编或链接脚本中指定数据段的对齐方式为8字节对齐。对齐在计算机系统中非常重要,它影响数据在内存中的布局和访问效率。 接下来,我需要考虑不同的对齐方式带来的影响。比如,未对齐的数据访问在某些处理器上可能会导致性能下降,甚至引发硬件异常。特别是对于需要原子操作或向量化指令的数据,对齐是必须的。 然后,我需要具体分析ALIGN(8)的作用。8字节对齐意味着.data段的起始地址必须是8的倍数。这会影响后续数据成员的布局,每个成员也会根据其类型和对齐要求进行排列。例如,一个4字节的int变量在8字节对齐的段中,可能会有4字节的填充,以确保下一个8字节对齐的数据正确放置。 还需要考虑链接器如何处理多个目标文件的.data段。如果每个目标文件的.data段都按8字节对齐,链接时可能会在段之间插入填充,以维持对齐要求。这可能导致可执行文件的大小增加,尤其是当多个小数据段被链接时,填充会累积。 此外,不同处理器架构的对齐要求不同。例如,RISC架构如ARM或MIPS可能严格要求对齐,而x86可能更宽松但仍有性能影响。用户可能是在针对特定处理器进行优化,所以需要提到架构相关的影响。 还要考虑内存访问效率。对齐的数据可以被处理器更高效地加载,尤其是SIMD指令(如SSE、AVX)通常需要数据在特定边界对齐。使用ALIGN(8)可能提升这些指令的执行效率,减少内存访问周期。 可能的问题包括内存浪费,因为填充字节会增加程序的内存占用。这对于内存受限的嵌入式系统可能尤为重要,需要在空间和性能之间权衡。 最后,需要给出实际例子或建议,比如在需要处理双精度浮点数(8字节)或64位数据时,使用8字节对齐可以避免多次内存访问,提高性能。同时,提醒用户根据目标平台和具体需求选择合适的对齐方式,避免不必要的内存开销。 总结时,要分点说明影响,包括内存布局、访问效率、可执行文件大小、硬件兼容性等方面,确保回答结构清晰,用户能逐步理解每个影响点。</think>在GHS编译器中,使用`.data ALIGN(8)`指定数据段对齐方式时,会对程序编译结果产生以下分层次影响: --- ### 1. **内存布局优化** - **地址对齐规则**:强制`.data`段起始地址为**8字节边界**(地址值末3位为0)。 - **数据成员排列**:段内变量按`8字节倍数`地址存储。例如: - `int x`(4字节)后若有`double y`,编译器会插入4字节填充,使`y`地址对齐8字节。 - **多目标文件链接**:若多个目标文件的`.data`段均要求8字节对齐,链接器会在段之间插入填充字节以维持对齐。 --- ### 2. **硬件访问效率** - **性能提升**: - 对齐数据可触发CPU单周期访问(如ARMv8的LDR指令加载64位数据)。 - 未对齐访问可能导致多次总线操作(如32位系统读取跨越4字节边界的8字节数据)。 - **SIMD指令支持**: - SSE/AVX等向量指令要求数据按16/32字节对齐,但基础对齐(如8字节)是必要前提。 --- ### 3. **存储空间权衡** - **内存浪费**: ```c // 示例:结构体因对齐产生的填充 struct { char a; // 1字节 // 填充7字节(ALIGN(8)时) double b; // 8字节 }; // 总大小=16字节(无对齐时为9字节) ``` - **嵌入式系统影响**:在内存受限场景中需评估对齐带来的空间成本。 --- ### 4. **硬件兼容性** - **严格对齐架构**: - ARM/MIPS/RISC-V未对齐访问会触发异常(需`-mno-unaligned-access`编译选项)。 - PowerPC对非对齐浮点数访问直接报错。 - **x86容忍度**:虽支持非对齐访问,但性能下降约20%-30%(需额外时钟周期处理)。 --- ### 5. **应用场景建议** - **必须使用的情况**: - 操作64位原子变量(如C11 `atomic_uint64_t`) - 使用`double`类型数组且目标架构有严格对齐要求 - **避免滥用的情况**: - 以字符缓冲区为主的.data段 - 内存极度受限的IoT设备程序 --- ### 验证方法 通过编译后生成的Map文件检查实际对齐效果: ```plaintext Memory Map: .data 0x20001000 ALIGN(8) 0x20001000 . = ALIGN(0x8) 0x20001000 _sdata = . *(.data*) 0x20001008 some_double // 实际分配地址符合8对齐 ``` --- ### 总结 `.data ALIGN(8)`是空间换时间的典型取舍,需结合目标硬件特性(如Cortex-A53的NEON指令对齐要求)和实际数据使用模式(如高频访问的64位数据)综合决策。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值