基于ARM7TDMI的S3C44B0X嵌入式微处理器技术课件课件第4章_第1页
基于ARM7TDMI的S3C44B0X嵌入式微处理器技术课件课件第4章_第2页
基于ARM7TDMI的S3C44B0X嵌入式微处理器技术课件课件第4章_第3页
基于ARM7TDMI的S3C44B0X嵌入式微处理器技术课件课件第4章_第4页
基于ARM7TDMI的S3C44B0X嵌入式微处理器技术课件课件第4章_第5页
已阅读5页,还剩89页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、第4章 ARM汇编语言编程 本章重点本章重点 : ARM汇编器提供的汇编语言特性,包括行格式、 预定义名和内建变量、伪指令、符号、指示符 和宏、表达式和操作符等,以及它们的使用; ARM汇编语言编程举例。 4.1 ARM汇编语言概述 本章讲述的由ARM汇编器提供的汇编语言特 性,如伪指令等,以及它们在编程中如何使用。 主要内容如下: 源代码行格式、预定义的寄存器和协处理 器名、内建变量; ARM伪指令; Thumb伪指令; 符号; 指示符和宏; 表达式和操作符。 4.2 ARM汇编语言特性 4.2.1 行格式、预定义名和内建变量 行格式 symbolinstruction|directive|

2、pseudo_instruction;comment 也就是: 符号 指令|指示符|伪指令 ;注释 上述行格式中用大括号括起来的三部分是 可选的。如果一行中没有symbol,指令|指示符 |伪指令不能从第1列位置开始,指令|指示符|伪 指令前面必须放置空格或Tab(制表符)字符。 每一条指令的助记符可以使用全部大写或 全部小写字符,但不能在同一条指令的助记符 中大、小写混用。指示符必须大写。指令中每 一个寄存器名能够全部大写或全部小写,但不 能大、小写混用。 行格式中symbol通常是标号(label),在 指令或伪指令前它总是标号,在某些指示符前 它是表示变量或常量的符号(symbol)。

3、行格式中symbol必须从第一列开始,不能 含任何如空格或Tab的字符,详见本节符号命名 规则部分。 预定义寄存器和协处理器名 预定义寄存器名 预定义程序状态寄存器名 预定义浮点寄存器名 预定义协处理器名和协处理器寄存器名 内建变量 内建变量(built in variables)见表4.1,它 们是由ARM汇编器定义过的。内建变量不能用 SETA、SETL或SETS指示符设置,它们能被用在 表达式或条件中,如: IF ENDIAN=“big” 4.2.2 ARM伪指令 可用在ARM状态的伪指令有: ADR、ADRL、LDFD、LDFS、LDR和NOP 伪指令。 ADR ARM伪指令 ADR伪

4、指令装入一个相对程序或相对寄存 器的地址到一个寄存器。 格式 ADRcondition register,expression 使用 使用中,ADR总是被汇编成一条指令。汇 编器试图产生一条ADD或SUB指令,装入地址。 如果不能用一条指令构造出地址,则产生错误 信息,汇编失败。 如果expression是相对程序的,计算产生的 地址必须与ADR伪指令在同一个代码区域。 使用举例 Test1 MOV r1,#0ADR r2,Test1 ;产生指令SUB r2,pc,#0 xC ADRL ARM伪指令 ADRL伪指令装入一个相对程序或相对寄存 器的地址到一个寄存器。与ADR伪指令功能相 似,但A

5、DRL比ADR能装入更大的地址范围,原 因是ADRL产生两条数据处理指令。 格式 ADRLcondition register,expression 使用 使用中,ADRL总是被汇编成2条指令。如 果汇编器不能以2条指令构造出地址,则产生错 误信息,汇编失败。 如果expression是相对程序的,它必须计算 产生一个与ADRL伪指令在同一个代码区域的地 址,否则在随后的连接时,地址可能出了范围。 使用举例 start MOV r0,#10ADRL r4,start+60000 ;产生指令ADD r4,pc,#0 xe800 ; ADD r4,r4,#0 x254 LDFD和LDFS ARM伪

