最近比较一下KEIL和IAR两个编译器产生的代码,基于Cortex-M3处理器的,然后发现了一几个奇怪的地方。

很简单的一个C的for循环

 void fun_for_add_65535(void)
{
int i;
for (i=; i<65535; i++)
;
} void fun_for_add_65536(void)
{
int i;
for (i=; i<65536; i++)
;
}

按道理这两个函数除了for的终止值不同之外,产生的汇编代码应该不会有什么差异。但是不是。

先看一下在不优化的情况下产生的 void fun_for_add_65535(void) 汇编代码,IAR不优化是-On选项。

使用fromelf打印出汇编语句如下:

                 void fun_for_add_65535(void)
{
int i;
for (i=; i<65535; i++)
\ fun_for_add_65535:
\ MOVS R1,#+
\ MOVS R0,R1
\ ??fun_for_add_65535_0:
\ 4FF6FF71 MOVW R1,#+
\ CMP R0,R1
\ 0000000A 01DA BGE.N ??fun_for_add_65535_1
\ 0000000C 401C ADDS R0,R0,#+
\ 0000000E F9E7 B.N ??fun_for_add_65535_0
;
}
\ ??fun_for_add_65535_1:
\ BX LR ;; return

再来看一下不优化的情况下产生的 void fun_for_add_65536(void) 汇编代码,如下:

首先说明一个是产生的汇编代码中没有进入函数和退出函数时对寄存器的入栈和出栈的封皮,因为没有使用到R4以上的寄存器。

根据ARM 过程调用标准(简称APCS)。

一般来说cortex-m3在编译时会用R0-R3最参数传递,而多余4个的参数需要用到堆栈,返回时返回值放在R0,也就是APCS里约定:每个函数假定R0-R3在函数调用后会改变,而其他的通用寄存器除SP,LR,PC的不会改变,也就是你如果在函数里使用R4,R5等,你要对其进行压栈操作,函数返回时要恢复。因为调用你得函数认为R4,R5它调用完你之后不会改变。

                 void fun_for_add_65536(void)
{
int i;
for (i=; i<65536; i++)
\ fun_for_add_65536:
\ MOVS R1,#+
\ MOVS R0,R1
\ ??fun_for_add_65536_0:
\ B0F5803F CMP R0,#+
\ 01DA BGE.N ??fun_for_add_65536_1
\ 0000000A 401C ADDS R0,R0,#+
\ 0000000C FAE7 B.N ??fun_for_add_65536_0
;
}
\ ??fun_for_add_65536_1:
\ 0000000E BX LR ;; return

抛开一些无关信息,直接对比两个汇编文件的关键信息:

        fun_for_add_65535:
0021 MOVS R1,#+0
0800 MOVS R0,R1
??fun_for_add_65535_0:
4FF6FF71 MOVW R1,#+65535
8842 CMP R0,R1
01DA BGE.N ??fun_for_add_65535_1
401C ADDS R0,R0,#+1
F9E7 B.N ??fun_for_add_65535_0
      fun_for_add_65536:
0021 MOVS R1,#+0
0800 MOVS R0,R1
??fun_for_add_65536_0:
B0F5803F CMP R0,#+65536

01DA BGE.N ??fun_for_add_65536_1
401C ADDS R0,R0,#+1
FAE7 B.N ??fun_for_add_65536_0

我把主要的差异点标了出来,很奇怪,为什么65535(0xFFFF)反而要比65536(0x10000)多了一条MOVW的指令,而65536的CMP指令可以直接比较呢?

65535的CMP只有一条THUMB2(8842),应该是因为直接寄存器比较,而65536的则是和立即数比较,所以产生的是B0F5803F

总的来说65536的for循环多了两个byte的代码,但是执行时间上快了,因为少了每次比较前的对R1赋值的操作。

IAR产生了这样的代码,看看KEIL产生的代码是怎么样的。

这是 fun_for_add_65535 产生的

 ;;;7      //char *str = "abcdefg";
;;;8 void fun_for_add_65535(void)
MOVS r0,#
;;;9 {
;;;10 int i;
;;;11 for (i=0; i<65535; i++)
e000 B |L1.|
|L1.|
1c40 ADDS r0,r0,#
|L1.|
f64f71ff MOV r1,#0xffff
00000a CMP r0,r1
00000c dbfa BLT |L1.|
;;;12 ;
;;;13 }
00000e BX lr
;;;14
ENDP

这是 fun_for_add_65536产生的,

                   fun_for_add_65536 PROC
;;;15 void fun_for_add_65536(void)
MOVS r0,#
;;;16 {
;;;17 int i;
;;;18 for (i=0; i<65536; i++)
e000 B |L1.|
|L1.|
1c40 ADDS r0,r0,#
|L1.|
f5b03f80 CMP r0,#0x10000
00001a dbfb BLT |L1.|
;;;19 ;
;;;20 }
00001c BX lr
;;;21
ENDP

同样把主要的差异点标了出来,同样的现象,65536的多了一行MOV赋值给R1的汇编代码

 6 ;;;11       for (i=0; i<65535; i++)
