为了说明C语言如何被编译成汇编语言,我们可以通过一个简单的C程序,并使用ARM编译器来生成相应的汇编代码。
// simple.c
int add(int a, int b) {
return a + b;
}
int main() {
int result = add(5, 3);
return result;
}
使用ARM编译器生成汇编代码
我们使用 arm-none-eabi-gcc
(GNU ARM Embedded Toolchain)来编译这个C程序并生成汇编代码。以下是在Linux或类似Unix系统上的操作步骤:
-
安装ARM编译工具链: 如果未安装,可以通过以下命令安装:
sudo apt-get install gcc-arm-none-eabi
-
编译生成汇编代码: 使用
-S
选项来生成汇编代码。arm-none-eabi-gcc -S simple.c -o simple.s
-
查看生成的汇编代码: 打开生成的
simple.s
文件。
生成的汇编代码(simple.s)
.cpu arm7tdmi
.eabi_attribute 20, 1
.eabi_attribute 21, 1
.eabi_attribute 23, 3
.eabi_attribute 24, 1
.eabi_attribute 25, 1
.eabi_attribute 26, 1
.eabi_attribute 30, 6
.eabi_attribute 34, 0
.eabi_attribute 18, 4
.file "simple.c"
.text
.align 2
.global add
.arch armv4t
.syntax unified
.arm
.fpu softvfp
.type add, %function
add:
@ Function supports interworking.
@ args = 0, pretend = 0, frame = 8
@ frame_needed = 1, uses_anonymous_args = 0
@ link register save eliminated.
str fp, [sp, #-4]!
add fp, sp, #0
sub sp, sp, #12
str r0, [fp, #-8]
str r1, [fp, #-12]
ldr r2, [fp, #-8]
ldr r3, [fp, #-12]
add r3, r2, r3
mov r0, r3
add sp, fp, #0
@ sp needed
ldr fp, [sp], #4
bx lr
.size add, .-add
.align 2
.global main
.syntax unified
.arm
.fpu softvfp
.type main, %function
main:
@ Function supports interworking.
@ args = 0, pretend = 0, frame = 8
@ frame_needed = 1, uses_anonymous_args = 0
push {fp, lr}
add fp, sp, #4
sub sp, sp, #8
mov r1, #3
mov r0, #5
bl add
str r0, [fp, #-8]
ldr r3, [fp, #-8]
mov r0, r3
sub sp, fp, #4
@ sp needed
pop {fp, lr}
bx lr
.size main, .-main
.ident "GCC: (GNU Arm Embedded Toolchain 9-2020-q2-update) 9.3.1 20200408 (release)"
代码分析
add
函数
str fp, [sp, #-4]!
:将当前帧指针fp
的值保存到栈中,并将栈指针sp
向下偏移4字节。!
表示更新后sp
的值。add fp, sp, #0
:将sp
的值赋给fp
,即建立当前函数的栈帧。sub sp, sp, #12
:为局部变量分配12字节的空间。str r0, [fp, #-8]
和str r1, [fp, #-12]
:分别将函数参数a
和b
存储到栈帧中。ldr r2, [fp, #-8]
和ldr r3, [fp, #-12]
:从栈帧中加载参数a
和b
的值到寄存器r2
和r3
。add r3, r2, r3
:计算a + b
,结果存储在r3
中。mov r0, r3
:将计算结果r3
设置为函数返回值。add sp, fp, #0
:恢复栈指针sp
到函数调用前的位置。ldr fp, [sp], #4
:恢复帧指针fp
的值。bx lr
:返回到调用该函数的地方。lr
寄存器保存了返回地址。
main
函数
push {fp, lr}
:将当前的帧指针fp
和链接寄存器lr
保存到栈中。add fp, sp, #4
:建立当前函数的栈帧。sub sp, sp, #8
:为局部变量分配8字节的空间。mov r1, #3
和mov r0, #5
:将常数3
和5
分别加载到寄存器r1
和r0
中,作为add
函数的参数。bl add
:调用add
函数。str r0, [fp, #-8]
:将add
函数的返回值存储到栈帧中。ldr r3, [fp, #-8]
:从栈帧中加载add
函数返回值到寄存器r3
。mov r0, r3
:将r3
设置为main
函数的返回值。sub sp, fp, #4
:恢复栈指针sp
到函数调用前的位置。pop {fp, lr}
:恢复帧指针fp
和链接寄存器lr
的值。bx lr
:返回到调用该函数的地方。
总结
通过上述步骤,我们展示了如何使用ARM编译器将一个简单的C程序编译成汇编代码。这个过程包括安装编译工具链、编写和编译C代码、生成和分析汇编代码。生成的汇编代码展示了C程序如何在低级别实现,包括函数调用、参数传递和返回值处理等细节。