6、指令 LDFD伪指令装入一个双精度浮点常数到指 定的浮点寄存器。 LDFS伪指令装入一个单精度浮点常数到指 定的浮点寄存器。 格式 LDFDcondition fp-register,=expression LDFScondition fp-register,=expression 使用 使用举例 LDFD f2,=2.01E205 ;装入2.01E205到f2 LDFS f0,=2.01E-5 ;装入2.01E-5到f0 LDR ARM伪指令 LDR伪指令装入一个32位常数值或一个地 址到一个寄存器。 格式 LDRcondition register,=expression|label-ex

7、pression 使用 使用LDR伪指令有两个主要目的,一是当 一个立即数的值由于超了范围,不能用MOV和 MVN指令装入到一个寄存器时,用LDR伪指令 产生一个文字池常数;二是装入一个相对程序 或外部的地址到一个寄存器。 使用举例 LDR r0,=0 x1ff ;装入0 x1ff到r0 LDR r1,=label ;装入label地址到r1 NOP ARM伪指令 对NOP伪指令,汇编器产生什么也不操作 的ARM指令:MOV r0,r0。 格式 NOP 4.2.3 Thumb伪指令 ADR Thumb伪指令 ADR伪指令装入一个相对程序或相对寄存 器的地址到一个寄存器。 格式 ADR regi

8、ster,expression 使用 使用中,在Thumb状态,ADR只能产生字 对齐的地址。要使用ALIGN指示符去确认 expression是字对齐的。 若表达式是相对程序的,必须计算产生一 个与ADR伪指令在同一个代码区域的地址。 使用举例 ADR r3,testexml ;产生指令ADD r3,pc,#nn ;code ALIGN testexm1 DCW 1,2,3,4 LDR Thumb伪指令 LDR伪指令装入一个32位常数值或一个地址到一 个低寄存器中。 格式 LDR register,=expression|label-expression 使用 使用LDR伪指令有两个主要目的

9、,一是当一个立即 数的值由于超出MOV指令的范围,不能装入一个寄存 器时,产生文字池常数;二是装入一个相对程序或外 部的地址到一个寄存器。 使用举例 LDR r0,=0 x0ffe ;装入0 x0ffe到r0 LDR r1,=labeladdr ;装入labeladdr地址到r1 MOV Thumb伪指令 MOV伪指令传送一个低寄存器的值到另一 个低寄存器(r0-r7)。而MOV指令不能传送一 个低寄存器的值到另一个低寄存器。 格式 MOV Rd,Rs 使用 在使用中,汇编器将MOV伪指令变成带立 即数的ADD指令,指令中立即数的值为0。 使用举例 MOV Rd,Rs;产生指令ADD Rd,R

10、s,#0 NOP Thumb伪指令 对NOP伪指令,汇编器产生什么也不操作 的Thumb指令:MOV r8,r8。 格式 NOP 4.2.4 符号(symbols) 使用符号能够代表变量、地址和数值常数。 符号代表地址时,也称为标号。 符号命名规则 符号命名遵守以下规则: 在符号名中可以使用大写字母、小写字母、数 字字符或下划线字符。 除了局部标号外,不允许在符号名的第一个字 符位置使用数字字符。 符号名中对大、小写字母是敏感的。 在符号名中所有的字符是有意义的。 在它们的作用范围内,符号名必须是唯一的。 符号名必须不使用内建变量名、预定义寄存器 名和预定义协处理器名。 符号名应该不使用与指令

11、助记符或指示符相同 的名字。 如果需要在符号名中使用更大范围的字符,使 用如下举例的格式为符号名划界线: |C$code| 其中两边的两条竖线不是符号的一部分, 只用于为符号名划界线,它们之间不允许使用 竖线、分号和换行符。 变量(variables) 变量有三种类型: 数值 逻辑 串 变量的类型不能被改变,变量的值可以被改变。 汇编时串变量的替换 标号(labels) 相对程序的标号 相对寄存器的标号 绝对地址 局部标号(local labels) 局部标号使用099范围内的一个数,可以 有选择地在其后跟随一个表示当前范围的名字。 局部标号用在指令中,指出分支的目标处。 局部标号格式为: n