7 000002 e000 B |L1.6|
8 |L1.4|
9 000004 1c40 ADDS r0,r0,#1
10 |L1.6|
11 000006 f64f71ff MOV r1,#0xffff
12 00000a 4288 CMP r0,r1
13 00000c dbfa BLT |L1.4|
14 ;;;12 ;
 6 ;;;18       for (i=0; i<65536; i++)
7 000012 e000 B |L1.22|
8 |L1.20|
9 000014 1c40 ADDS r0,r0,#1
10 |L1.22|

11 000016 f5b03f80 CMP
r0,#0x10000
12 00001a dbfb BLT |L1.20|
13 ;;;19 ;

尝试开了一下优化,在IAR low优化级别下,产生的汇编代码差异还是一样,65535多了一行赋值代码。

 5    \              fun_for_add_65535:
6 \ 0020 MOVS R0,#+0
7 \ 00E0 B.N ??fun_for_add_65535_0
8 \ ??fun_for_add_65535_1:
9 \ 401C ADDS R0,R0,#+1
10 \ ??fun_for_add_65535_0:
11 \ 4FF6FF71 MOVW R1,#+65535
12 \ 8842 CMP R0,R1
13 \ FADB BLT.N ??fun_for_add_65535_1
 5    \                fun_for_add_65536:
6 \ 0020 MOVS R0,#+0
7 \ 00E0 B.N ??fun_for_add_65536_0
8 \ ??fun_for_add_65536_1:
9 \ 401C ADDS R0,R0,#+1
10 \ ??fun_for_add_65536_0:
11 \ B0F5803F CMP R0,#+65536
12 \ FBDB BLT.N ??fun_for_add_65536_1

在IAR medium的优化级别下,汇编实现则变了,但总的来说还是65536的实现更加快速一些。

而在KEIL开O1级优化下,两个代码除了初值不同没什么差异。如下所示:

1 ;;;11       for (i=0; i<65535; i++)
2 000002 f64f71ff MOV r1,#0xffff
3 |L1.6|
4 000006 1c40 ADDS r0,r0,#1
5 000008 4288 CMP r0,r1
6 00000a dbfc BLT |L1.6|
 5 ;;;18       for (i=0; i<65536; i++)
6 000010 f44f3180 MOV r1,#0x10000
7 |L1.20|
8 000014 1c40 ADDS r0,r0,#1
9 000016 4288 CMP r0,r1
10 000018 dbfc BLT |L1.20|

优化之后处理是要好了很多。

甚是奇怪,是编译器的问题吗?继续尝试其他的值,发现大于65535的其他值产生的代码都一样,除了初值不同。然而小于65535的则是截然不同的,在大于4096的循环下产生的汇编代码跟65535的一样,多一条MOVW的指令,小于4096的则和65536的一样,直接CMP比较立即数。然而更加令我惊讶的是,4096的初始值和4097的初始值产生的汇编代码竟然是一样的!如下所示:

 4         for (i=0; i<4096; i++)
5 \ fun_for_add_4096:
6 \ 0020 MOVS R0,#+0
7 \ 00E0 B.N ??fun_for_add_4096_0
8 \ ??fun_for_add_4096_1:
9 \ 401C ADDS R0,R0,#+1
10 \ ??fun_for_add_4096_0:
11 \ B0F5805F CMP R0,#+4096
12 \ FBDB BLT.N ??fun_for_add_4096_1
21           for (i=0; i<4097; i++)
22 \ fun_for_add_4097:
23 \ 0020 MOVS R0,#+0
24 \ 00E0 B.N ??fun_for_add_4097_0
25 \ ??fun_for_add_4097_1:
26 \ 401C ADDS R0,R0,#+1
27 \ ??fun_for_add_4097_0:
28 \ B0F5805F CMP R0,#+4096
29 \ FBDD BLE.N ??fun_for_add_4097_1

这真是让我无比惊讶,在KEIL上面4096和4097也产生了一样的代码,百思不得其解。什么问题?

最后直接在开发板上调试发现循环次数都是正确的。

猛然发现最后一条指令分别是BLT和BLE,查找arm指令手册发现LT是带符号数小于跳转,而LE则是带符号数小于等于跳转,因为4097是多一次比较的。

哈哈,想着不太可能arm编译器出现这样低级的问题的。

那么最后只剩下一个疑问就是上面的多一条MOVW指令的问题。

我的KEIL是4.73版本,IAR是5.3版本。

KEIL的armcc是ARM C/C++ Compiler, 5.03 [Build 76] [MDK-ARM Standard]

IAR的iccarm是IAR ANSI C/C++ Compiler V5.30.2.51295/W32 for ARM