12、routname 常量 常量由数值常量、串常量、布尔常量和字符常量 组成。 数值常量 数值常量在汇编语言中采用以下三种形式: 十进制数,例如234; 十六进制数,例如0 x7b或0 x7B; n进制数,格式为n_xxx,n是29之间的一个基数,xxx 是这个基数下的数值,例如8_375,表示基数为8(八 进制数),数值为375。 串常量 布尔常量 字符常量 使用举例: anum SETA 3500 ;假定anum在以前声明过 addr DCD 0 x00ff ;十六进制数 DCD 0 x00FF ;十六进制数 DCD 2_11000011 ;二进制数 bnum SETA 8_74007 ;假定

13、bnum在以前声明过,八进制数 LDR r1,=A ;字符 4.2.5 指示符(directives) 汇编器提供指示符用来支持: 定义数据结构和为数据分配空间; 文件分隔成逻辑上的一个或多个区域; 错误报告和汇编列表控制; 符号定义; 条件汇编和重复汇编,以及在一个文件中包含 辅助文件。 4.2.6 与代码有关的指示符 定义区域的AREA指示符 AREA指示符通知汇编器,汇编一个新的代 码或数据区域。区域是能被连接器(linker)处 理的、独立的、命名的、不可分的代码或数据 块。 格式 AREA name,attr ,attr 使用 一个汇编程序必须最少有一个AREA指示符。 当程序比较大

14、时,可以将代码和数据用不 同的名字分为多个区域。 使用举例 如下例子定义了一个只读,称为Test的代 码区域。 AREA Test,CODE,READONLY ;代码区域,名为Test ;code 用于选择指令集的CODE16和CODE32指示符 格式 格式分别是: CODE16 CODE32 使用 在ARM状态当使用BX指令分支到Thumb指 令时,使用CODE16。CODE16放置在分支目标 处代码的前面。当从Thumb状态分支到ARM指 令时,使用CODE32。CODE32放置在分支目标 处代码的前面。 使用举例 使用举例1:这个例子给出如何从ARM指令 分支到Thumb指令。 AREA

15、 ARMtoThumb,CODE,READONLY ;这个区域开始于ARM状态 ADR r1,test1+1 ;装入地址,设置最低位为1 BX r1 ;分支并且改变指令集 CODE16 ;跟随指令为 Thumb指令 test1 MOV r0,#20 ;Thumb指令 源程序结束的END指示符 END指示符告诉汇编器,已经汇编到源文 件结束处。 格式 END 使用 使用时,每个汇编语言源文件必须以END 指示符结束。 使用举例 AREA Init,CODE,READONLY END 声明汇编程序入口点的ENTRY指示符 ENTRY指示符声明一个程序的入口点。 格式 ENTRY 使用 使用时,必须

16、为一个程序指定一个且只有一个 ENTRY指示符。如果不存在ENTRY或存在一个以上 ENTRY,连接时产生错误信息。如果在一个单一的源 文件中存在多个ENTRY,汇编时产生错误信息。 使用举例 AREA Init,CODE,READONLY ENTRY ;程序入口点 禁止浮点运算的NOFP指示符 NOFP指示符表明在一个汇编语言源文件中 不接受浮点指令。 格式 NOFP 使用 使用NOFP指示符确认在软件或目标硬件不 支持浮点指令的情况下,程序中没有使用浮点 指令。 定义局部标号使用范围的ROUT指示符 ROUT指示符标记局部标号使用范围的界线。 格式 name ROUT 使用 在使用中,RO

17、UT指示符限制了局部标号的使用 范围。这使得程序员容易避免偶然引用一个错误的标 号这种情况的发生。如果不存在ROUT指示符,局部标 号的使用范围是整个AOF区域。如果存在ROUT指示符, 局部标号的使用范围在两个ROUT之间。 使用name选项用于保证每次引用正确的标号。如 果标号名或引用标号名与ROUT指示符前面的名字不匹 配,汇编器产生错误信息,汇编失败。 使用举例(见参考书P129) 4.2.7 与数据定义有关的指示符 在代码中使用数据的DATA指示符 DATA指示符通知汇编器,这个标号是在代 码中的数据标号,这意味着标号是在代码段中 的数据的地址。 格式 label DATA 使用 使

18、用时,如果需要在Thumb代码区域用到 某一个数据定义指示符,如DCD、DCB和DCW, 定义数据时,必须使用DATA指示符。 使用举例 AREA test,CODE Thumb_Code ;code ;code MOV pc,lr Thumb_Data DATA DCB 2,5,8 分配存储器字节的DCB指示符 DCB也可以用 = 指示符代替。 DCB指示符分配一个或多个存储器中的字 节,并且定义初始运行时的存储器内容。 格式 label DCB expression ,expression 使用 如果需要在Thumb代码中用DCB定义带标 号的数据,必须使用DATA指示符。 如果DCB后跟

19、随着指令,应该使用ALIGN指 示符去确认指令存放是边界对齐的。 使用举例(见参考书P131) 分配存储器半字的DCW和DCWU指示符 DCW指示符分配一个或多个存储器中的半 字,以2字节边界对齐,定义初始运行时的存储 器内容。 DCWU与DCW的区别是:DCWU不要求以2 字节边界对齐。 格式 格式分别是: label DCW expression,expression label DCWU expression,expression 使用 使用时,如果需要在Thumb代码中用DCW (DCWU)定义一个带标号的数据,必须使用 DATA指示符。 如果DCW(DCWU)后跟随着指令,要使 用A

20、LIGN指示符,确认指令是字边界对齐的。 使用举例(见参考书P132) 分配存储器字的DCD和DCDU指示符 DCD也可以用 分配127字节内容为0的存储区 边界对齐的ALIGN指示符 用ALIGN指示符在代码中对齐当前的位置 到确定的边界。 格式 ALIGN expression,offset-expression 使用 在使用中,用ALIGN去确认代码地址被正 确地对齐。 当需要时,可以使用ALIGN去确认Thumb 代码地址按字对齐。例如,ADR Thumb伪指令 只能装入字对齐的地址。 使用举例(见参考书P135) 声明文字池的LTORG指示符 LTORG指示符通知汇编器,立即汇编当前

21、 的文字池。 格式 LTORG 使用 由AREA指示符定义开始的每个区域,在代 码区域结尾处或汇编结尾处,即使不写出 LTORG,汇编器也执行LTORG指示符。 使用LTORG确认在LDR、LDFD和LDFS伪指 令范围内,文字池被汇编。大一些的程序可能 要求几个文字池。 放LTORG指示符应该在无条件分支或子程 序返回指令之后,使得处理器不会试图把常数 当作指令去执行。 在文字池中,汇编器以字边界对齐数据。 使用举例(见参考书P136) 定义结构化内存表的MAP和FIELD指示符 MAP也可以用 指示符代替。 FIELD也可以用 # 指示符代替。 MAP和FIELD指示符用于描述结构化内存表

22、。 MAP指示符 MAP指示符设置结构化内存表首地址在指 定位置。结构化内存表定位计数器用表示, 被设置成相同地址。 格式 MAP expression,base-register 使用 MAP指示符可以多次使用,定义多个结构 化内存表。 计数器在第一个MAP指示符被使用前设置 为0。 使用举例(见参考书P137) FIELD指示符 FIELD指示符描述已经由MAP定义的结构化 内存表中的数据域。 格式 label FIELD expression 使用 使用 指示符与 # 指示符的组合来描述结 构化内存表。 使用举例(见参考书P137) 4.2.8 符号定义指示符 声明并初始化全局变量的GB

23、LA、GBLL和GBLS 指示符 GBLA指示符声明并初始化一个全局算术变 量,取值范围与数值表达式相同。 GBLL指示符声明并初始化一个全局逻辑变 量,取值范围为TRUE或FALSE。 GBLS指示符声明并初始化一个全局串变量, 取值范围与串表达式相同。 格式 格式分别是: GBLA variable-name GBLL variable-name GBLS variable-name 使用 使用举例(见参考书P139) 声明并初始化局部变量的LCLA、LCLL和LCLS 指示符 局部变量仅仅能在一个宏内声明。 LCLA指示符声明并初始化一个局部算术变 量,取值范围与数值表达式相同。 LCL