for循环产生的Cortex-M3汇编代码的一个奇怪现象的更多相关文章

  1. ARM Cortex M3(V7-M架构)硬件启动程序 一

    Cortex-m3启动代码分析笔记 启动代码文件名是STM32F10X.S,它的作用先总结下,然后再分析. 启动代码作用一般是: 1)堆和栈的初始化: 2)中断向量表定义: 3)地址重映射及中断向量表 ...

  2. STM32学习之路入门篇之指令集及cortex——m3的存储系统

    STM32学习之路入门篇之指令集及cortex——m3的存储系统 一.汇编语言基础 一).汇编语言:基本语法 1.汇编指令最典型的书写模式: 标号 操作码        操作数1, 操作数2,... ...

  3. cortex m0启动代码详解

    转自:http://www.cnblogs.com/mddblog/p/4920063.html 阅读目录 概述 1.堆栈空间定义 2.存放中断向量表 3. 复位中断函数(Reset_Handler) ...

  4. 浅析VS2010反汇编 VS 反汇编方法及常用汇编指令介绍 VS2015使用技巧 调试-反汇编 查看C语言代码对应的汇编代码

    浅析VS2010反汇编 2015年07月25日 21:53:11 阅读数:4374 第一篇 1. 如何进行反汇编 在调试的环境下,我们可以很方便地通过反汇编窗口查看程序生成的反汇编信息.如下图所示. ...

  5. STM8S汇编代码分析

    转载:http://blog.csdn.net/u010093140/article/details/50021897使用STVD建立完汇编工程项目之后(具本建立方法可以看我的另一篇博文http:// ...

  6. ARM汇编初探---汇编代码中都有哪几类指令---ARM伪指令介绍

    要学习一个东西首先要把概念搞清楚,以下仅仅是自己的一些关于汇编的理解. 可运行文件里的01码是机器码,机器码不等于汇编码,尽管机器码能够非常easy翻译成汇编码. 汇编码中包括非常多汇编指令.伪指令和 ...

  7. 【嵌入式开发】裸机引导操作系统和ARM 内存操作 ( DRAM SRAM 类型 简介 | Logical Bank | 内存地址空间介绍 | 内存芯片连接方式 | 内存初始化 | 汇编代码示例 )

    [嵌入式开发]ARM 内存操作 ( DRAM SRAM 类型 简介 | Logical Bank | 内存地址空间介绍 | 内存芯片连接方式 | 内存初始化 | 汇编代码示例 )     一. 内存 ...

  8. 【freertos】002-posix模拟器设计与cortex m3异常处理

    目录 前言 posix 标准接口层设计 模拟器的系统心跳 模拟器的task底层实质 模拟器的任务切换原理 cortex M3/M4异常处理 双堆栈指针 双操作模式 栈帧 EXC_RETURN 前言 如 ...

  9. linux内核分析作业:以一简单C程序为例,分析汇编代码理解计算机如何工作

    一.实验 使用gcc –S –o main.s main.c -m32 命令编译成汇编代码,如下代码中的数字请自行修改以防与他人雷同 int g(int x) { return x + 3; } in ...

随机推荐

  1. Unity截图

    什么都不说了,直接上代码. using UnityEngine; using System.Collections; using System.IO; public class CutImage : ...

  2. Java Tomcat Glassfish Weblogic远程debug(remote debug)

    tomcat ./catalina.sh jpda start 这条命令启动tomcat,它就会监听8000端口,等待调试器的连接. 默认监听8000端口,通过设置环境变量JPDA_ADDRESS指定 ...

  3. JAVA关键字transient

    转载自http://www.cnblogs.com/liuling/archive/2013/05/05/transient.html 1.transient关键字只能修饰变量,而不能修饰方法和类.注 ...

  4. [非技术参考]C#枚举类型

    (一)首先讲一个不熟悉的数据类型:byte byte 关键字代表一种整型,该类型按下表所示存储值: 类型 范围 大小 .NET Framework 类型 byte 0 到 255 无符号 8 位整数 ...

  5. [poco] HttpRequest之post方法

    转自 http://www.cnblogs.com/yuanxiaoping_21cn_com/archive/2012/06/10/2544032.html #import <iostream ...

  6. Android Shape画圆,矩形

    画圆环代码如下: 画圆环,外边的边界宽度大一点即可: <?xml version="1.0" encoding="utf-8"?> <shap ...

  7. hibernate中多对多关联

    hibernate中多对多关联 “计应134(实验班) 凌豪” 在关系数据库中有一种常见的关系即多对多关系,例如课程和学生的关系,一个学生可以选择多门课程,同时一门课程也可以被多个学生选择, 因此课程 ...

  8. python退格、方向键无法正常使用解决方法

    CentOS 6.5 自带的Python 2.6.6 箭头以及退格键(Backspace)可正常使用: 自定义所安装的Python 2.7.6却发现箭头以及退格键(Backspace)在使用的时候出现 ...

  9. 如何在Delphi中调用VC6.0开发的COM

    上次写了如何在VC6.0下对Delphi写的COM进行调用,原本想马上写如何在Delphi中调用VC6.0开发的COM时,由于在写事例程序中碰到了个很怪的问题,在我机子上用VC写的接口程序编译能通过. ...

  10. 2016 Multi-University Training Contest 5&6 总结

    第五场和第六场多校都打得很糟糕. 能做到不以物喜不以己悲是假的,这对队伍的情绪也可以算上是比较大的打击. 很多时候我们发现了问题,但是依旧没有采取有效的方法去解决它,甚至也没有尝试去改变.这是一件相当 ...