24、L指示符声明并初始化一个局部逻辑变 量,取值范围为TRUE或FALSE。 LCLS指示符声明并初始化一个局部串变量, 取值范围与串表达式相同。 格式 格式分别是: LCLA variable-name LCLL variable-name LCLS variable-name 使用 使用举例(见参考书P140) 设置变量值的SETA、SETL和SETS指示符 SETA指示符设置局部或全局算术变量的值。 SETL指示符设置局部或全局逻辑变量的值。 SETS指示符设置局部或全局串变量的值。 格式 格式分别是: variable-name SETA expression variable-name

25、SETL expression variable-name SETS expression 使用 使用SETA、SETL和SETS之前,必须先声明 全局变量或局部变量,后设置它们的值。 使用举例(见参考书P141) 为协处理器寄存器定义一个名字的CN指示符 为指定的协处理器定义一个名字的CP指示符 (具体内容见参考书P141) 给符号名一个数值常数的EQU指示符 EQU也可以用 * 指示符代替。 格式 name EQU expression 使用 使用EQU来定义常数,与C语言中#define类似。 使用举例 num2 EQU 25 ;给符号num2指定值为25 声明全局符号的EXPORT或G

26、LOBAL指示符 GLOBAL与EXPORT有相同的功能。 格式 EXPORT symbol qualifier,qualifier,qualifier 使用 使用EXPORT指示符,允许别的文件中的代码引 用当前文件中的符号。 使用举例 AREA TestSub,CODE,READONLY EXPORT DoSub ;函数名能被外部模块使用 DoSub SUB r1,r2,r1 为指定的浮点寄存器定义一个名字的FN指示符 声明其他文件定义的符号的IMPORT或EXTERN 指示符 保留局部符号的KEEP指示符 定义寄存器列表名的RLIST指示符 定义寄存器名的RN指示符 (具体内容见参考书P

27、142-145) 4.2.9 汇编控制指示符 条件汇编IF、ELSE和ENDIF指示符 IF指示符可以用 代替;ELSE指示符可以 用 | 代替;ENDIF指示符可以用 代替。 IF指示符引入一个条件,由这个条件决定 是否汇编指令和或指示符代码1。 ELSE指示符标记指令和或指示符代码2的 开始,当IF后的条件为假,则汇编指令和或 指示符代码2。 ENDIF指示符标记条件汇编结束。 格式 IF logical-expression 指令和或指示符代码1 ELSE 指令和或指示符代码2 ENDIF 使用 汇编器根据条件决定是否汇编某一段代码 使用举例 GBLL Test ;声明一个全局逻辑变量

28、Test=TRUE;IF ;指令和或指示符代码1 | ;ELSE ;指令和或指示符代码2 ;ENDIF。 重复汇编WHILE和WEND指示符 WHILE指示符测试一个条件,由这个条件 决定是否汇编指令和或指示符代码。 WEND指示符表示指令和或指示符代码 结束,由WHILE再次测试条件,决定是否重复 进行汇编,直到条件不成立。 格式 WHILE logical-expression 指令和或指示符代码 WEND 使用 在使用中,WHILE和WEND配对使用,对指令和 或指示符代码重复汇编。重复次数可以是0。 在WHILEWEND内可以使用IFENDIF。 WHILEWEND能被嵌套使用。 使用

29、举例 GBLA count count SETA 3 WHILE count = 5 count SETA count+1 ;指令和或指示符代码;重复汇编3次 WEND 宏定义MACRO、MEND和退出宏MEXIT指示符 MACRO指示符标记一个宏定义的开始; MEND指示符标记这个宏定义的结束; 而MEXIT指示符通知汇编器,从宏中退出。 MACRO和MEND指示符 格式 MACRO macro-prototype ;code MEND 在MACRO指示符后,下一行必须跟着宏原型 (macro-prototype)语句。 宏原型语句格式是: $label macroname $paramet

30、er1,$parameter2 使用 使用时,在宏的内部,像$label、 $parameter这些参数,能够像其他的变量那样, 以同样的方法被使用。每次宏调用(macro invocation)时,都要给它们一个新的值。参数 必须使用$,用来与其他符号区别。 $label是可选参数。如果宏内定义一个内部 标号,$label是有用的。它被看作宏的一个参数。 如果使用符号 | 作为变量,用于表示一个 参数的缺省值。如果变量被省略,用空串替换。 如果一个参数后面紧跟着文本或另一个参 数,在扩展时它们之间无空格时,用“.”放在它 们中间。如果前面是文本后面是参数,不能使 用“.”。 宏定义了局部变量

31、的使用范围。 宏能够被嵌套。 使用举例(见参考书P147-148) MEXIT指示符 MEXIT用于在宏定义结束前退出。 格式 MEXIT 使用 在汇编期间,当需要从一个宏中退出时,使 用MEXIT指示符。 使用举例(见参考书P148) 包含文件的INCLUDE和INCBIN指示符 (见参考书P148-149) 4.2.10 报告指示符 报告指示符的共同之处是使用它们能够产 生一些报告信息。报告指示符包括: ASSERT、INFO、OPT、SUBT和TTL指示符。 表4.7 报告指示符 指示符指示符描描 述述使使 用用 举举 例例 解解 释释 ASSERT断言断言 ASSERT label1=

32、 等,使用无符号数的解释, 因此0-1结果为FALSE。 相对寄存器和相对程序的表达式 一个相对寄存器的表达式计算产生指定寄 存器加或减一个数值常数。 一个相对程序的表达式计算产生程序计数 器(pc)加或减一个数值常数。 逻辑表达式 逻辑表达式由逻辑常量(TRUE或 FALSE)、逻辑变量、布尔操作符、关系和括 号的组合组成。 关系由变量、文字、常量或使用适当操作 符的表达式结合在一起组成。 一元操作符(unary operators) 一元操作符(也称单目运算符)有最高优 先级并且首先被计算。一元操作符位于它的操 作数前面,多个相邻的操作符从右到左计算。 一元操作符见表4.9。(P153)

33、二元操作符(binary operators) 二元操作符(也称双目运算符)被写在一 对它们要进行操作的子表达式之间。相同优先 级的操作符从左到右计算。 表4.10中将二元操作符按优先级不同分为6 组,1组优先级最高,组内*、MOD有相同 的优先级;6组优先级最低。(P154) 4.3 ARM汇编语言编程举例 4.3.1 汇编语言和汇编器 汇编语言 汇编器 (详细内容见参考书P155-156) 4.3.2 ARM汇编语言模块举例 【例4.1】 汇编语言模块举例。 AREA example,CODE,READONLY;命名代码块为命名代码块为example ENTRY;标记第一条要执行的指令标记

34、第一条要执行的指令 run1 MOV r1,#20;设置参数设置参数 MOV r2,#15 SUB r1,r1,r2;r1=r1-r2 ;代码代码 MOV r0,#0 x18;传送到软件中断的参数传送到软件中断的参数 LDR r1,=0 x20026;传送到软件中断的参数传送到软件中断的参数 SWI 0 x123456;通过软件中断指令返回通过软件中断指令返回 END;标记文件结束标记文件结束 4.3.3 调用子程序 【例4.2】 子程序调用。 AREA addsubrout,CODE,READONLY;命名代码块命名代码块 ENTRY;标记执行的第一条指令标记执行的第一条指令 run1 MOV r0,#101;建立参数建立参数 MOV r1,#321 MOV r2,#43 MOV r3,#55 BL addfun;调用子程序调用子程序 MOV r0,#0 x18 LDR r1,=0 x20026 SWI 0 x123456;通过软件中断指令返回通过软件中断指令返回 addfun ADD r0,r0,r1;子程序代码子程序代码 ADD r0,r0,r2 ADD r0,r0,r3 MOV pc,lr;从子程序返回从子程序返回 END;标记文件结束标记文

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论