精华内容
下载资源
问答
  • debug 指令

    千次阅读 2016-07-19 14:41:27
    它可以用于逐指令执行某个程序以验证程序运行的正确性,也可以追踪执行过程、比较一个指令执行前后的值以及比较与移动内存数据的范围,读写文件与磁盘扇区。  它的功能包括以下几个方面。  1,直接输入,更改...
      DEBUG命令参数详解
      DEBUG是一个DOS实用程序,是供程序员使用的程序调试工具,可以用它检查内存中任何地方的字节以及修改任何地方的字节。它可以用于逐指令执行某个程序以验证程序运行的正确性,也可以追踪执行过程、比较一个指令执行前后的值以及比较与移动内存中数据的范围,读写文件与磁盘扇区。
      它的功能包括以下几个方面。
      1,直接输入,更改,跟踪,运行汇编语言源程序;
      2,观察操作系统的内容;
      3,查看ROM BIOS的内容;
      4,观察更改RAM内部的设置值;
      5,以扇区或文件的方式读写软盘数据
      DEBUG把所有数据都作为字节序列处理。因此它可以读任何类型的文件。DEBUG可以识别两种数据: 十六进制数据和ASCⅡ码字符。它的显示格式是各个字节的十六进制值以及值在32与126之间的字节的相应ASCⅡ码字符。
      在DEBUG中输入数据有两种方法: 提示方法和非提示方法。在用提示方法时,用户可以输入要求输入数据的命令,后跟数据所要输入的地址。然后用户就可以看到该地之中已有内容及一个冒号提示符。此时用户可以在提示符下输入一个新的值或者按下回车键或CTRL+C回到短横(-)提示符。在运用非提示方法时,用户可以输入要输入数据的内存地址以及要输入的字节。但与使用字处理程序或正文编辑程序时不一样,在使用DEBUG时,用户不能直接移动光标到一入口点输入或修改数据,而要一次输入一个或几个字节。
      在使用DEBUG时可以只涉及内存中的数据,从而一般都要指定所要处理的内存地址,地址的输入格式是: [段地址]: [位移]。如果没有输入地址,DEBUG将假定为当前内存段,从位于地址100H的字节开始。前100H字节保留给程序段前缀使用,这一专用区域用于建立DOS与程序之间的联系。DEBUG总是用四位十六进制数表示地址。用两位数表示十六进制数据。
      讲到这里大家应该对DEBUG有了初步的了解,但是光知道这些可不够,接下来我来讲讲DEBUG的命令格式和命令。当输入
      DEBUG
      调用了DEBUG程序,就会出现一个短横提示符,用户就可以在这一短横后输入DEBUG程序的命令。有些DEBUG命令会显示一个内存地址并产生一个作为提示符的冒号。在这些提示符后,用户可以输入一个新值以改变所显示位置原来的值。如果用户不输入一个新值而是按下回车或CTRL+C,那么原来的值不会改变。
      一般用不着把地址和命令名字分开。例如,用转储命令D察看100号地址的数据,那么这个命令可以用以下任一种形势输入: 
      D100
      D 100
      D。100
      D,100
      如果输入的命令中出现了错误,DEBUG将在下一行对着错误的位置标记出来,例如: 
      -s100 d 12
      ^Error
      DEBUG的命令及功能如下: 
      * A[地址] 汇编命令
      功能: 将指令直接汇编成机器码输入到内存中。
      说明: 用于小段程序的汇编及修改目标程序,所有输入的数字均采用十六进制, 用户装入内存的汇编语句是连续存放的,若没有指定地址,并且前面没有使用汇编命令,改语句被汇编到 CS:0100区域。
      例A:>DEBUG
      -a 0100
      08F1:0100 MOV AH,09
      08F1:0102 MOV DX,109
      08F1:0105 INT 21H;
      08F1:0107 INT 21H
      08F1:0109 db 'May I help you $'
      08F1:0115←离开a状态
      -g ←运行
      May I help you 运行结果
      Program terminated normally表示运行正常
      * C[源地址范围][目的地址]比较命令
      功能: 比较两内存区域中的内容是否相同,若不同则显示其地址和内容。
      如:C4000:0 3F 100
      就是用来比较4000;0000-4000:003F与DS:0100-DS:013F之间的内容:其显示格式如下:
      内存地址1内含值1内含值2内存地址2
      例:比较4000:0 3F 100内容的差异
      -C4000:0 3F 100
      4000:0000 64 43 08F1:0100
      4000:0001 3E 69 08F1:0101 显示内容的差异处
      4000:0002 78 FF 08F1:0102
      …………………………………… 
      如果要比较的范围在DS内,则段地址不必指出:
      如:-C 0 4 100;比较DS:0---DS:4与DS:100---DS:104
      C命令的另一种格式如下:C地址1 L 长度 地址2
      如:-C000:0 L4 0;由0000:0与与DS:0开始比较它同-C0000:0 3 0命令相等,显示结果如下:
      0000:0000 8A C0 08F1:0000
      0000:0001 10 20 08F1:0001 它们都比较4个字节
      0000:0002 1C 00 08F1:0002
      0000:0003 49 7F 08F1:0003
      * D[地址] 或D[起始地址][目的地址] 转储命令
      功能: 以内存映象方式显示内存中的信息。
      说明: 转储用左右两部分显示内存映象内容,左边以十六进制,右边以ASCII字符显示,所有不可打印字符用句号(。)表示。每行显示16个字节的内容,在第八和第九个字节之间有一个连字符 - 此命令隐含的段地址为DS的值。若未指定起始地址,则D命令从显示的最后一个单元的下一个单元开始显示,若以前没有使用给D命令, 则从初使化的段寄存器的内容,加上地址偏移量 0100H 开始显示。
      例:-d10,4f即为显示DS:4f的内容在D命令中如不指出段地址,则其默认为DS段。
      如指明段地址,则从指明的段地址列出指定的范围
      如:-dfff:00:0f
      我们也可以指定长度来列出所需要内存内容
      如:-d 100 L20即为显示由DS:100-DS:11F的内容,共20H个字节:
      * E[地址] [字节串) 修改内存命令
      功能: 从指定的地址开始修改内存值。
      格式:E起始地址[数据行]
      (1)用给定内容代替指定范围的单元内容
      -E地址 内容表
      例:-E100 41 42 43 44 48 47 46 45
      -D 100,L08
      08F1:0100 41 42 43 44 48 47 46 45 ABCDHGFE…
      (2)逐个内存内容
      例:-E 100:
      08F1:0100 76 42 :42是操作员键入
      此命令是将原100号内存内容76修改为42,用D命令可察看。
      * F[地址范围] [字节或字节串] 填写命令
      功能: 将要填写的字节或字节串填入由地址范围指定的存储器中。
      例:-f100 120 61 62 63 64
      -d100 11f
      08F1:0100 61 62 63 64 61 62 63 64 -61 62 63 64 61 62 63 abcd abcd abcd abcd 
      08F1:0110 13 67 98 E3 C8 2E B3 B6 -03 21 AC 19 3121 4E 96 g……1…
      如果数据行超出指定的范围,则填不下的数值会被忽略。
      例:-f 100 107 41 43 43 44 45 46 47 48 49 4A 4B 4C 4D -d 100,lof
      08F1:0100 41 42 43 44 45 46 47 64 -61 62 63 64 ABCDEFGdabcdabcd
      由上例可看出,超出范围的数据被忽略
      另外,F和E命令都可填入字符串:
      如:-F 100 105 "MSDOS"
      -d 100 l0f
      08F1:0100 4D 53 44 4F 53 46 47 64 -61 62 63 64 MS DOS FGabcd abcd 
      * G[=起始地址] [[断点]……] 执行命令
      功能: 执行正在调试的程序,当达到断点时停止执行, 并且显示寄存器标志和下一条要执行的命令。
      说明: 如果没有指定起始地址,那么当前指令地址由CS,IP寄存器的内容来决定,如果用户指定起始地址就从指定的起始地址开始执行。如果指定断点,当指令到达指令地址时停止执行,并显示各寄存器标志位的内容和下一条要执行的命令,最多允许用户设定10个断点。
      例:A:\>debug tan.exe
      -u:反编译成汇编语言程码
      …………
      . .
      -g 100 指定中断点
      Program terminated normally:
      另外:我们在DEBUG下可运行一个文件.EXE
      如:A:\>debug tan.exe
      -g
      即可开始运行此程序,和在DOS下完全一样:
      * H[数值][数值] 十六进制算术运算命令
      功能: 分别显示两个十六进制数相加的和以及第一个数减去第二个数的差。
      说明: 替用户完成简单的十六进制数的运算。
      例:-h4538 5623
      9B5B EF15
      * I[端口地址]
      功能: 从指定的端口输入并显示(用十六进制)的一个字节。
      例:-i70
      F9;显示70端口的内容为F9
      I命令可由80X86的64K个端口取数据
      * L[地址][盘号:][逻辑扇区号][扇区数]
      功能: 将一个文件或盘的绝对扇区装入存储器。
      说明: 单个L命令能够装入的最大扇区数是 80H,其中盘号 0,1,2,3……分别代表 A,B,C,……出现读盘错,显示错误信息。
      (1)格式1.L装入地址 驱动器名 起始扇区/扇区数
      这种方式可把磁盘上指定扇区范围的内容装入到存储器从指定地址开始的区域中,在此外扇区编号引用逻辑/扇区的方式。
      例:-L 100 0 01,将A驱的0扇区装至CS:100上
      -d 100 10f
      08F1:0100 EB 3C 90 3C 53 44 4F 53 -36 2E 32 32 02 01 01 00.L,MSDOS 6.22……
      (2)格式2:L装入地址
      这种方式可把指定文件装入内存,装入的文件可在进入DEBUG时指定亦可用N命令建立,格式为-n文件名:
      例1 DEBUG tan.pas
      -L 100
      例2 DEBUG 
      -n tan.pas
      -L 100
      须知:L命令只能读取逻辑扇区,不能读取硬盘分区表
      L命令中所用的磁盘代码A=00,B=01,C=02……
      * M[地址范围][起始地址] 数据传送命令
      功能: 把地址范围内的存储器单元的内容移到起始地址的指定地址中
      说明: 传送期间,源区和目标区可以部分重叠;传送后源区域数据保持不变。
      例:-e100 41 42 43 44 45
      -d100 10f
      08F1:0100 41 42 43 44 45 62 62 63 64 -61 62 63 64 61 62 63 64 ABCDEBCDABCDABCD
      -M 100 104 110
      -d110 L1F
      08F1:0100 41 42 43 45 0A 21 19-20 01 01 20 07 96 87 9F ABCDE……
      * N[盘号: ] [路径] [文件名] [扩展名]
      功能: 定义操作文件名。
      说明: 可同时定义两个操作文件,并将形成的文件控制块相应的设置在内存 CS:5C和CS:6C上,供以后的L和W命令操作之用。我们在运行程序侦错时,在启动DEBUG时在其后加文件程序名以及该程序的参数或运行文件,但当我们侦错一段后,可能装入其它文件来测试,这时我们可利用N命令来设置而无需退出DEBUG。
      例:A:\>DEBUG tan.exe
      -n youg.pas
      当程序侦错一段时间后,若要把tan.exe装入tan1.pas则-ntanl.pas
      * O[端口地址][数据] 输出命令
      功能: 发送字节到指定的输出端口。
      例:当我们遇到开机要求输入口令时,可用如下方式取消
      -O 70 10
      _O 71 00
      * P[=地址] [数据] 进程命令
      功能: 将一个子程序调用指令,循环指令,中断指令或一个重复字符串指令,停止在下一条指令上。
      说明: 在执行一条子程序调用指令,循环指令,中断指令或一个重复字符串指令时,发出P命令去执行有关指令,并且返回到要执行的下一条指令。 P命令和T一样选用来跟踪程序运行过程用的命令,我们可以在P命令中指定程序运行的起始地址,指令个数,如未指定则CS:IP所指定程序的地址开始一次运行一条令。
      P与T命令的差别在于P命令把CALL/INT当成一条指令来执行,简化了跟踪过程,P命令只运行RAM内存的命令,而T命令则可运行RAM和ROM里的程序。
      * Q 退出命令。
      * R[寄存器] 寄存器命令
      功能: 一,显示单个寄存器的内容,并提供修改功能。 二,显示所有寄存器内容, 再加上字母标志位状态以及要执行的下一条指令。 三,显示8个标志位状态, 并提供修改功能。若不想改变则回车即可。
      例:-r bx
      bx 0050
      :51
      -r
      AX=0000 BX=0051 CX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
      DS=0003 ES=0CD3 SS=0CD3 IP=0100 NV UP EI PL NZ NA OP NC 
      0CD3:0100 0F DB OF
      若想改变标志寄存器,用-RF回车,则DEBUG会将标志内容显示出来,若想改变任一标志,只要输入该标志的名称即可。
      标志名称 设置 未设置 标志名称 设置 未设置
      滋出 OV(未溢出) NV(未溢出) 零位 ZR NZ(不等于零)
      方向 DN(减少) UP(增加) 辅助进位 AC NA(无进位) 
      中断 EI(许可) DI(禁止) 奇偶标志 PE(偶) PO(奇) 
      符号 NG(负) PL(正) 进位 CY NC(清除进位) 
      例:-Rf 
      NV UP EI PL NZ NA PO NC :-OV DI← 输入值 
      * S[地址范围] [字符串]
      功能: 在指定的地址范围内查找给定的字符串。
      说明: 用来指定在地址范围内查找一个字符串,若找到则显示其地址, 否则直接显示DEBUG提示符。隐含地址为DS段值。
      在此内存可以用(起始地址)(终止地址)或(起始地址)L(长度)的方式来表示,而字符串与数据行则可混合使用:如:02.76"BC"。 
      例:-d100 lof 
      08F1:0100 OF 2A 41 43 0B 31 42 96 -FF F0 B9 8A F3 00 B1.. AC,1B... 
      -S 100 lof"AC" 
      08F1:0102← 表示找到,由0102开始, 
      * T[=地址][指令条数] 跟踪命令
      功能: 逐条跟踪程序的执行,每条指令执行后都将显示各寄存器的内容。
      说明: 通常采用跟踪一条指令,但用户也可以用指令条数设定一次跟踪多条指令, 每执行一条指令之后,显示所有寄存器的内容和标志状态。
      逐条指令跟踪 -T[=起始地址] 
      从指定地址起执行一条指令后停下来,显示所有寄存器内容及标志位的值,如来指定地址则从当前CS:IP开始执行。 
      A:\>DEBUG 
      -A 
      08F1:0100 MOV DL,03H 
      08F1:0102 MOV AH,02H 
      08F1:0104 INT 21H 
      08F1:0106 INT 20H 
      08F1:0108 
      -T 
      AX=0000 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000 
      DS=08F1 ES=08F1 SS=08F1 CS=08F1 1P=0105 NV UP EI PL NZ PO CY 
      09F1:0102 B402 MOV AH,02 
      若指定起始地址,则T命令会从指定的地址开始跟踪,我们还可以指定跟踪一次所运行指令的个数,用Ctrl+S暂停屏幕的显示,以便观察。 
      -t=100 10;由CS:100开始跟踪10条指令 
      * U[起始地址]或者[地址范围]
      功能: 将内存中的内容转换为汇编语句。
      说明: 反汇编的字节数取决与用户的系统显示形式,以及在U命令中使用的可选项。
      (1)从指定地址开始编译,反汇编32个字节 
      -U[地址]←从CS:100开始,其反汇编32个字节 
      如果地址被省略,则从一个U命令的最后一条指令的下一条单元开始汇编32个字节。 
      (2)对指定的存贮范围进行反汇编 
      -u起始地址 终止地址(L 长度) 
      例:-U 100 109 
      08F1:0100 CD20 INT 20 
      08F1:0102 FF9F009A CALL FA12[BX+9A00] 
      08F1:0106 F0 LOCK 
      08F1:0107 FE1D CALL FAR [DI] 
      08F1:0109 F0 LOCK 
      说明:如指定了范围则整个范围全都会被反编译 
      * W[地址] [盘符:] [起始扇区] [扇区数] 写盘命令
      功能: 将修改过的数据写到磁盘上。
      说明: 可以将指定内存地址开始的数据写在磁盘上,可以在没有指定参数, 或指定地址参数的情况下,将调试文件写在磁盘上。
      在运行W时需设置文件的大小CX或BX寄存器 
      (1)把数据写入磁盘的指定扇区 
      -W 起始地址 驱动器名 起始扇区 扇区数 
      (2)把数据写入指定文件中 
      -W 起始地址 
      例A:\>DEBUG 
      -A 
      08f1:0100.... 
      . 写入程序段 
      08F1: 012A.... 
      -R CX 
      CX:0000 
      :2A←写入字节数,即为程序结尾地址减起始地址 
      -n tan.com←设置文件名,需后缀为com 
      -w 
      Wring 002A bytes 
      -q 
      A:\>TAN←即可执行此程序 
      * "XD"命令:释放EMS内存 
      例:-XD 0001 
      handle 0001 deallocatel←释放了 
      利用XD释放后可再分配 
      *"XM"命令;把扩充内存上的内存页区映射到主内存区 
      格式:XM RAM 长页码 主内存页码句柄 
      例:-XM3 2 0001←把0001号句柄的第3号逻辑页区映射到2号真实页区 
      Logical page 03 mapped to physical page 02 
      *"XS"命令:显示当前EMS使用情况。 
      格式 -XS 
      实用debug命令集锦 
      1. 非物理0磁道坏软盘的修复 
      此种损坏从软盘盘面上来看并没有明显的划伤和霉变。一般可以恢复其数据,也可是软盘重新在利用。 
      处理方法如下: 
      ⅰ. 进入debug 
      ⅱ. 取一张引导区没有损坏的好磁盘,插入软驱 
      -l 100 0 0 1 
      ⅲ. 插入损坏的磁盘到软驱 
      -w 100 0 0 1 
      -q 
      注意:好盘与坏盘容量必须相同 
      2. 物理0磁道坏软盘中的数据读取 
      对于0磁道损坏的磁盘,一般来说是应该抛弃了,当你也不妨试一试已下方法: 
      磁化处理:用较强的磁铁在靠近坏磁盘的表面处反复移动,切不可碰到磁盘介质,以免划伤表面,然后在试试格式化。 
      软盘换面:小心的将磁盘打开,坚磁片与金属芯片分开,方面后再按原来的方法粘在一起即可,在重新格式化。 
      diskfix:对于diskfix想必大家都用的比较多了,里面的磁盘修复功能很好用的。能修复大多数磁盘表面错误。 
      3. 硬盘启动失败处理 
      在正常机上格式化一张软盘,无其它数据 
      进入debug 
      -l 0 2 0 1 
      -w 0 0 0 1 
      -q 
      用系统盘启动故障机后 
      在进入debug 
      -l 0 0 0 1 
      -w 0 2 0 1 
      -q 
      4. 软盘不能正确读取解决方法 
      如果使用软盘时出现如下提示 
      general failure error reading drive a 
      可以用以下方法解决: 
      将一张好盘插入软驱 
      进入debug 
      -a 100 
      ****:*100 mov al,0 
      ****:**** mov cx,1 
      ****:**** mov dx,0 
      ****:**** mov bx,1000 
      ****:**** int 25 
      ****:**** int 20 
      回车 
      -g=0100 
      插入坏盘并进入debug 
      -a 200 
      ****:*100 mov al,1 
      ****:**** mov cx,1 
      ****:**** mov dx,0 
      ****:**** mov bx,1000 
      ****:**** int 26 
      ****:**** int 20 
      回车 
      -g=200 
      5. cmos 数据的保存,恢复 
      cmosram的’地址口’的口地址为 70h ’数据口’的口地址为 71h 读取时只需将读的cmosram的地址送到70h,随后就可以从71 h中得到所需数据。 
      (1)读取cmos数据 进入debug 
      -a 100 
      ****:*100 mov bx,1000 
      ****:**** mov cx,0040 
      ****:**** mov ax,0000 
      ****:0109 mov dx,cx 
      ****:**** mov cx,0005 
      ****:010e loop 010e 
      ****:**** out 70,al 
      ****:**** mov cx,0005 
      ****:0115 loop 0115 
      ****:**** in al,71 
      ****:**** mov 【bx】,al 
      ****:**** cmp ah,0e 
      ****:**** jb 0123 
      ****:**** add ah,80 
      ****:0123 inc ah 
      ****:**** inc bx 
      ****:**** mov cx,dx 
      ****:**** mov al,ah 
      ****:**** loop 0109 
      ****:**** mov ah,3c 
      ****:**** mov dx,0150 
      ****:**** mov cx,0020 
      ****:**** int 21 
      ****:**** mov bx,ax 
      ****:**** mov dx,1000 
      ****:**** mov cx,0040 
      ****:**** mov ah,40 
      ****:**** int 21 
      ****:**** mov ah,4c 
      ****:**** int 21 
      -a 150 
      ****:0150 db "cmos.dat",0 
      ****:0159 
      -r cx 
      cx 0000 
      :60 
      -n save cmos.com 
      -w 
      -q 
      -w 100 2 0 1 
      -q 
      (2)恢复cmos数据 进入debug 
      -a 100 
      ****:*100 mov cx,0150 
      ****:**** mov ah,3d 
      ****:**** mov al,00 
      ****:**** int 21 
      ****:**** mov dx,1000 
      ****:**** mov bx,ax 
      ****:**** mov cx,0040 
      ****:**** mov ah,3f 
      ****:**** int 21 
      ****:**** mov ax,0000 
      ****:**** mov bx,dx 
      ****:**** mov dx,cx 
      ****:**** mov cx,0005 
      ****:**** loop 011f 
      ****:**** mov al,ah 
      ****:**** out 70,al 
      ****:**** mov cx,0005 
      ****:**** loop 0128 
      ****:**** mov al,【bx】 
      ****:**** out 71,al 
      ****:**** jb 0136 
      ****:**** add ah,80 
      ****:**** inc ah 
      ****:**** inc bx 
      ****:**** mov cx,dx 
      ****:**** loop 011a 
      ****:**** mov ax,0040 
      ****:**** mov ds,ax 
      ****:**** mov ax,1234 
      ****:**** mov 【0072】,ax 
      ****:**** jmp ffff:0000 
      -a 150 
      ****:0150 db "cmos.dat",0 
      ****:0159 
      -r cx 
      cx 0000 
      :60 
      -n write cmos.com 
      -w 
      -q 
      6. dos 引导扇区数据的保存与恢复 
      dos引导程序是被读到内存0000:7c00初开始执行的 
      获得正常的引导程序 
      进入debug 
      -l 100 2 0 1 
      -n a:dosboot.com 
      -r cx 
      :200 
      -w 
      -q 
      装入引导程序 
      进入debug 
      -n a:dosboot.com 
      -l 
      -r cx 
      :200 
      -w 100 2 0 1 
      -q 
      7. 硬盘主引导扇区数据的保存与恢复 
      硬盘工作正常时读取主引导扇区信息 
      注意:当分区改变时不能用此数据恢复 
      保存主引导扇区数据进入debug 
      -a 100 
      mov ax,0201 
      mov bx,0110 
      mov cx,0001 
      mov dx,0080 
      int 13 
      int 3 
      -g=100 
      -e 102 3 
      -e 10e c3 
      -r bx 
      bx 0110 
      :0 
      -r cx 
      cx 0001 
      :210 
      -n a:rboot.com 
      -w 
      -q 
      恢复主引导扇区数据:只需运行a盘的rboot.com 
      8. 硬盘非分配表备份与恢复 
      计算机运行正常时分配表备份 
      进入debug 
      -l 100 2 0 1 
      -n a:dbrup.dat 
      -r cx 
      :200 
      -w 
      恢复 
      进入debug 
      -n a:dbrup.dat 
      -l 
      -w 100 2 0 1 
      -q 
      9. 硬盘保护卡内幕 
      对于经常在外边上机的人来说,计算机维护人员一旦设置硬盘保护卡,自己作一些事来特别麻烦,想不想屏蔽掉硬盘保护卡,用以下方法或许可以借鉴: 
      进入debug 
      -a 100 
      mov ah,0 
      mov dl,0 
      int 13 
      -t 
      一直按t知道找到 cs=f000 记下此时 ds 的值 如:1234 
      -e e0:4c 
      34 12 00 f0 
      -q 
      10. 用debug作硬盘低级格式化 
      硬盘低级格式化一般用dm,但debug也可以低级格式化硬盘 
      进入debug 
      -a 100 
      mov ax,500 
      mov bx,180 
      mov cx,0 
      mov dx,80 
      int 13 
      int 3 
      -e 180 0 0 0 2 
      -q 
      11. 冷启动与热启动 
      用debug实现系统冷启动与热启动程序 
      冷启动: 
      -a 100 
      jmp ffff:0 
      int 20 
      -n a:reset.com 
      -r cx 
      :0007 
      -w 
      -q 
      热启动: 
      -a 100 
      mov ax,0040 
      mov ds,ax 
      mov ax,1234 
      mov si,0072 
      mov (si),ax 
      jmp ffff:0 
      -n a:rset.com 
      -r cx 
      :0014 
      -w 
      -q 
      12. dos内部命令加密 
      如加密 dir 
      用pctools或diskedit找 c:下的command.com文件 
      编辑该文件 
      pctools中:f-----f1 然后找所有03 44 49 52 找到后按f5修改成你所要的值 如: foo 以后只有输入foo 才能列出文件或文件目录。 
      diskfix 有良好的界面,操作起来和pctools差不多。 
      注意:如果不能修改,则解开文件的锁定 luck 
      其它命令代码 
      type 04 54 59 50 45 
      cd 02 43 44 
      del 03 44 45 44 
      copy 04 43 49 50 59 
    展开全文
  • mips指令

    千次阅读 2015-06-24 13:00:40
    16位的目标地址意味着,指令的跳转或子函数的位置必须在64K以内(上下32K); 3、所有的动作原理上要求必须在1个时钟周期内完成,一个动作一个阶段; 4、有32个通用寄存器,每个寄存器32位(对32位机)或64位(对...
    
    

    MIPS指令特点:
    1、所有指令都是32位编码;
    2、有些指令有26位供目标地址编码;有些则只有16位。因此要想加载任何一个32位值,就得用两个加载指令。16位的目标地址意味着,指令的跳转或子函数的位置必须在64K以内(上下32K);
    3、所有的动作原理上要求必须在1个时钟周期内完成,一个动作一个阶段;
    4、有32个通用寄存器,每个寄存器32位(对32位机)或64位(对64位机);
    5、本身没有任何帮助运算判断的标志寄存器,要实现相应的功能时,是通过测试两个寄存器是否相等来完成的;
    6、所有的运算都是基于32位的,没有对字节和对半字的运算(MIPS里,字定义为32位,半字定义为16位);
    7、没有单独的栈指令,所有对栈的操作都是统一的内存访问方式。因为push和pop指令实际上是一个复合操作,包含对内存的写入和对栈指针的移动;

    8、由于MIPS固定指令长度,所以造成其编译后的二进制文件和内存占用空间比x86的要大,(x86平均指令长度只有3个字节多一点,而MIPS是4个字节);

    9、寻址方式:只有一种内存寻址方式。就是基地址加一个16位的地址偏移;

    10、内存中的数据访问必须严格对齐(至少4字节对齐)

    11、跳转指令只有26位目标地址,再加上2位的对齐位,可寻址28位的空间,即256M。意思即是说,在一个C程序内,goto语句只能跳转到它之前的128M和之后的128M这个地址空间之内

    12、条件分支指令只有16位跳转地址,加上2位的对齐位,共18位寻址空间,即256K。意思即是说,在一个C程序内,if语句只能跳转到它之前的128K和之后的128K这个地址空间之内;

    13、MIPS默认不把子函数的返回地址(就是调用函数的受害指令地址)存放到栈中,而是存放到$31寄存器中;这对那些叶子函数有利。如果遇到嵌套的函数的话,有另外的机制处理;

    14、流水线效应。由于采用了高度的流水线,结果产生了一些对程序员来说可见的效应,需要注意。最重要的两个效应就是分支延迟效应和载入延迟效应。
        a 任何一个分支跳转语句后面的那条语句叫做分支延迟槽。实际上在程序执行到分支语句时,当他刚把要跳转到的地址填充好(到代码计数器里),还没完成本条指令,分支语句后面的那个指令就执行了。这是因为流水线效应,几条指令同时在执行,只是处于不同的阶段。具体看书上说提前半条指令执行,没看懂。分支延迟槽常用被利用起来完成一些参数初始化等相关工作,而不是被浪费了。
        b 载入延迟是这样的。当执行一条从内存中载入数据的命令时,是先载入到高速缓冲中,然后再取到寄存器中,这个过程相对来说是比较慢的。在这个过程完成之前,可能已经有几条在流水线上的指令被执行了。这几条在载入指令后被执行的指令就被称作载入延迟槽。现在就有一个问题,如果后面这几条指令要用到载入指令所载入的那个数据怎么办?一个通用的办法是,把内部锁加在数据载入过程上,这样,当后面的指令要用这条指令时,就只有先停止运行(在ALU阶段),等这条数据载入指令完成了后再开始运行。

    *MIPS指令的五级流水线:每条指令都包含五个执行阶段。
    第一阶段:从指令缓冲区中取指令。占一个时钟周期;
    第二阶段:从指令中的源寄存器域(可能有两个)的值(为一个数字,指定$0~$31中的某一个)所代表的寄存器中读出数据。占半个时钟周期;
    第三阶段:在一个时钟周期内做一次算术或逻辑运算。占一个时钟周期;
    第四阶段:指令从数据缓冲中读取内存变量的阶段。从平均来讲,大约有3/4的指令在这个阶段没做什么事情,但它是指令有序性的保证(为什么是保证,我还没看清楚?)。占一个时钟周期;
    第五阶段:存储计算结果到缓冲或内存的阶段。占半个时钟周期;
    => 所以一条指令要占用四个时钟周期;

    15、MIPS的虚拟地址内存映射空间
    a  0x0000 0000 ~ 0x7fff ffff
    用户级空间,2GB,要经MMU(TLB)地址翻译。kuseg。可以控制要不要经过缓冲。

    b  0x8000 0000 ~ 0x9fff ffff
    kseg0. 这块区域为操作系统内核所占的区域,共512M。使用时,不经过地址翻译,将最高位去掉就线性映射到内存的低512M(不足的就裁剪掉顶部)。但要经过缓冲区过渡。

    c  0xa000 0000 ~ 0xbfff ffff
    kseg1. 这块区域为系统初始化所占区域,共512M。使用时,不经过地址翻译,也不经过缓冲区。将最高3位去掉就线性映射到内存的低512M(不足的就裁剪掉顶部)。

    d  0xc000 0000 ~ 0xffff ffff
    kseg2. 这块区域也为内核级区域。要经过地址翻译。可以控制要不要经过缓冲。

    16、MIPS的协处理器
    CP0:这是MIPS芯片的配置单元。必不可少,虽然叫做协处理器,但是通常都是做在一块芯片上。绝大部分MIPS功能的配置,缓冲的控制,异常/中断的控制,内存管理的控制都在这里面。所以是一个完整的系统所必不可少的;

    17、 MIPS的高速缓冲
    MIPS一般有两到三级缓冲,其中第一级缓冲数据和指令分开存储。这样的好处是指令和数据可以同时存取,提高效率。但缺点是提高了复杂度。第二级缓冲和第三级缓冲(如果有的话)就不再分开存放啦。

    缓冲的单元叫做缓冲行(cache line)。每一行中,有一个tag,然后后面接的是一些标志位和一些数据。缓冲行按顺序线性排列起来,就组成了整个缓冲。

    cache line的索引和存取有一套完整的机制。
    18、MIPS的异常机制
    精确异常的概念:在运行流程中没有任何多余效应的异常。即当异常发生时,在受害指令之前的指令被完全执行,而受害指令及后面的指令还没开始执行(注:说受害指令及后面的指令还没做任何事情是不对的,实际上受害指令是处于其指令周期的第三阶段刚完成,即ALU阶段刚完成)。精确异常有有助于保证软件设计上不受硬件实现的影响。

    CP0中的EPC寄存器用于指向异常发生时指令跳转前的执行位置,一般是受害指令地址。当异常时,是返回这个地址继续执行。但如果受害指令在分支延迟槽中,则会硬件自动处理使EPC往回指一条指令,即分支指令。在重新执行分支指令时,分支延迟槽中的指令会被再执行一次。

    精确异常的实现对流水线的流畅性是有一定的影响的,如果异常太多,系统执行效率就会受到影响。

    *异常又分常规异常和中断两类。常规异常一般为软件的异常,而中断一般为硬件异常,中断可以是芯片内部,也可以是芯片外部触发产生。

    异常发生时,跳转前最后被执行的指令是其MEM阶段刚好被执行完的那条指令。受害指令是其ALU阶段刚好执行完的那条指令。

    异常发生时,会跳到异常向量入口中去执行。MIPS的异常向量有点特殊,它一般只个2个或几个中断向量入口,一个入口给一般的异常使用,一个入口给 TLB miss异常使用(这样的话,可以省下计算异常类型的时间。在这种机制帮助下,系统只用13个时钟周期就可以把TLB重填好)。

    CP0寄存器中有个模式位,SR(BEV),只要设置了,就会把异常入口点转移到非缓冲内存地址空间中(kseg1)。

    MIPS系统把重启看作一个不可回归的异常来处理。
    冷启动:CPU硬件完全被重新配置,软件重新加载;
    热启动:软件完全重新初始化;

    MIPS对异常的处理的哲学是给异常分配一些类型,然后由软件给它们定义一些优先级,然后由同一个入口进入异常分配程序,在分配程序中根据类型及优先级确定该执行哪个对应的函数。这种机制对两个或几个异常同时出现的情况也是适合的。

    下面是当异常发生时MIPS CPU所做的事情:
    a 设置EPC指向回归的位置;
    b 设置SR(EXL)强迫CPU进入kernel态,并禁止所有中断响应。
    c 设置Cause寄存器,以使软件可以得到异常的类型信息;还有其它一些寄存器在某些异常时会被设置;
    d CPU开始从异常入口取指令,然后以后的所有事情都交由软件处理了。

    k0和k1寄存器用于保存异常处理函数的地址。
    异常处理函数执行完成后,会回到异常分配函数那去,在异常分配函数里,有一个eret指令,用于回归原来被中断的程序继续执行;eret指令会原子性地把中断响应打开(置SR(EXL)),并把状态级由kernel转到user级,并返回原地址继续执行。

    19、中断
    MIPS CPU有8个独立的中断位(在Cause寄存器中),其中,6个为外部中断,2个为内部中断(可由软件访问)。一般来说,片上的时钟计数/定时器,会连接到一个硬件位上去。

    SR(IE)位控制全局中断响应,为0的话,就禁止所有中断;
    SR(EXL)和SR(ERL)位(任何一个)如果置1的话,会禁止中断;
    SR(IM)有8位,对应8个中断源,要产生中断,还得把这8位中相应的位置1才行;

    中断处理程序也是用通用异常入口。但有些新的CPU有变化。

    *在软件中实现中断优先级的方案
    a 给各种中断定优先级;
    b CPU在运行时总是处于某个优先级(即定义一个全局变量);
    c 中断发生时,只有等于高于CPU优先级的中断优先级才能执行;(如果CPU优先级处于最低,那么所有的中断都可以执行);
    d 同时有多个中断发生时,优先执行优先级最高的那个中断程序;

    20、大小端问题
    硬件上也有大端小端问题,比如串口通讯,一个字节一个字节的发,首先是低位先发出去。
    还有显卡的显示,比如显示黑白图像,在屏幕上一个点对应显存中的一位,这时,这个位对应关系就是屏幕右上角那个点对应显存第一个字节的7号位,即最高位。第一排第8位点对应第一个字节的0号位。

    21、MIPS上的Linux运行情况

    用户态和核心态:在用户态,不能随意访问内核代码和数据存放区,只能访问用户态空间和内核允许访问(通过某种机制)的内核页面。也不能执行CP0相关的指令。用户态要执行内核的某些服务,就得用系统调用(system_call),在系统调用的最后,是一个eret指令。

    任何时候Linux都有至少一个线程在跑,Linux一般不禁止中断。发生中断时,其环境是从被中断线程借来的。

    中断服务程序(ISR)应该短小。

    MIPS Linux系统上半地址空间只能用内核特权级访问。内核不通过TLB地址翻译。

    所有线程都共用相同的内核地址空间,但只有同一组线程才用同一个用户地址空间(指向同一个mm_struct结构)。

    如果物理内存高于512M,那么不能用kseg0和kseg1来映射高于512M的内存部分。只能用kseg2来映射。kseg2要经过TLB。

    从某个方面说,内核就是一组供异常处理函数调用的子程序。内核中,线程调度器就是这样一个小的子程序。由各个线程(异常处理程序也可以算作一个特殊的线程,换他书上的说法)调用。

    MIPS Linux有异常模式,而x86上没有这个概念。

    异常要小心操作。不是仅用软件锁就能解决的。

    21、原子操作
    MIPS为支持操作系统的原子操作,特地加了一组指令 ll/sc。它们这样来使用:

    先写一句
    atomic_block:
    LL XX1, XXX2
    ….
    sc XX1, XXX2
    beq XX1, zero, automic_block
    ….

    在ll/sc中间写上你要执行的代码体,这样就能保证写入的代码体是原子执行的(不会被抢占的)。

    其实,LL/sc两语句自身并不保证原子执行,但他耍了个花招:
    用一个临时寄存器XX1,执行LL后,把XXX2中的值载入XX1中,然后会在CPU内部置一个标志位,我们不可见,并保存XXX2的地址,CPU会监视它。在中间的代码体执行的过程中,如果发现XXX2的内容变了(即是别的线程执行了,或是某个中断发生了),就自动把CPU内部那个标志位清0。执行sc 时,把XX1的内容(可能已经是新值了)存入XXX2中,并返回一个值存入XX1中,如果标志位还为1,那么这个返回的值就为1;如果标志位为0,那么这个返回值就为0。为1的话,就表明这对指令中间的代码是一次性执行完成的,而不是中间受到了某些中断,那么原子操作就成功了;为0的话,就表明原子操作没成功,执行后面beq指令时,就会跳转到ll指令重新执行,直到原子操作成功为止。

    所以,我们要注意,插在LL/sc指令中间的代码必须短小。

    据经验,一般原子操作的循环不会超过3次。

    22、系统调用 syscall
    系统调用也通过异常入口进入系统内核,选择8号异常代码处理函数进行处理,进入系统调用分配函数后,还要根据传进来的参数再一次分配到具体的功能函数上去。系统调用传递参数是在寄存器中进行的。

    系统调用号存放在v0中,参数存放在a0-a3。如果参数过多,会有另一套机制来处理。系统调用的返回值通常放在v0中。如果系统调用出错,则会在a3中返回一个错误号。

    23、异常入口点位于kseg0的底部,是硬件规定的。

    24、注意:地址空间的0x0000 0000是不能用的,从0开始的一个或多个页不会被映射。

    25、内存分页映射有以下优点:
    a 隐藏和保护数据;
    b 分配连续的地址给程序;
    c 扩展地址空间;
    d 按需求载入代码和数据(通过异常方式);
    e 便于重定位;
    f 代码和数据在线程中共享,便于交换数据;

    所有的线程是平等的,所有的线程都有自己的内存管理结构体;运行于同一地址空间的线程组,共享有大部分这种数据结构。在线程中,保存有本地址空间已经使用的页面的一个页表,用来记录每个已用的虚页与实际物理页的映射关系;

    26、ASID是与虚拟页高位配合使用。用于描述在TLB和Cache中的不同的线程,只有8位,所以最多只能同时运行256个线程。这个数字一般来说是够的。如果超过这个数目了,就要把Cache刷新了重新装入。所以,在这点上,与x86是不同的。

    27、MIPS Linux的内存驻留页表结构
    用的是两级页表,一个页表目录,一个页表,页表中的每一项是一个 EntryLo0-1。
    (这与x86方式类似)。而没有用MIPS原生设计的方案。

    28、TLB的refill过程-硬件部分
    a CPU先产生一个虚拟地址,要到这个地址所对应的物理地址上取数据(或指令)或写数据(或指令)。
    低13位被分开来。然后高19位成为VPN2,和当前线程的ASID(从EntryHi(ASID)取)一起配合与TLB表中的项进行比较。(在比较过程中,会受到PageMask和G标志位的影响)
    b 如果有匹配的项,就选择那个。虚拟地址中的第12位用于选取是用左边的物理地址项还是用右边的物理地址项。
    然后就会考察V和D标志位,V标志位表示本页是否有效,D表示本页是否已经脏了(被写过)。
    如果V=0,或D=1,就会引发翻译异常,BadVAddr会保存现在处理的这个虚拟地址,EntryHi会填入这个虚拟地址的高位,还有Context中的内容会被重填。
    然后就会考察C标志位,如果C=1,就会用缓冲作中转,如果C=0,就不使用缓冲。
    这几级考察都通过了之后,就正确地找到了那个对应的物理地址。
    c 如果没有匹配的项,就会触发一个TLB refill异常,然后后面就是软件的工作了;

    29、TLB的refill过程-软件部分
    a 计算这个虚拟地址是不是一个正确的虚拟地址,在内存页表中有没有与它对应的物理地址;如果没有,则调用地址错误处理函数;
    b 如果在内存页表中找到了对应的物理地址,就将其载入寄存器;
    c 如果TLB已经满了,就用random选取一个项丢弃;
    d 复制新的项进TLB。

    30、MIPS Linux中标志内存页已经脏了的方式与x86不同。它要耍个把戏:
    a 当一个可写的页第一次载入内存中时(从磁盘载入?载入的时候就分配一个物理页,同时就分配个对应的虚拟页,并在内存页表中添一个Entry),将其Entry的D标志位清0;
    b 然后,当后面有指令要写这个页时,就会触发一个异常(先载入TLB中判断),我们在这个异常处理函数中把内存页表项中的标志位D置1。这样后面的就可以写了。并且,由于这个异常把标志位改了,我们认为这个物理页是脏的了。
    c 至于TLB中已经有的那个Entry拷贝还要修改它的D标志位,这样这次写入操作才能继续入下进行。

    31、MIPS中的C语言参数传递机制?

    32、MIPS中的堆栈结构及在内存中的分布?


    指令长度和寄存器个数
    MIPS的所有指令都是32位的,指令格式简单。不像x86那样,x86的指令长度不是固定的,以80386为例,其指令长度可从1字节(例如PUSH)到17字节,这样的好处代码密度高,所以MIPS的二进制文件要比x86的大大约20%~30%。而定长指令和格式简单的好处是易于译码和更符合流水线操作,由于指令中指定的寄存器位置是固定的,使得译码过程和读指令的过程可以同时进行,即固定字段译码。
    32 个通用寄存器,寄存器数量跟编译器的的要求有关。寄存器分配在编译优化中是最重要的优化之一(也许是做重要的)。现在的寄存器分配算法都是基于图着色的技术。其基本思想是构造一个图,用以代表分配寄存器的各个方案,然后用此图来分配寄存器。粗略说来就是使用有限的颜色使图中相临的节点着以不同的颜色,图着色问题是个图大小的指数函数,有些启发式算法产生近乎线形时间运行的分配。全局分配中如果有16个通用寄存器用于整型变量,同时另有额外的寄存器用于浮点数,那么图着色会很好的工作。在寄存器数教少时候图着色并不能很好的工作。
       问: 既然不能少于16个,那为什么不用64个呢?
    答:使用64个或更多寄存器不但需要更大的指令空间来对寄存器编码,还会增加上下文切换的负担。除了那些很大不能感非常复杂的函数,32个寄存器就已足够保存经常使用的数据。使用更多的寄存器并不必要,同时计算机设计有个原则叫“越小越快”,但是也不是说使用31个寄存器会比32个性能更好,32个通用寄存器是流行的做法。
    指令格式
    所有MIPS指令长度相同,都是32位,但为了让指令的格式刚好合适,于是设计者做了一个折衷:所有指令定长,但是不同的指令有不同的格式。MIPS指令有三种格式:R格式,I格式,J格式。每种格式都由若干字段(filed)组成,图示如下:
    I型指令
          6    5     5     16
       ------|-----|-----|------------------|
       | op | rs | rt   | 立即数操作 |
           ------|-----|-----|------------------|
    加载/存储字节,半字,字,双字
    条件分支,跳转,跳转并链接寄存器
    R型指令
          6    5     5     5     5     6
       ------|-----|-----|-----|-----|--------|
       |op | rs   | rt   | rd |shamt|funct |
       ------|-----|-----|-----|-----|---------|
    寄存器-寄存器ALU操作
    读写专用寄存器
    J型指令
          6             26
       ------|------------------------------|
       |op   |  跳转地址          |
           ------|------------------------------|
    跳转,跳转并链接
    陷阱和从异常中返回

    各字段含义
    op:指令基本操作,称为操作码。
    rs:第一个源操作数寄存器。
    rt:第二个源操作数寄存器。
    rd:存放操作结果的目的操作数。
    shamt:位移量
    funct:函数,这个字段选择op操作的某个特定变体。
      
    所有指令都按照着三种类型之一来编码,通用字段在每种格式中的位置都是相同的。
        这种定长和简单格式的指令编码很规则,很容易看出其机器码,例如:
    add $t0,$s0,$s1
        表示$t0=$s0+$s1,即16号寄存器(s0)的内容和17号寄存器(s1)的内容相加,结果放到8号寄存器(t0)。
        指令各字段的十进制表示为
       ------|-----|-----|-----|-----|------|
       |   0 | 16 | 17 |   8 |   0 |   32 |
       ------|-----|-----|-----|-----|------|
    op=0和funct=32表示这是加法,16=$s0表示第一个源操作数(rs)在16号寄存器里,17=$s1表示第二个源操作数(rt)在17号寄存器里,8=$t0表示目的操作数(rd)在8号寄存器里。
    把各字段写成二进制,为
    ------|-----|-----|-----|-----|------|
       |000000|10000|10001|01000|00000|100000|
    ------|-----|-----|-----|-----|------|
    这就是上述指令的机器码(machine code),可以看出是很有规则性的。

    通用寄存器(GPR)
    有32个通用寄存器,$0到$31:
    $0: 即$zero,该寄存器总是返回零,为0这个有用常数提供了一个简洁的编码形式。MIPS编译器使用slt,beq,bne等指令和由寄存器$0获得的0 来 产生所有的比较条件:相等,不等,小于,小于等于,大于,大于等于。还可以用add指令创建move伪指令,即
    move $t0,$t1
    实际为
    add $t0,$0,$t1
    焦林前辈提到他移植fpc时move指令出错,转而使用add代替的。
       使用伪指令可以简化任务,汇编程序提供了比硬件更丰富的指令集。
    $1:即$at,该寄存器为汇编保留,刚才说到使用伪指令可以简化任务,但是代价就是要为汇编程序保留一个寄存器,就是$at。
    由于I型指令的立即数字段只有16位,在加载大常数时,编译器或汇编程序需要把大常数拆开,然后重新组合到寄存器里。比如加载一个32位立即数需要 lui(装入高位立即数)和addi两条指令。像MIPS程序拆散和重装大常数由汇编程序来完成,汇编程序必需一个临时寄存器来重组大常数,这也是为汇编保留$at的原因之一。
    $2..$3:($v0-$v1)用于子程序的非浮点结果或返回值,对于子程序如何传递参数及如何返回,MIPS范围有一套约定,堆栈中少数几个位置处的内容装入CPU寄存器,其相应内存位置保留未做定义,当这两个寄存器不够存放返回值时,编译器通过内存来完成。
    $4..$7:($a0-$a3)用来传递前四个参数给子程序,不够的用堆栈a0-a3和v0-v1以及ra一起来支持子程序/过程调用,分别用以传递参数,返回结果和存放返回地址。当需要使用更多的寄存器时,就需要堆栈(stack)了,MIPS编译器总是为参数在堆栈中留有空间以防有参数需要存储。
    $8..$15:($t0-$t7)临时寄存器,子程序可以使用它们而不用保留。
    $16..$23:($s0-$s7)保存寄存器,在过程调用过程中需要保留(被调用者保存和恢复,还包括$fp和$ra),MIPS提供了临时寄存器和保存寄存器,这样就减少了寄存器溢出(spilling,即将不常用的变量放到存储器的过程),编译器在编译一个叶(leaf)过程(不调用其它过程的过程)的时候,总是在临时寄存器分配完了才使用需要保存的寄存器。
    $24..$25:($t8-$t9)同($t0-$t7)
    $26..$27:($k0,$k1)为操作系统/异常处理保留,至少要预留一个。异常(或中断)是一种不需要在程序中显示调用的过程。MIPS有个叫异常程序计数器(exception program counter,EPC)的寄存器,属于CP0寄存器,用于保存造成异常的那条指令的地址。查看控制寄存器的唯一方法是把它复制到通用寄存器里,指令mfc0(move from system control)可以将EPC中的地址复制到某个通用寄存器中,通过跳转语句(jr),程序可以返回到造成异常的那条指令处继续执行。仔细分析一下会发现个有意思的事情:
    为了查看控制寄存器EPC的值并跳转到造成异常的那条指令(使用jr),必须把EPC的值到某个通用寄存器里,这样的话,程序返回到中断处时就无法将所有的寄存器恢复原值。如果先恢复所有的寄存器,那么从EPC复制过来的值就会丢失,jr就无法返回中断处;如果我们只是恢复除有从EPC复制过来的返回地址外的寄存器,但这意味着程序在异常情况后某个寄存器被无端改变了,这是不行的。为了摆脱这个两难境地,MIPS程序员都必须保留两个寄存器$k0和$k1,供操作系统使用。发生异常时,这两个寄存器的值不会被恢复,编译器也不使用k0和k1,异常处理函数可以将返回地址放到这两个中的任何一个,然后使用jr跳转到造成异常的指令处继续执行
    $28:($gp)C语言中有两种存储类型,自动型和静态型,自动变量是一个过程中的局部变量。静态变量是进入和退出一个过程时都是存在的。为了简化静态数据的访问,MIPS软件保留了一个寄存器:全局指针 gp(global pointer,$gp),如果没有全局指针,从静态数据去装入数据需要两条指令:一条有编译器和连接器计算的32位地址常量中的有效位;令一条才真正装入数据。全局指针只想静态数据区中的运行时决定的地址,在存取位于gp值上下32KB范围内的数据时,只需要一条以gp为基指针的指令即可。在编译时,数据须在以gp为基指针的64KB范围内。
    $29:($sp)MIPS硬件并不直接支持堆栈,例如,它没有x86的SS,SP,BP寄存器,MIPS虽然定义$29为栈指针,它还是通用寄存器,只是用于特殊目的而已,你可以把它用于别的目的,但为了使用别人的程序或让别人使用你的程序,还是要遵守这个约定的,但这和硬件没有关系。x86有单独的PUSH和POP指令,而MIPS没有,但这并不影响 MIPS使用堆栈。在发生过程调用时,调用者把过程调用过后要用的寄存器压入堆栈,被调用者把返回地址寄存器$ra和保留寄存器压入堆栈。同时调整堆栈指针,当返回时,从堆栈中恢复寄存器,同时调整堆栈指针。
    $30:($fp)GNU MIPS C编译器使用了侦指针(frame pointer),而SGI的C编译器没有使用,而把这个寄存器当作保存寄存器使用($s8),这节省了调用和返回开销,但增加了代码生成的复杂性。
    $31:($ra)存放返回地址,MIPS 有个jal(jump-and-link,跳转并链接)指令,在跳转到某个地址时,把下一条指令的地址放到$ra中。用于支持子程序,例如调用程序把参数放到$a0~$a3,然后jal X跳到X过程,被调过程完成后把结果放到$v0,$v1,然后使用jr $ra返回。
    在调用时需要保存的寄存器为$a0~$a3,$s0~$s7,$gp,$sp,$fp,$ra。
    跳转范围
    J 指令的地址字段为26位,用于跳转目标。指令在内存中以4字节对齐,最低两个有效位不需要存储。在MIPS中,每个地址的最低两位指定了字的一个字节,cache映射的下标是不使用这两位的,这样能表示28位的字节编址,允许的地址空间为256M。PC是32位的,那其它4位从何而来呢?MIPS的跳转指令只替换PC的低28位,而高4位保留原值。因此,加载和链接程序必须避免跨越256MB,在256M的段内,分支跳转地址当作一个绝对地址,和 PC无关,如果超过256M(段外跳转)就要用跳转寄存器指令了。
    同样,条件分支指令中的16位立即数如果不够用,可以使用PC相对寻址,即用分支指令中的分支地址与(PC+4)的和做分支目标。由于一般的循环和if语句都小于2^16个字(2的16次方),这样的方法是很理想的。

     

    0 zero 永远返回值为0
    1 at 用做汇编器的暂时变量
    2-3 v0, v1 子函数调用返回结果
    4-7 a0-a3 子函数调用的参数
    8-15 t0-t7 暂时变量,子函数使用时不需要保存与恢复
    24-25 t8-t9
    16-25 s0-s7 子函数寄存器变量。子函数必须保存和恢复使用过的变量在函数返回之前,从而调用函数知道这些寄存器的值没有变化。
    26,27 k0,k1 通常被中断或异常处理程序使用作为保存一些系统参数
    28 gp 全局指针。一些运行系统维护这个指针来更方便的存取“static“和”extern"变量。
    29 sp 堆栈指针
    30 s8/fp 第9个寄存器变量。子函数可以用来做桢指针
    31 ra 子函数的返回地□

    这些寄存器的用法都遵循一系列约定。这些约定与硬件确实无关,但如果你想使用别人的代码,编译器和操作系统,你最好是遵循这些约定。

    寄存器名约定与使用

    *at: 这个寄存器被汇编的一些合成指令使用。如果你要显示的使用这个寄存器(比如在异常处理程序中保存和恢复寄存器),有一个汇编directive可被用来禁止汇编器在directive之后再使用at寄存器(但是汇编的一些宏指令将因此不能再可用)。

    *v0, v1: 用来存放一个子程序(函数)的非浮点运算的结果或返回值。如果这两个寄存器不够存放需要返回的值,编译器将会通过内存来完成。详细细节可见10.1节。


    *a0-a3: 用来传递子函数调用时前4个非浮点参数。在有些情况下,这是不对的。请参考10.1细节。

    * t0-t9: 依照约定,一个子函数可以不用保存并随便的使用这些寄存器。在作表达式计算时,这些寄存器是非常好的暂时变量。编译器/程序员必须注意的是,当调用一个子函数时,这些寄存器中的值有可能被子函数破坏掉。

    *s0-s8: 依照约定,子函数必须保证当函数返回时这些寄存器的内容必须恢复到函数调用以前的值,或者在子函数里不用这些寄存器或把它们保存在堆栈上并在函数退出时恢复。这种约定使得这些寄存器非常适合作为寄存器变量或存放一些在函数调用期间必须保存原来值。

    * k0, k1: 被OS的异常或中断处理程序使用。被使用后将不会恢复原来的值。因此它们很少在别的地方被使用。

    * gp: 如果存在一个全局指针,它将指向运行时决定的,你的静态数据(static data) 区域的一个位置。这意味着,利用gp作基指针,在gp指针32K左右的数据存取,系统只需要一条指令就可完成。如果没有全局指针,存取一个静态数据区域的值需要两条指令:一条是获取有编译器和loader决定好的32位的地址常量。另外一条是对数据的真正存取。为了使用gp, 编译器在编译时刻必须知道一个数据是否在gp的64K范围之内。通常这是不可能的,只能靠猜测。一般的做法是把small global data (小的全局数据)放在gp覆盖的范围内(比如一个变量是8字节或更小),并且让linker报警如果小的全局数据仍然太大从而超过gp作为一个基指针所能存取的范围。

    并不是所有的编译和运行系统支持gp的使用。

    *sp: 堆栈指针的上下需要显示的通过指令来实现。因此MIPS通常只在子函数进入和退出的时刻才调整堆栈的指针。这通过被调用的子函数来实现。sp通常被调整到这个被调用的子函数需要的堆栈的最低的地方,从而编译器可以通过相对於sp的偏移量来存取堆栈上的堆栈变量。详细可参阅10.1节堆栈使用。

    * fp: fp的另外的约定名是s8。如果子函数想要在运行时动态扩展堆栈大小,fp作为桢指针可以被子函数用来记录堆栈的情况。一些编程语言显示的支持这一点。汇编编程员经常会利用fp的这个用法。C语言的库函数alloca()就是利用了fp来动态调整堆栈的。

    如果堆栈的底部在编译时刻不能被决定,你就不能通过sp来存取堆栈变量,因此fp被初始化为一个相对与该函数堆栈的一个常量的位置。这种用法对其他函数是不可见的。

    * ra: 当调用任何一个子函数时,返回地址存放在ra寄存器中,因此通常一个子程序的最后一个指令是jr ra.

    子函数如果还要调用其他的子函数,必须保存ra的值,通常通过堆栈。

    对於浮点寄存器的用法,也有一个相应的标准的约定。在这里,我们已经介绍了MIPS引入的寄存

    指令实例:

    1. load/store
      la $t0, val_1 复制val_1表示的地址到t0寄存器中     注: val_1是个Label
     lw $t2, ($t0) t0寄存器中的值作为地址,把这个地址起始的Word 复制到t2 中
     lw $t2, 4($t0) t0寄存器中的值作为地址, 把这个地址再加上偏移量4后 所起始的Word 复制到t2 中
     sw $t2, ($t0) 把t2寄存器中值(1 Word),存储到t0的值所指向的RAM中
     sw $t2, -12($t0) 把t2寄存器中值(1 Word),存储到t0的值再减去偏移量12, 所指向的RAM 中

    2. 算数运算指令
      算数运算指令的所有操作数都是寄存器,不能直接使用RAM地址或间接寻址。
      操作数的大小都为 Word (4-Byte)
      指令格式与实例 注释
      move $t5, $t1       // $t5 = $t1;
      add $t0, $t1,       // $t2 $t0 = $t1 + $t2; 带符号数相加
      sub $t0, $t1,       // $t2 $t0 = $t1 - $t2; 带符号数相减
      addi $t0, $t1, 5    // $t0 = $t1 + 5;
      addu $t0, $t1, $t2  // $t0 = $t1 + $t2; 无符号数相加
      subu $t0, $t1, $t2  // $t0 = $t1 - $t2; 无符号数相减
      mult $t3, $t4       // $t3 * $t4, 把64-Bits 的积,存储到Lo,Hi中。即: (Hi, Lo) = $t3 * $t4;
      div $t5, $t6        // Lo = $t5 / $t6 (Lo为商的整数部分); Hi = $t5 mod $t6 (Hi为余数)
      mfhi $t0            // $t0 = Hi
      mflo $t1            // $t1 = Lo

    3. 分支跳转指令
     分支指令格式与实例 注释
      b target 无条件的分支跳转,将跳转到target 标签处
      beq $t0, $t1, target       // 如果 $t0 == $t1, 则跳转到target 标签处
      blt $t0, $t1, target       // 如果 $t0 < $t1,  则跳转到target 标签处
      ble $t0, $t1, target       // 如果 $t0 <=$t1,  则跳转到target 标签处
      bgt $t0, $t1, target       // 如果 $t0 > $t1,  则跳转到target 标签处
      bge $t0, $t1, target       // 如果 $t0 >= $t1, 则跳转到target 标签处
      bne $t0, $t1, target       // 如果 $t0 != $t1, 则跳转到target 标签处

    4. 跳转指令
     指令格式与实例 注释
      j target          // 无条件的跳转, 将跳转到target 标签处
      jr $t3            // 跳转到t3寄存器所指向的地址处(Jump Register)

    5. 子函数调用指令
     指令格式与实例 注释
      jal sub_routine_label 执行步骤:
      a. 复制当前的PC(Program Counter)到$ra寄存器中。 因为当前的PC 值就是子函数执行完毕后的返回
           地址。
      b. 程序跳转到子程序标签sub_routine_label处。  
      注:子函数的返回,使用 jr $ra  
      如果子函数内又调用了其他的子函数,那么$ra的值应该被保存到堆栈中。 因为$ra的值总是对应着当前执
        行的子函数的返回地址。
    展开全文
  • 这篇文章来自于农夫山泉,我们是大自然的搬运工,哈哈,文章来源于网络 ...首先, 题主"李建国"自问自答的部分说的是正确的,CPU的指令集是软件与CPU这两个层级之间的接口, 而CPU自己, 就是对于这一套CPU指令集的"...

    这篇文章来自于农夫山泉,我们是大自然的搬运工,哈哈,文章来源于网络

    https://blog.csdn.net/yu132563/article/details/55251534

     这个问题包括CPU的硬件结构和汇编语言的范畴. 这里梳理一下.

    首先, 题主"李建国"自问自答的部分说的是正确的, CPU的指令集是软件与CPU这两个层级之间的接口, 而CPU自己, 就是对于这一套CPU指令集的"实例化"

    无论处于上层的软件多么的高级, 想要在CPU执行, 就必须被翻译成"机器码", 翻译这个工作由编译器来执行. 编译器在这个过程中, 要经过"编译", "汇编", "链接"几个步骤, 最后生成"可执行文件". 可执行文件中保存的是二进制机器码. 这串机器码可以直接被CPU读取和执行. 

    软件意义上, "指令集"实际上是一个规范, 规范汇编的文件格式.
    以下为一条x86汇编代码:
    mov word ptr es:[eax + ecx * 8 + 0x11223344], 0x12345678

    这里可以体现出指令集的格式限制:
    1. 可以使用mov指令, 但它只能有2个操作数.
    2. 它的操作数长度是16 (word), 不要看到后面0x12345678就认为是32位操作数.
    3. 它带有段超越前缀, 这里使用了es, 还可以使用ds, cs, ss, fs, gs. 但是只能用这几个.
    4. 第一个操作数是一个内存地址, 第二个是立即数. 但是, 这个内存地址不能乱写, 写成[eax+ecx*10+0x11223344]就错了.

    实际上, 一条汇编指令与一段机器码是一一对应的. 上面这段汇, 可以被x86编译器翻译成几乎唯一的一段机器码:
    26 66 c7 84 c8 44 33 22 11 78 56
    上面提到的1,2,3,4点如果有一个弄错, 这一步就会失败.

    可以看出来, 指令集的作用, 就是告诉程序员/编译器, 汇编一定要有格式. 支持什么指令, 指令带什么限制条件, 用什么操作数, 用什么地址, 都是指令集规范的内容, 要是写错了, 就无法翻译成机器码.
    指令集规范汇编, 汇编可以翻译成机器码, 机器码告诉CPU每个周期去做什么. 因此, CPU指令集是描述CPU能实现什么功能的一个集合, 就是描述"CPU能使用哪些机器码"的集合".

    那机器码进入到CPU后又做什么呢?
    =====================编译器和CPU的分界线========================

    需要被执行的机器码先要被OS调度到内存之中, 程序执行时, 机器码依次经过了Memory--Cache--CPU fetch, 进入CPU流水线, 接着就要对它进行译码了, 译码工作生成的象是CPU内部数据格式, 微码(或者类似的格式, 这个格式不同的厂商会自己设计). 

    这个过程画成图就是:

    软件层: 汇编语言
    ------------------------------------------------------------------------
    接口: 汇编语言所对应的机器码
    ------------------------------------------------------------------------
    硬件层: CPU使用内部数据结构进行运算

    如果机器码代表的功能是在指令集规范内的, 这条机器码就可以生产微码, 并在CPU内正常流动. 假设机器码是错误的, 是不可以通过CPU的译码阶段的, 控制电路一定会报错. 这种情况反映在Windows里往往都是蓝屏, 因为CPU无法继续执行, 它连下一条指令在哪都不知道.

    那么指令集在CPU里就代表: 只有CPU指令集范围内的指令可以被成功的译码, 并送往CPU流水线后端去执行.
    和常规的想法不一样, CPU不需要任何形式的存储介质去存储指令集, 因为"译码"这个步骤就是在对指令集里规范的机器码做解码. 硬件上, 译码这件事需要庞大数目的逻辑门阵列来实现.


    跳出格式这个圈子来看待这个问题. 可以说, CPU执行单元的能力, 决定了指令集的范围. 比如, CPU的执行单元有能力执行16位加法, 32位加法, 64位加法, 那么指令集里一般就会有ADD 16, ADD 32, ADD 64这样的表达方式. 如果CPU的执行单元没有电路执行AVX指令, 那么指令集里一般就没有VINSERTF128这样的指令供使用. 所以, 强有力的执行单元能够提供更多的指令集.

    再来看"CPU指令集在哪里"这个问题, 回答是, CPU本身就是CPU指令集. 指令集规定CPU可以做什么事, CPU就是具体做这件事的工具. 如果一定要指定一个狭义的CPU指令集的存放位置. 那就是CPU中的"译码电路".

    =======================================================================================================================
    =======================================================================================================================

    作者:Cascade
    链接:https://www.zhihu.com/question/20793038/answer/16198162
    来源:知乎
    著作权归作者所有,转载请联系作者获得授权。

    是,这个解释起来有点长。Be patient
    现代的CPU没拆过,我只在计算机组成原理实验课上用VHDL在某个实验平台上做过一个模拟的CPU。举个例子你可能比较好理解。
    比如我们设计一套指令集,其中肯定有条加法指令。比如Add R1 R2 。我们可以认为这条指令的意思是计算寄存器R1中的内容和R2的和,然后把结果存到R1寄存器中。
    那么经过编译后这条指令会变成二进制,比如010100010010 。这条二进制指令一共12位。明显可以分为三大部分。最前面的0101表示这是条加法指令,后面0001说的是第一个操作数是寄存器1,最后0010说的是第二个数就是寄存器2(其实实际没有这么简单的指令,至少应该区分操作数是寄存器还是直接的数据,但为了把这说的更容易理解作了简化)。我们可以通过十二根导线把这条指令输入一个CPU中。导线通电就是1,不通电就是0 。为了叙述方便我们从左到右用A0-A11给这12根导线编上号。
    然后计算机会分析这条指令。步骤如下:

    1. 最开始的两根导线A0和A1,第一根有电第二根没电,就能知道这是一条运算指令(而非存储器操作或者跳转等指令)。那么指令将被送入逻辑运算单元(ALU)去进行计算。其实很简单。只要这两根线控制接下来那部分电路开关即可。
    2. 接下来的A2和A3,01表示加法,那么就走加法运算那部分电路,关闭减法等运算电路。
    3. A4-A7将被送入寄存器电路,从中读取寄存器保存的值。送到ALU的第一个数据接口电路上。
    4. 后面的A8-A11同样被送入寄存器选择电路,接通R2寄存器,然后R2就把值送出来,放到ALU的第二个数据接口上。
    5. ALU开始运算,把两个接口电路上的数据加起来,然后输出。
    6. 最后结果又被送回R1。

    基本上简单的运算计算机就是这么操作的。他其实不知道你那些指令都是什么意思。具体的指令编程机器码后就会变成数字电路的开关信号。其中某几段会作为控制信号,控制其他部分的数据走不同的电路以执行运算。他没有一个地方保存着如何翻译这些机器码的字典,所有机器码的意义都被体现在整个电路的设计中了。
    当然,从汇编到机器码这步是汇编程序翻译的。汇编程序当然知道某条指令要翻译成什么样的机器码。

    对于第二个例子,其实搞过单片机的人都比较了解了,就是一个GPIO管脚就是一个位操作。对应一个高低电平的输入。

    刚才是解释了下什么是指令集,现在再来讨论下指令集大的分类(下面的内容来自百度百科)

    微处理器的指令集架构(Instruction Set Architecture)常见种类如下: 复杂指令集运算(Complex Instruction Set Computing,CISC);精简指令集运算(Reduced Instruction Set Computing,RISC) ;显式并行指令集运算(Explicitly Parallel Instruction Computing,EPIC);超长指令字指令集运算(VLIW)

    CISC

    目前x86架构微处理器如Intel的Pentium/Celeron/Xeon与AMD的Athlon/Duron/Sempron;以及其64位扩展系统的x86-64的架构的EM64T的Pentium/Xeon与AMD64的Athlon 64/Opteron都属于CISC系列。主要针对的操作系统是微软的Windows。另外Linux,一些UNIX等都可以运行在x86(CISC)架构的微处理器。

    RISC

    RISC这种指令集运算包括HP的PA-RISC,IBM的PowerPC,Compaq(被并入HP)的Alpha,MIPS公司的MIPS,SUN公司的SPARC等。目前只有UNIX,Linux,MacOS等操作系统运行在RISC处理器上。

    EPIC

    EPIC乃先进的全新指令集运算,只有Intel的IA-64架构的纯64位微处理器的Itanium/Itanium 2。EPIC指令集运算的IA-64架构主要针对的操作系统是微软64位安腾版的Windows XP以及64位安腾版的Windows Server 2003。另外一些64位的Linux,一些64位的UNIX也可以运行IA-64(EPIC)架构。

    VLIW

    通过将多条指令放入一个指令字,有效的提高了CPU各个计算功能部件的利用效率,提高了程序的性能。

    然后具体的指令集,有下面这些

    指令集是存储在CPU内部,对CPU运算进行指导和优化的硬程序。拥有这些指令集,CPU就可以更高效地运行。Intel主要有x86EM64T,MMX,SSE,SSE2SSE3,SSSE3 (Super SSE3),SSE4A,SSE4.1SSE4.2AVX,AVX2,AVX-512,VMX等指令集。AMD主要是x86x86-64,3D-Now!指令集。

    展开全文
  • freemarker指令(九)

    千次阅读 2017-12-24 22:53:24
    如果你没有在这里发现模板指令,可能你需要在废弃的 FTL 结构来查找它了。 2.1 if ,else ,elseif 指令 2.1.1 概要 ... ... ... ... ... 这里:  condition , condition2 等:表达式将被计算成...

    Freemarker指令

    如果你没有在这里发现模板中的指令,可能你需要在废弃的 FTL 结构中来查找它了。

    2.1 if ,else ,elseif  指令

    2.1.1  概要

    <#if condition>

    ...

    <#elseif condition2>

    ...

    <#elseif condition3>

    ...

    ...

    <#else>

    ...

    </#if>

    这里:

      condition , condition2 等:表达式将被计算成布尔值。

    2.1.2  描述

    你可以使用 if , elseif 和 else 指令来条件判断是否越过模板的一个部分。这些

    condition -s 必须计算成布尔值,否则错误将会中止模板处理。 elseif -s 和 else -s

    必须出现在 if 的内部(也就是,在 if 的开始标签和技术标签之间)。 if 中可以包含任意

    数量的 elseif -s(包括 0 个)而且结束时 else 是可选的。

    比如:

    只有 if ,没有 elseif 和 else :

    <#if x == 1>

    x is 1

    </#if>

    只有 if 和 else ,没有 elseif :

    <#if x == 1>

    x is 1

    <#else>

    x is not 1

    </#if>

    if 和两个 elseif ,没有 else :

    <#if x == 1>

    x is 1

    <#elseif x == 2>

    x is 2

    <#elseif x == 3>

    x is 3

    </#if>

    if 和 3 个 elseif ,还有 else :

    <#if x == 1>

    x is 1

    <#elseif x == 2>

    x is 2

    <#elseif x == 3>

    x is 3

    <#elseif x == 4>

    x is 4

    <#else>

    x is not 1 nor 2 nor 3 nor 4

    </#if>

    要了解更多布尔表达式,可以参考:模板开发指南/模板/表达式部分内容。

    你(当然)也可以嵌套 if 指令:

    <#if x == 1>

    x is 1

    <#if y == 1>

    and y is 1 too

    <#else>

    but y is not

    </#if>

    <#else>

    x is not 1

    <#if y < 0>

    and y is less than 0

    </#if>

    </#if>

    注意:

    如何测试 x 比 1 大? <#if x > 1> 是不对的,因为 FreeMarker 将会解释第一个 > 作

    为结束标记。因此,编写 <#if (x > 1)> 或 <#if x &gt;1> 是正确的。

    2.2 switch ,case ,default ,break  指令

    2.2.1  概要

    <#switch value>

    <#case refValue1>

    ...

    <#break>

    <#case refValue2>

    ...

    <#break>

    ...

    <#case refValueN>

    ...

    <#break>

    <#default>

    ...

    </#switch>

    这里:

      value , refValue1 等:表达式将会计算成相同类型的标量。

    2.2.2  描述

    这个指令的用法是不推荐的,因为向下通过的行为容易出错。使用 elseif -s 来代替,

    除非你想利用向下通过这种行为。

    Switch 被用来选择模板中的一个片段,如何选择依赖于表达式的值:

    <#switch being.size>

    <#case "small">

    This will be processed if it is small

    <#break>

    <#case "medium">

    This will be processed if it is medium

    <#break>

    <#case "large">

    This will be processed if it is large

    <#break>

    <#default>

    This will be processed if it is neither

    </#switch>

    在 switch 中间必须有一个或多个 <#case value> ,在所有 case 标签之后,有

    一个可选的 <#default> 。当 FreeMarker 到达指令时,它会选择一个 refValue 和

    value 相等的 case 指令来继续处理模板。如果没有和合适的值匹配的 case 指令,如

    果 default 指令存在,那么就会处理 default 指令,否则就会继续处理 switch 结

    束标签之后的内容。现在有一个混乱的事情:当它选择一个 case 指令后,它就会继续处

    理 case 指令中的内容,直到遇到 break 指令。也就是它遇到另外一个 case 指令或

    <#default> 标记时也不会自动离开switch 指令。比如:

    <#switch x>

    <#case x = 1>

    1

    <#case x = 2>

    2

    <#default>

    d

    </#switch>

    如果 x 是 1,那么它会打印 1 2 d;如果 x 是 2,那么就会打印 2 d;如果 x 是 3,那么

    它会打印 d。这就是前面提到的向下通过行为。 break 标记指示 FreeMarker 直接略过剩下

    的 switch 代码段。

    2.3 list ,break  指令

    2.3.1  概要

    <#list sequence as item>

    ...

    </#list>

    这里:

      sequence :表达式将被算作序列或集合

      item :循环变量(不是表达式)的名称

    2.3.2  描述

    你可以使用 list 指令来处理模板的一个部分中的一个序列中包含的各个变量。在开

    始标签和结束标签中的代码将会被处理,首先是第一个子变量,然后是第二个子变量,接着

    是第三个子变量,等等,直到超过最后一个。对于每个变量,这样的迭代中循环变量将会包

    含当前的子变量。

    在 list 循环中,有两个特殊的循环变量可用:

      item_index :这是一个包含当前项在循环中的步进索引的数值。

      item_has_next :来辨别当前项是否是序列的最后一项的布尔值。

    示例 1:

    <#assign seq = ["winter","spring", "summer", "autumn"]>

    <#list seq as x>

    ${x_index + 1}. ${x}<#ifx_has_next>,</#if>

    </#list>

    将会打印:

    1. winter,

    2. spring,

    3. summer,

    4. autumn

    示例 2:你可以使用 list 在两个数字中来计数,使用一个数字范围序列表达式:

    <#assign x=3>

    <#list 1..x as i>

    ${i}

    </#list>

    输出是:

    1

    2

    3

    注意上面的示例在你希望 x 是 0 的时候不会有作用,那么它打印 0 和-1。

    你可以使用 break 指令在它通过最后一个序列的子变量之前离开 list 循环。比如

    这会仅仅打印“winter”和“spring”。

    <#list seq as x>

    ${x}

    <#if x ="spring"><#break></#if>

    </#list>

    通常来说,避免 list 中使用无论何时可能包装了 Iterator 作为参数的集合和使

    用包装了 java.util.Collection 或序列的集合是最好的。但是在某些情况,当你

    处理时仅仅有一个 Iterator 。要注意如果你传递了一个包装了 Iterator 的集合给

    list ,你仅仅可以迭代一次元素,因为Iterator s 是由它们一次性对象的特性决定的。

    当你尝试第二次列出这样一个集合变量时,错误会中止模板的处理。

    2.4 include  指令

    2.4.1  概要

    <#include path>

    or

    <#include path options>

    这里:

      path :要包含文件的路径;一个算作是字符串的表达式。(用其他话说,它不用

    是一个固定的字符串,它也可以是像 profile.baseDir + "/menu.ftl"

    这样的东西。)

    1. winter,

    2. spring,

    3. summer,

    4. autumn

    <#assign x=3>

    <#list 1..x as i>

    ${i}

    </#list>

    1

    2

    3

    <#list seq as x>

    ${x}

    <#if x ="spring"><#break></#if>

    </#list>

      options :一个或多个这样的选项:encoding=encoding ,  parse=parse

      encoding :算作是字符串的表达式

      parse :算作是布尔值的表达式(为了向下兼容,也接受一部分字符串值)

    2.4.2  描述

    你可以使用它在你的模板中插入另外一个 FreeMarker 模板文件(由 path 参数指定)。

    被包含模板的输出格式是在 include 标签出现的位置插入的。被包含的文件和包含它的

    模板共享变量,就像是被复制粘贴进去的一样。 include 指令不能由被包含文件的内容

    所替代,它只是当 FreeMarker 每次在模板处理期间到达 include 指令时处理被包含的文

    件。所以对于如果 include 在 list 循环之中的例子,你可以为每个循环周期内指定不

    同的文件名。

    注意:

    这个指令不能和 JSP(Servlet)的 include 搞混,因为它不涉及到 Servlet 容器中,只是

    处理应外一个 FreeMarker 模板,不能“离开”FreeMarker。关于如何处理“JSP include”,

    可以参考 FAQ 中的内容。

    path 参数可以是如"foo.ftl" 和 "../foo.ftl" 一样的相对路径,或者是如

    "/foo.ftl" 这样的绝对路径。相对路径是相对于使用 import 指令的模板文件夹。绝

    对路径是相对于程序员在配置 FreeMarker 时定义的基路径(通常指代“模板的根路径”)。

    注意:

    这和 FreeMarker 2.1 版本之前的处理方式不同,之前的路径通常是绝对路径。为了保留

    原来的行为,要在 Configuration 对象中开启经典的兼容模式。

    通常使用/(斜杠)来分隔路径成分,而不是\(反斜杠)。如果你从你本地的文件系统

    加载模板,要使用反斜杠(像 Windows 操作系统)。FreeMarker 会自动转换它们。

    比如:

    假设/common/copyright.ftl 包含:

    Copyright 2001-2002 ${me}<br>

    All rights reserved.

    那么这个:

    <#assign me = "JuilaSmith">

    <h1>Some test</h1>

    <p>Yeah.

    <hr>

    <#include"/common/copyright.ftl">

    会打印出:

    <h1>Some test</h1>

    <p>Yeah.

    <hr>

    Copyright 2001-2002 Juila Smith

    All rights reserved.

    支持的 options 选项有:

      parse:如果它为真,那么被包含的文件将会当作FTL 来解析,否则整个文件将被

    视为简单文本(也就是说不会在其中查找 FreeMarker 的结构)。如果你忽略了这个

    选项,那么它默认是 true。

      encoding:被包含文件从包含它的文件继承的编码方式(实际就是字符集),除非

    你用这个选项来指定编码方式。编码名称要和 java.io.InputStreamReader 中支持的

    那些一致(对于 Java API 1.3 版本: MIME  希望的字符集是从 IANA字符集注册处得

    到的)。合法的名字有:ISO-8859-2,UTF-8,Shift_JIS,Big5,EUC-KR,GB2312。

    比如:

    <#include"/common/navbar.html" parse=false

    encoding="Shift_JIS">

    注意,对于所有模板可能会用 Configuration 的“自动包含”设置自动处理通用

    的包含物。

    2.4.2.1  使用获得 机制

    有一个特殊的路径组成,是用一个星号( * )来代表的。它被解释为“当前目录或其他

    任意它的父目录”。因此,如果模板在 /foo/bar/template.ftl 位置上,有下面这

    行:

    那么引擎就会在下面的位置上寻找模板,并按这个顺序:

    /foo/bar/footer.ftl

     /foo/footer.ftl

     /footer.ftl

    这种机制被称为 acquisition  获得并允许设计者在父目录中放置通用的被包含的文件,而

    且当需要时在每个子路径基础上重新定义它们。我们说包含它们的模板获得了从包含它的第

    一个父目录中的模板。注意你不但可以在星号的右面指定一个模板的名字,也可以指定一个

    子路径。也就是说,如果前面的模板由下面这个所替代:

    <#include"*/commons/footer.ftl">

    那么引擎将会从下面的路径开始寻找模板,并按这个顺序:

    /foo/bar/commons/footer.ftl

    /foo/commons/footer.ftl

      /commons/footer.ftl

    然而,在路径中最大只能有一个星号。指定多余一个星号会导致模板不能被发现。

    2.4.2.2  本地化查找

    无论何时模板被加载,它都被分配了一个本地化环境。本地化环境是语言和可选的国家

    或方言标识。模板一般是由程序员编写一些代码来加载的,出于一些方面的考虑,程序员为

    模板选择一种本地化环境。比如:当 FreemarkerServlet 加载模板时,它经常用本

    地化环境匹配浏览器请求 Web 页面的语言偏好来请求模板。

    当一个模板包含另一个模板时,它试图加载以相同的本地化环境加载模板。假定你的模

    板以本地化 en_US 来加载,那就意味着是 U.S. English。当你包含另外一个模板:

    <include "footer.ftl">

    那么引擎实际上就会寻找一些模板,并按照这个顺序:

     footer_en_US.ftl

     footer_en.ftl

      footer.ftl

    要注意你可以使用 Configuration 的 setLocalizedLookup 方法关闭本地化查找特性。

    当你同时使用获得机制和本地化查找时,在父目录中有指定本地化的模板优先于在子目

    录中有很少本地化的模板。假设你使用下面的代码来包含 /foo/bar/template.ftl :

    <include "*/footer.ftl">

    引擎将会查找这些模板,并按照这个顺序:

    /foo/bar/footer_en_US.ftl

    /foo/footer_en_US.ftl

     /footer_en_US.ftl

    /foo/bar/footer_en.ftl

    /foo/footer_en.ftl

     /footer_en.ftl

     /foo/bar/footer.ftl

      /foo/footer.ftl

      /footer.ftl

    2.5 import  指令

    2.5.1  概要

    <#import path as hash>

    这里:

      path :模板的路径。这是一个算作是字符串的表达式。(换句话说,它不是一个固

    定的字符串,它可以是这样的一些东西,比如, profile.baseDir +

    "/menu.ftl" 。)

      hash :哈希表变量的结束名称,你可以由它来访问命名空间。这不是表达式。

    2.5.2  描述

    引入一个库。也就是说,它创建一个新的命名空间,然后在那个命名空间中执行给定

    path 参数中的模板,所以模板用变量(宏,函数等)填充命名空间。然后使得新创建的命

    名空间对哈希表的调用者可用。这个哈希表变量将会在命名空间中,由 import (就像你

    可以用 assign 指令来创建一样。)的调用者被创建成一个普通变量,名字就是 hash 参

    <include "footer.ftl">

    <include "*/footer.ftl">

    数给定的。

    如果你用同一个 path 多次调用 import ,它会创建命名空间,但是只运行第一次

    import 的调用。后面的调用仅仅创建一个哈希表变量,你只是通过它来访问同一个命名

    空间。

    由引入的模板打印的输出内容将会被忽略(不会在包含它的地方被插入)。模板的执行

    是来用变量填充命名空间,而不是写到输出中。

    例如:

    <#import "/libs/mylib.ftl" asmy>

    <@my.copyrightdate="1999-2002"/>

    path 参数可以是一个相对路径,比如"foo.ftl" 和 "../foo.ftl" ,或者是像

    "/foo.ftl" 一样的绝对路径。相对路径是相对于使用 import 指令模板的目录。绝对

    路径是程序员配置 FreeMarker 时定义的相对于根路径(通常指代“模板的根目录”)的路径。

    通常使用 / (斜杠)来分隔路径组成,而不是 \ (反斜杠)。如果你从你本地的文件系统

    中加载模板,那么它使用反斜杠(比如在 Windows 环境下),FreeMarker 将会自动转换它们。

    像 include 指令一样,获得机制和本地化擦找也可以用来解决路径问题。

    注意,对于所有模板来说,它可以自动做通用的引入操作,使用 Configuration

    的“自动引入”设置就行了。

    如果你命名空间不是很了解,你应该阅读:模板开发指南/其他/命名空间部分的内容。

    2.6 noparse  指令

    2.6.1  概要

    <#noparse>

    ...

    </#noparse>

    2.6.2  描述

    FreeMarker 不会在这个指令体中间寻找 FTL 标签,插值和其他特殊的字符序列,除了

    noparse 的结束标记。

    例如:

    <#import "/libs/mylib.ftl" asmy>

    <@my.copyrightdate="1999-2002"/>

    Example:

    --------

    <#noparse>

    <#list animals as being>

    <tr><td>${being.name}<td>${being.price}Euros

    </#list>

    </#noparse>

    将会输出:

    Example:

    --------

    <#list animals as being>

    <tr><td>${being.name}<td>${being.price}Euros

    </#list>

    2.7 compress  指令

    2.7.1  概要

    <#compress>

    ...

    </#compress>

    2.7.2  描述

    当你使用了对空白不敏感的格式(比如 HTML 或 XML)时压缩指令对于移除多余的空白

    是很有用的。它捕捉在指令体(也就是在开始标签和结束标签中)中生成的内容,然后缩小

    所有不间断的空白序列到一个单独的空白字符。如果被替代的序列包含换行符或是一段空间,

    那么被插入的字符也会是一个换行符。开头和结尾的不间断的空白序列将会完全被移除。

    <#assign x = " moo \n\n ">

    (<#compress>

    1 2 3 4 5

    ${moo}

    test only

    I said, test only

    </#compress>)

    会输出:

    (1 2 3 4 5

    moo

    test only

    I said, test only)

    2.8 escape ,noescape  指令

    2.8.1  概要

    <#escape identifier as expression>

    ...

    <#noescape>...</#noescape>

    ...

    </#escape>

    2.8.2  描述

    当你使用 escape 指令包围模板中的一部分时,在块中出现的插值( ${...} )会和

    转义表达式自动结合。这是一个避免编写相似表达式的很方便的方法。它不会影响在字符串

    形式的插值(比如在 <#assign x = "Hello ${user}!"> )。而且,它也不会影

    响数值插值( #{...} )。

    例如:

    <#escape x as x?html>

    First name: ${firstName}

    Last name: ${lastName}

    Maiden name: ${maidenName}

    </#escape>

    事实上它等同于:

    First name: ${firstName?html}

    Last name: ${lastName?html}

    Maiden name: ${maidenName?html}

    注意它和你在指令中用什么样的标识符无关 - 它仅仅是作为一个转义表达式的正式参

    数。

    当你在 include 指令中调用宏时, 理解 在模 板文 本中转 义仅 仅对 出现在

    <#escape ...> 和</#escape> 中的插值起作用是很重要的。也就是说,它不会转义

    文本中 <#escape ...> 之前的东西或 </#escape> 之后的东西,也不会从 escape -d

    部分中来调用。

    <#assign x ="<test>">

    <#macro m1>

    m1: ${x}

    </#macro>

    <#escape x as x?html>

    <#macro m2>m2: ${x}</#macro>

    ${x}

    <@m1/>

    </#escape>

    ${x}

    <@m2/>

    输出将是:

    &lt;test&gt;

    m1: <test>

    <test>

    m2: &lt;test&gt;

    从更深的技术上说, escape 指令的作用是用在模板解析的时间而不是模板处理的时

    间。这就表示如果你调用一个宏或从一个转义块中包含另外一个模板,它不会影响在宏 / 被

    包含模板中的插值,因为宏调用和模板包含被算在模板处理时间。另外一方面,如果你用一

    个转义区块包围一个或多个宏声明(算在模板解析时间,和宏调用想法),那么那些宏中的

    插值将会和转义表达式合并。

    有时需要暂时为一个或两个在转义区块中的插值关闭转义。你可以通过关闭,过后再重

    新开启转义区块来达到这个功能,但是那么你不得不编写两遍转义表达式。你可以使用非转

    义指令来替代:

    <#escape x as x?html>

    From: ${mailMessage.From}

    Subject: ${mailMessage.Subject}

    <#noescape>Message:

    ${mailMessage.htmlFormattedBody}</#noescape>

    ...

    </#escape>

    和这个是等同的:

    From: ${mailMessage.From?html}

    Subject: ${mailMessage.Subject?html}

    Message: ${mailMessage.htmlFormattedBody}

    ...

    转义可以被嵌套(尽管你不会在罕见的情况下来做)。因此,你可以编写如下面代码(这

    个例子固然是有点伸展的,正如你可能会使用 list 来迭代序列中的每一项,但是我们现

    在所做的是阐述这个观点)的东西:

    <#escape x as x?html>

    Customer Name: ${customerName}

    Items to ship:

    <#escape x as itemCodeToNameMap[x]>

    ${itemCode1}

    ${itemCode2}

    ${itemCode3}

    ${itemCode4}

    </#escape>

    </#escape>

    实际上和下面是等同的:

    Customer Name: ${customerName?html}

    Items to ship:

    ${itemCodeToNameMap[itemCode1]?html}

    ${itemCodeToNameMap[itemCode2]?html}

    ${itemCodeToNameMap[itemCode3]?html}

    ${itemCodeToNameMap[itemCode4]?html}

    当你在嵌入的转义区块内使用非转义指令时,它仅仅不处理一个单独层级的转义。因此,

    为了在两级深的转义区块内完全关闭转义,你需要使用两个嵌套的非转义指令。

    2.9 assign  指令

    2.9.1  概要

    <#assign name=value>

    or

    <#assign name1=value1 name2=value2 ...nameN=valueN>

    or

    <#assign same as above... in namespacehash>

    or

    <#assign name>

    capture this

    </#assign>

    or

    <#assign name in namespacehash>

    capture this

    </#assign>

    这里:

      name :变量的名字。不是表达式。而它可以本写作是字符串,如果变量名包含保

    留字符这是很有用的,比如 <#assign "foo-bar" = 1> 。注意这个字符串

    没有展开插值(如 "${foo}" )。

      value :存储的值。是表达式。

      namespacehash :(通过 import )为命名空间创建的哈希表。是表达式。

    2.9.2  描述

    使用这个指令你可以创建一个新的变量,或者替换一个已经存在的变量。注意仅仅顶级

    变量可以被创建/替换(也就是说你不能创建/替换 some_hash.subvar ,除了

    some_hash )。

    关于变量的更多内容,请阅读:模板开发指南/其他/在模板中定义变量

    例如: seasons 变量可以存储一个序列:

    <#assign seasons = ["winter","spring", "summer", "autumn"]>

    比如:变量 test 中存储增长的数字:

    <#assign test = test + 1>

    作为一个方便的特性,你可以使用一个 assign 标记来进行多次定义。比如这个会做

    上面两个例子中相同的事情:

    <#assign

    seasons = ["winter","spring", "summer", "autumn"]

    test = test + 1

    如果你知道什么是命名空间: assign 指令在命名空间中创建变量。通常它在当前的

    命名空间(也就是和标签所在模板关联的命名空间)中创建变量。但如果你是用了 in

    namespacehash ,那么你可以用另外一个命名空间来创建/替换变量。比如,这里你在

    命名空间中 /mylib.ftl 创建/替换了变量 bgColor 。

    <#import "/mylib.ftl" asmy>

    <#assign bgColor="red" inmy>

    assign 的极端使用是当它捕捉它的开始标记和结束标记中间生成的输出时。也就是

    说,在标记之间打印的东西将不会在页面上显示,但是会存储在变量中。比如:

    <#macro myMacro>foo</#macro>

    <#assign x>

    <#list 1..3 as n>

    ${n} <@myMacro />

    </#list>

    </#assign>

    Number of words: ${x?word_list?size}

    ${x}

     

    将会打印:

    Number of words: 6

    1 foo

    2 foo

    3 foo

    请注意你不应该使用它来往字符串中插入变量:

    <#assign x>Hello${user}!</#assign> <#-- BAD PRACTICE! -->

    你可以这么来写:

    <#assign x="Hello${user}!">

    2.10 global  指令

    2.10.1  概要

    <#global name=value>

    or

    <#global name1=value1 name2=value2 ...nameN=valueN>

    or

    <#global name>

    capture this

    </#global>

    这里:

      name :变量的名称。它不是表达式。但它可以被写作是字符串形式,如果变量名

    包含保留字符这是很有用的,比如 <#global "foo-bar" = 1> 。注意这个

    字符串没有扩展插值(如 "${foo}" )。

      value :存储的值,是表达式。

    2.10.2  描述

    这个指令和 assign 相似,但是被创建的变量在所有的命名空间中都可见,但又不会

    存在于任何一个命名空间之中。精确地说,正如你会创建(或替换)一个数据模型变量。因

    此,这个变量是全局的。如果在数据模型中,一个相同名称的变量存在的话,它会被使用这

    个指令创建的变量隐藏。如果在当前的命名空间中,一个相同名称的变量存在的话,那么会

    隐藏由 global 指令创建的变量。

    比如 <#global x = 1> ,用创建了一个变量,那么在所有命名空间中x 都可见,

    除非另外一个称为 x 的变量隐藏了它(比如你已经用 <#assign x = 2> 创建了一个变

    量)。这种情形下,你可以使用特殊变量 globals ,比如 ${.globals.x} 。注意使用

    globals 你看到所有全局可访问的变量;不但由global 指令创建的变量,而且是数据

    模型中的变量。

    自定义 JSP 标记的用户请注意:用这个指令创建的变量集合和 JSP 页面范围对应。这就

    意味着,如果自定义 JSP 标记想获得一个页面范围的属性(page-scope bean),在当前命名

    空间中一个相同名称的变量,从 JSP 标记的观点出发,将不会隐藏。

    <#assign x>Hello${user}!</#assign> <#-- BAD PRACTICE! -->

    <#assign x="Hello${user}!">

    2.11 local  指令

    2.11.1  概要

    <#local name=value>

    or

    <#local name1=value1 name2=value2 ...nameN=valueN>

    or

    <#local name>

    capture this

    </#local>

    这里:

      name :在 root 中局部对象的名称。它不是一个表达式。但它可以被写作是字符串

    形式,如果变量名包含保留字符这是很有用的,比如 <#global "foo-bar" =

    1> 。注意这个字符串没有扩展插值(如"${foo}" )。

      value :存储的值,是表达式。

    2.11.2  描述

    它和 assign 指令类似,但是它创建或替换局部变量。这仅仅在宏和方法的内部定义

    才会有作用。

    要获得更多关于变量的信息,可以阅读:模板开发指南/其他/在模板中定义变量部分内

    容。

    2.12 setting  指令

    2.12.1  概要

    <#setting name=value>

    这里:

      name :设置的名称。不是表达式!

      value :设置的值,是表达式。

    2.12.2  描述

    为进一步的处理而设置。设置是影响 FreeMarker 行为的值。新值仅仅在被设置的模板

    处理时出现,而且不触碰模板本身。设置的初始值是由程序员设定的(参加:程序开发指南

    /配置/设置信息部分的内容)。

    支持的设置有:

      locale :输出的本地化(语言)。它可以影响数字,日期等显示格式。它的值是

    由语言编码(小写两个字母的 ISO-639 编码)和可选的国家码(大写的两个字母

    ISO-3166 编码)组成的字符串,它们以下划线相分隔,如果我们已经指定了国家那

    么一个可选的不同编码(不是标准的)会以下划线分隔开国家。合法的值示例:

    en , en_US , en_US_MAC 。FreeMarker 会尝试使用最特定可用的本地化设置,

    所以如果你指定了 en_US_MAC ,但是它不被知道,那么它会尝试 en_US ,然

    后尝试 en ,然后是计算机(可能是由程序员设置的)默认的本地化设置。

      number_format :当没有指定确定的格式化形式时,用来转化数字到字符串形

    式的数字格式化设置。可以是下列中的一个预定义值 number (默认的),

    computer , currency ,或 percent 。此外,以 Java 小数数字格式化语法

    书写的任意的格式化形式也可以被指定。更多的格式形式请参考处理数字的内建函

    数 string 。

      boolean_format :以逗号分隔的一对字符串来分别展示true 和 false 值,当

    没有指定确定的格式时,转换布尔值到字符串。默认值是 "true,false" 。也

    可以参考处理布尔值的内建函数 string 。

      date_format , time_format ,datetime_format :,当没有指定确定

    的格式时,用来转换日期到字符串的日期/时间格式形式。如 ${someDate} .

    date_format 这个情形,它只影响和日期相关的日期(年,月,日),

    time_format 只 影 响 和 时 间 相 关 的 日 期 ( 时 , 分 , 秒 , 毫 秒 ),

    datetime_format 只影响时间日期类型的日期(年,月,日,时,分,秒,

    毫秒)。这个设置的可能值和处理日期的内建函数 string 的参数相似;可以在

    那部分参考更多内容。比如 "short" , "long_medium" ,"MM/dd/yyyy" 。

      time_zone :时区的名称来显示并格式化时间。默认情况下,使用系统的时区。

    也可以是 Java 时区 API 中的任何值。比如: "GMT" , "GMT+2" , "GMT-1:30" ,

    "CET" ,"PST" , "America/Los_Angeles" 。

      url_escaping_charset :用来 URL 转义(比如${foo?url} )的字符集,

    来计算转义( %XX )的部分。通常包含 FreeMarker 的框架应该设置它,所以你不

    应该在模板中来设置。(程序员可以阅读程序开发指南/其他/字符集问题部分来获

    取更多内容)

      classic_compatible :这是对专业人员来说的,它的值应该是一个布尔值。

    参见 freemarker.template.Configurable 的文档来获取更多信息。

    示例:假设模板的初始本地化是 hu(Hungarian,匈牙利),那么这个:

    ${1.2}

    <#setting locale="en_US">

    ${1.2}

    将会输出:

    1,2

    1.2

    因为匈牙利人使用逗号作为小数的分隔符,而美国人使用点。

    2.13  用户自定义指令(<@...>)

    2.13.1  概要

    <@user_def_dir_exp param1=val1param2=val2 ... paramN=valN/>

    (注意 XML 风格, > 之前的 / )

    如果你需要循环变量,请参考 2.13.2.2 节内容。

    <@user_def_dir_exp param1=val1param2=val2 ... paramN=valN ;

    lv1, lv2, ..., lvN/>

    或者和上面两个相同但是使用结束标签,请参考 2.13.2.1 节内容。

    <@user_def_dir_exp ...>

    ...

    </@user_def_dir_exp>

    <@user_def_dir_exp ...>

    ...

    </@>

    或和上面的相同但是使用位置参数传递,请参考 2.13.2.3 节内容

    <@user val1, val2, ..., valN/>

    等…

    这里:

      user_def_dir_exp :表达式算作是自定义指令(比如宏),将会被调用。

      param1 , param2 等:参数的名称,它们不是表达式。

      val1 , val2 等:参数的值,它们是表达式。

      lv1 , lv2 等:循环变量的名称,它们不是表达式。

    参数的数量可以是 0(也就是没有参数)。

    参数的顺序并不重要(除非你使用了位置参数传递)。参数名称必须唯一。在参数名中

    小写和大写的字母被认为是不同的字母(也就是 Color 和 color 是不同的)。

    2.13.2  描述

    这将调用用户自定义指令,比如宏。参数的含义,支持和需要的参数的设置依赖于具体

    的自定义指令。

    你可以阅读模板开发指南/其他/定义你自己的指令部分。

    示例 1:调用存储在变量 html_escape 中的指令:

    <@html_escape>

    a < b

    Romeo & Juliet

    </@html_escape>

    输出:

    a &lt; b

    Romeo &amp; Juliet

    示例 2:调用有参数的宏

    <@list items=["mouse","elephant", "python"] title="Animals"/>

    ...

    <#macro list title items>

    <p>${title?cap_first}:

    <ul>

    <#list items as x>

    <li>${x?cap_first}

    </#list>

    </ul>

    </#macro>

    输出:

    <p>Animals:

    <ul>

    <li>Mouse

    <li>Elephant

    <li>Python

    </ul>

    ...

    2.13.2.1  结束标签

    你可以在结束标签中忽略 user_def_dir_exp 。也就是说,你可以写 </@> 来替代

    </@anything> 。这个规则当表达式user_def_dir_exp 太复杂时非常有用,因为

    你不需要在结束标签中重复表达式。此外,如果表达式包含比简单变量名和点还多的表达式,

    你就 不 能 再 重 复 它 们 了 。 比 如

    <@a_hash[a_method()]>...</@a_hash[a_method()]>就是错的,你必须

    写为 <@a_hash[a_method()]>...</@> 。 但 是

    <@a_hash.foo>...</@a_hash.foo> 是可以的。

    2.13.2.2  循环变量

    一些自定义指令创建循环变量(和 list 指令相似)。正如预定义指令(如 list )一

    样,当你调用这个指令(如 <#list foos as foo>...</#list> 中的 foo )时循

    环变量的名称就给定了,而变量的值是由指令本身设置的。在自定义指令的情形下,语法是

    循环变量的名称在分号之后给定。比如:

    <@myRepeatMacro count=4 ; x, last>

    ${x}. Something... <#if last> Thiswas the last!</#if>

    </@myRepeatMacro>

    注意由自定义指令创建的循环变量数量和分号之后指定的循环变量数量需要不匹配。也

    就是说,如果你对重复是否是最后一个不感兴趣,你可以简单来写:

    <@myRepeatMacro count=4 ; x>

    ${x}. Something...

    </@myRepeatMacro>

    或者你可以:

    <@myRepeatMacro count=4>

    Something...

    </@myRepeatMacro>

    此外,如果你在分号之后指定更多循环变量而不是自定义指令创建的,也不会引起错误,

    只是最后的循环变量不能被创建(也就是在嵌套内容中那些将是未定义的)。尝试使用未定

    义的循环变量,就会引起错误(除非你使用如 ?default 这样的内建函数),因为你尝试

    访问了一个不存在的变量。

    请参考模板开发指南/其他/定义你自己的指令部分来获取更多内容。

    2.13.2.3  位置参数传递

    位置参数传递(如 <@heading "Preface", 1/> )是正常命名参数传递(如

    <@heading title="Preface"level=1/> )的速记形式,这里忽略了参数的名

    称。如果自定义指令只有一个参数,或者对于经常使用的自定义指令它参数的顺序很好记忆,

    速记形式应该被应用。为了应用这种形式,你不得不了解声明的命名参数的顺序(如果指令

    只有一个参数这是很琐碎的)。也就是,如果 heading 被创建为 <#macro heading

    title level>... , 那 么 <@heading"Preface", 1/> 和 <@heading

    title="Preface"  level=1/> ( 或 <@heading  level=1

    title="Preface"/> ;如果你使用参数名称,那顺序就不重要了)是相等的。要注意

    位置参数传递现在仅仅支持宏定义。

    2.14 macro ,nested ,return  指令

    2.14.1  概要

    <#macro name param1 param2 ...paramN>

    ...

    <#nested loopvar1, loopvar2, ...,loopvarN>

    ...

    <#return>

    ...

    </#macro>

    这里:

      name :宏变量的名称,它不是表达式。然而,它可以被写成字符串的形式,如果

    <@myRepeatMacro count=4 ; x>

    ${x}. Something...

    </@myRepeatMacro>

    <@myRepeatMacro count=4>

    Something...

    </@myRepeatMacro>

    宏名称中包含保留字符时这是很有用的,比如 <#macro "foo-bar">... 。

    注意这个字符串没有扩展插值(如 "${foo}" )。

      param1 , param2 等: 局部变量的名称,存储参数的值(不是表达式),在 =

    号后面和默认值(是表达式)是可选的。默认值也可以是另外一个参数,比如

    <#macro section title label=title> 。

      paramN ,最后一个参数,可以可选的包含一个尾部省略( ... ),这就意味着宏

    接受可变的参数数量。如果使用命名参数来调用, paramN 将会是包含给宏的所

    有未声明的键/值对的哈希表。如果使用位置参数来调用, paramN 将是额外参数

    的序列。

      loopvar1 , loopvar2 等:可选的循环变量的值,是nested 指令想为嵌套

    内容创建的。这些都是表达式。

    return 和 nested 指令是可选的,而且可以在<#macro> 和 </#macro> 之间被

    用在任意位置和任意次数。

    没有默认值的参数必须在有默认值参数( paramName=defaultValue )之前。

    2.14.2  描述

    创建一个宏变量(在当前命名空间中,如果你知道命名空间的特性)。如果你对宏和自

    定义指令不了解,你应该阅读模板开发指南/其他/定义你自己的指令部分。

    宏变量存储模板片段(称为宏定义体)可以被用作自定义指令。这个变量也存储自定义

    指令的被允许的参数名。当你将这个变量作为指令时,你必须给所有参数赋值,除了有默认

    值的参数。默认值当且仅当你调用宏而不给参数赋值时起作用。

    变量会在模板开始时被创建;而不管 macro 指令放置在模板的什么位置。因此,这样

    也可以:

    <#-- call the macro; the macro variableis already created: -->

    <@test/>

    ...

    <#-- create the macro variable: -->

    <#macro test>

    Test text

    </#macro>

    然而,如果宏定义被插在 include 指令中,它们直到 FreeMarker 执行 include 指

    令时参会可用。

    例如:没有参数的宏:

    <#macro test>

    Test text

    </#macro>

    <#-- call the macro: -->

    <@test/>

    输出:

    Test text

    示例:有参数的宏:

    <#macro test foo bar baaz>

    Test text, and the params: ${foo}, ${bar},${baaz}

    </#macro>

    <#-- call the macro: -->

    <@test foo="a"bar="b" baaz=5*5-2/>

    输出:

    Test text, and the params: a, b, 23

    示例:有参数和默认值参数的宏:

    <#macro test foo bar="Bar"baaz=-1>

    Test text, and the params: ${foo}, ${bar},${baaz}

    </#macro>

    <@test foo="a"bar="b" baaz=5*5-2/>

    <@test foo="a"bar="b"/>

    <@test foo="a" baaz=5*5-2/>

    <@test foo="a"/>

    输出:

    Test text, and the params: a, b, 23

    Test text, and the params: a, b, -1

    Test text, and the params: a, Bar, 23

    Test text, and the params: a, Bar, -1

    示例:一个复杂的宏。

    <#macro list title items>

    <p>${title?cap_first}:

    <ul>

    <#list items as x>

    <li>${x?cap_first}

    </#list>

    </ul>

    </#macro>

    <@list items=["mouse","elephant", "python"] title="Animals"/>

    输出:

    <p>Animals:

    <ul>

    <li>Mouse

    <li>Elephant

    <li>Python

    </ul>

    示例:一个支持多个参数和命名参数的宏:

    <#macro img src extra...>

    <img src="/context${src?html}"

    <#list extra?keys as attr>

    ${attr}="${extra[attr]?html}"

    </#list>

    </#macro>

    <@img src="/images/test.png"width=100 height=50 alt="Test"/>

    输出:

    <imgsrc="/context/images/test.png"

    alt="Test"

    height="50"

    width="100"

    2.14.2.1 nested

    nested 指令执行自定义指令开始和结束标签中间的模板片段。嵌套的片段可以包含

    模板中合法的任意内容:插值,指令…等。它在上下文环境中被执行,也就是宏被调用的地

    方,而不是宏定义体的上下文中。因此,比如,你不能看到嵌套部分的宏的局部变量。如果

    你没有调用 nested 指令,自定义指令开始和结束标记中的部分将会被忽略。

    示例:

    <#macro do_twice>

    1. <#nested>

    2. <#nested>

    </#macro>

    <@do_twice>something</@do_twice>

    输出:

    1. something

    2. something

    嵌套指令可以对嵌套内容创建循环变量。比如:

    <#macro do_thrice>

    <#nested 1>

    <#nested 2>

    <#nested 3>

    </#macro>

    <@do_thrice ; x>

    ${x} Anything.

    </@do_thrice>

    这会打印:

    1 Anything.

    2 Anything.

    3 Anything.

    一个更为复杂的示例:

    <#macro repeat count>

    <#list 1..count as x>

    <#nested x, x/2, x==count>

    </#list>

    </#macro>

    <@repeat count=4 ; c, halfc, last>

    ${c}. ${halfc}<#if last>Last!</#if>

    </@repeat>

    输出将是:

    1. 0.5

    2. 1

    3. 1.5

    4. 2 Last!

    2.14.2.2 return

    使用 return 指令,你可以在任意位置留下一个宏或函数定义。比如:

    <#macro test>

    Test text

    <#return>

    Will not be printed.

    </#macro>

    <@test/>

    输出:

    Test text

    2.15 function ,return  指令

    2.15.1  概要

    <#function name param1 param2 ...paramN>

    ...

    <#return returnValue>

    </#function>

    这里:

      name :方法变量的名称(不是表达式)

      param1 , param2 等:局部变量的名称,存储参数的值(不是表达式),在 = 号

    后面和默认值(是表达式)是可选的。

      paramN ,最后一个参数,可以可选的包含一个尾部省略( ... ),这就意味着宏

    接受可变的参数数量。局部变量 paramN 将是额外参数的序列。

      returnValue :计算方法调用值的表达式。

    return 指令可以在 <#function...> 和 </#function> 之间被用在任意位置

    和任意次数。

    没有默认值的参数必须在有默认值参数( paramName=defaultValue )之前。

    2.15.2  描述

    创建一个方法变量(在当前命名空间中,如果你知道命名空间的特性)。这个指令和

    macro 指令的工作方式一样,除了 return 指令必须有一个参数来指定方法的返回值,

    而且视图写入输出的将会被忽略。如果到达 </#function> (也就是说没有 return

    returnValue ),那么方法的返回值就是未定义变量。

    示例 1:创建一个方法来计算两个树的平均值:

    <#function avg x y>

    <#return (x + y) / 2>

    </#function>

    ${avg(10, 20)}

    将会打印:

    15

    示例 2:创建一个方法来计算多个数字的平均值:

    <#function avg nums...>

    <#local sum = 0>

    <#list nums as num>

    <#local sum = sum + num>

    </#list>

    <#if nums?size != 0>

    <#return sum / nums?size>

    </#if>

    </#function>

    ${avg(10, 20)}

    ${avg(10, 20, 30, 40)}

    ${avg()!"N/A"}

    会打印:

    15

    25

    N/A

    2.16 flush  指令

    2.16.1  概要

    <#flush>

    2.16.2  描述

    当 FreeMarker 生成输出时,他/她通常存储生成的输出内容然后以一个或几个大片段送

    到客户端。这种发送的行为被称为冲洗(就像冲厕所)。尽管冲洗是自动发生的,有时你想

    在模板处理时的一点强制执行,这就是 flush 指令要做的。如果需要它,那么程序员会告

    诉你;通常你不必使用这个指令。自动冲洗的机制和 flush 指令的明确效果是在程序员的

    控制之下的。

    冲洗简单调用当前使用 java.io.Writer 实例的 flush方法。整体的缓冲和冲洗机制在 writer

    (就是传递给 Template.process 方法的参数)中已经实现了;FreeMarker 不用来处

    理它。

    2.17 stop  指令

    2.17.1  概要

    <#stop>

    <#stop reason>

    这里:

      reason :关于终端原因的信息化消息。表达式被算做是字符串。

    2.17.2  描述

    中止模板的处理。这是一种像紧急中断的机制:不要在普通情况下使用。抛出

    StopException 异常会发生中止,而且StopException 会持有 reason 参数的值。

    15

    25

    N/A

    2.18 ftl  指令

    2.18.1  概要

    <#ftl param1=value1 param2=value2 ...paramN=valueN>

    这里:

      param1 , param2 等:参数的名字,不是表达式。允许的参数有 encoding ,

    strip_whitespace , strip_text 等。参加下面。

      value1 , value2 等:参数的值。必须是一个常量表达式(如 true ,或

    "ISO-8859-5" ,或 {x:1, y:2})。它不能用变量。

    2.18.2  描述

    告诉 FreeMarker 和其他工具关于模板的信息,而且帮助程序员来自动检测一个文本文

    件是否是 FTL 文件。这个指令,如果存在,必须是模板的第一句代码。该指令前的任何空白

    将被忽略。这个指令的老式语法(#-less)格式是不被支持的。

    一些设置(编码方式,空白剥离等)在这里给定的话就有最高的优先级,也就是说,它

    们直接作用于模板而不管其他任何 FreeMarker 配置的设置。

    参数:

      encoding :使用这个你可以在模板文件中为模板指定编码方式(字符集) (也就

    是说 , 这 是 新 创 建 Template 的 encoding 设 置 , 而 且

    Configuration.getTemplate 中的 encoding 参数不能覆盖它。) 。要

    注意,FreeMarker 会尝试会和自动猜测的编码方式(这依赖于程序员对 FreeMarker

    的配置)找到 ftl 指令并解释它,然后就会发现 ftl 指令会让一些东西有所不

    同,之后以新的编码方式来读取模板。因此,直到 ftl 标记使用第一个编码方式

    读取到结尾,模板必须是合法的 FTL。这个参数的合法值是从 IANA 字符集注册表

    中参考 MIME 中的字符集名称。比如 ISO-8859-5,UTF-8 或 Shift_JIS。

      strip_whitespace :这将开启/关闭空白剥离。合法的值是布尔值常量 true

    和 flase (为了向下兼容,字符串 "yes" , "no" , "true" , "false" 也是

    可以的)。默认值(也就是当你不使用这个参数时)是依赖于程序员对 FreeMarker

    的配置,但是对新的项目还应该是 true 。

      strip_text :当开启它时,当模板被解析时模板中所有顶级文本被移除。这个

    不会影响宏,指令,或插值中的文本。合法值是布尔值常量 true 和 flase (为

    了向下兼容,字符串 "yes" , "no" , "true" , "false" 也是可以的)。默认

    值(也就是当你不使用这个参数时)是 flase 。

      strict_syntax :这会开启/关闭“严格的语法”。合法值是布尔值常量 true

    和 flase (为了向下兼容,字符串 "yes" , "no" , "true" , "false" 也是

    可以的)。默认值(也就是当你不使用这个参数时)是依赖于程序员对 FreeMarker

    的配 置 , 但 是 对 新 的 项 目 还 应 该 是 true ( 程 序 员 : 对 于

    config.setStrictSyntaxMode(true); ,你应该明确设置它为true )。

    要获取更多信息,可以参考:废弃的 FTL 结构/老式 FTL 语法部分。

      ns_prefixes : 这 是 关 联 节 点 命 名 空 间 前 缀 的 哈 希 表 。 比 如 :

    {"e":"http://example.com/ebook",

    "vg":"http://example.com/vektorGraphics"}。这通常是用于

    XML 处理的,前缀可以用于 XML 查询,但是它也影响visit 和 recurse 指令

    的工作。相同节点的命名空间只能注册一个前缀(否则会发生错误),所以在前缀

    和节点命名空间中是一对一的关系。前缀 D 和 N 是保留的。如果你注册前缀 D ,

    那么除了你可以使用前缀 D 来关联节点命名空间,你也可以设置默认的节点命名

    空间。前缀 N 就不能被注册;当且仅当前缀 D 被注册时, N 被用来表示在特定位

    置没有节点命名空间的节点。(要参考默认节点命名空间的用法, N ,一般的前缀,

    可以看 XML 处理中 visit 和 recurse 指令。) ns_prefixes 的作用限制在

    单独的 FTL 命名空间内,换句话说,就是为模板创建的 FTL 命名空间内。这也意味

    着 ns_prefixes 当一个 FTL 命名空间为包含它的模板所创建时才有作用,否则

    ns_prefixes 参数没有效果。FTL 命名空间当下列情况下为模板创建:(a)模

    板是“主”模板,也就是说它不是被 <#include ...> 来调用的模板,但是直

    接被调用的(和 process 一起的 Template 或 Environment 类的方法);

    (b)模板直接被 <#import ...> 调用。

      attributes :这是关联模板任意属性(名-值对)的哈希表。属性的值可以是

    任意类型(字符串,数字,序列等)。FreeMarker 不会尝试去理解属性的含义。它

    是由封装 FreeMarker(比如一个 Web 应用框架)的应用程序决定的。因此,允许

    的属性的设置是它们依赖的应用(Web 应用框架)的语义。程序员:你可以通过

    关联 Template 对象 的getCustomAttributeNames 和

    getCustomAttribute 方法(从freemarker.core.Configurable

    继承而来)获得属性。如当模板被解析时,关联 Template 对象的模板属性,属

    性可以在任意时间被读取,而模板不需要被执行。上面提到的方法返回未包装的属

    性值,也就是说,使用 FreeMarker 独立的类型,如 java.util.List 。

    这个指令也决定模板是否使用尖括号语法(比如 <#include 'foo.ftl'> )或方

    括号语法(如 [#include 'foo.ftl'] )。简单而言,这个指令使用的语法将会是整

    个模板使用的语法,而不管 FreeMarker 是如何配置的。

    2.19 t ,lt ,rt  指令

    2.19.1  概要

    <#t>

    <#lt>

    <#rt>

    <#nt>

    2.19.2  描述

    这些指令,指示 FreeMarker 去忽略标记中行的特定的空白

      t (整体削减):忽略本行中首和尾的所有空白。

      lt (左侧削减):忽略本行中首部所有的空白。

      rt (右侧削减):忽略本行中尾部所有的空白。

    这里:

      “首部空白”表示本行所有空格和制表符(和其他根据 UNICODE 中的空白字符,

    除了换行符)在第一个非空白字符之前。

      “尾部空白”表示本行所有的空格和制表符(和其他根据 UNICODE 中的空白字符,

    除了换行符)在最后一个非空白字符之后,还有行末尾的换行符。

    理解这些检查模板本身的指令是很重要的,而不是当你合并数据模型时,模板生成的输

    出。(也就是说,空白的移除发生在解析阶段)

    比如这个:

    --

    1 <#t>

    2<#t>

    3<#lt>

    4

    5<#rt>

    6

    --

    将会输出:

    --

    1 23

    4

    5 6

    --

    这些指令在行内的放置不重要。也就是说,不管你是将它们放在行的开头,或是行的末

    尾,或是在行的中间,效果都是一样的。

    2.20 nt  指令

    2.20.1  概要

    <#nt>

    2.20.2  描述

    “不要削减”。这个指令关闭行中出现的空白削减。它也关闭其他同一行中出现的削减

    指令( t , lt , rt 的效果)。

    2.21 attempt ,recover  指令

    2.21.1  概要

    <#attempt>

    attempt block

    <#recover>

    recover block

    </#attempt>

    这里:

      attempt block :任意内容的模板块。这是会被执行的,但是如果期间发生了

    错误,那么这块内容的输出将会回滚,之后 recover block 就会被执行。

      recover block : 任意内容的模板块。这个仅在attempt block 执行期

    间发生错误时被执行。你可以在这里打印错误信息或其他操作。

    recover 是命令的。 attempt/recover可以嵌套在其他 attempt s 或

    recover s 中。

    注意:

    上面 的 格 式 是 从 2.3.3 版 本 开 始 支 持 的 , 之 前 它 是

    <#attempt>...<#recover>...</#recover>,也支持向下兼容。此外,这些

    指令在 FreeMarker 2.3.1 版本时引入的,在 2.3 版本中是不存在的。

    2.12.2  描述

    如果你想让页面成功输出内容,尽管它在页面特定位置发生错误也这样,那么这些指令

    就是有用的。如果一个错误在 attempt block 执行期间发生,那么模板执行就会中止,

    但是 recover block 会代替 attempt block 执行。如果在 attempt block

    执行期间没有发生错误,那么 recover block 就会忽略。一个简单的示例如下:

    Primary content

    <#attempt>

    Optional content: ${thisMayFails}

    <#recover>

    Ops! The optional content is not available.

    </#attempt>

    Primary content continued

    如果 thisMayFails 变量不存在,那么输出:

    Primary content

    Ops! The optional content is not available.

    Primary content continued

    如果 thisMayFails 变量存在而且值为 123 ,那么输出:

    Primary content

    Optional content: 123

    Primary content continued

    attempt block 块有多或没有的语义:不管attempt block 块的完整内容是

    否输出(没有发生错误),或者在 attempt block (没有发生错误)块执行时没有输出

    结果。比如,上面的示例,发生在“Optional content”之后的失败被打印出来了,而没有在

    “Ops!”之前输出。( 这是在 attemptblock 块内,侵入的输出缓冲的实现,就连 flush

    指令也会送输出到客户端。)

    为了阻止来自上面示例的误解: attempt / recover 不仅仅是处理未定义变量(对

    于那个可以使用不存在变量控制符来处理)。它可以处理发生在块执行期间的各种类型的错

    误(而不是语法错误,这会在执行之前被检测到)。它的目的是包围更大的模板段,错误可

    能发生在很多地方。比如,你在模板中有一个部分,来处理打印广告,但是它不是页面的主

    要内容,所以你不想你的页面因为一些打印广告(也可能是短暂的数据库故障)的错误而挂

    掉。所以你将整个广告区域放在 attempt block 块中。

    在一些环境下,程序员配置 FreeMarker,所以对于特定的错误,它不会中止模板的执行,

    在打印一些错误提示信息到输出(请参考:程序开发指南/配置/错误处理部分)中之后,而

    是继续执行。 attempt 指令不会将这些抑制的错误视为错误。

    在 recover block 块中,错误的信息存在特殊变量 error 中。不要忘了以点开

    始引用特殊变量(比如: ${.error} )

    在模板执行期间发生的错误通常被被日志记录,不管是否发生在 attempt block

    块中。

    2.22 visit ,recurse ,fallback  指令

    2.22.1  概要

    <#visit node using namespace>

    <#visit node>

    <#recurse node using namespace>

    <#recurse node>

    <#recurse using namespace>

    <#recurse>

    Primary content

    Ops! The optional content is not available.

    Primary content continued

    Primary content

    Optional content: 123

    Primary content continued

    <#fallback>

    这里:

      node :算作节点变量的表达式。

      namespace :一个命名空间,或者是命名空间的序列。命名空间可以以命名空

    间哈希表(又称为根哈希表)给定,或者可以引入一个存储模板路径的字符串。代

    替命名空间哈希表,你也可以使用普通哈希表。

    2.22.2  描述

    visit 和 recurse 指令是用来递归处理树的。在实践中,这通常被用来处理 XML。

    2.22.2.1 visit  指令

    当你调用了 <#visit node> 时,它看上去像用户自定义指令(如宏)来调用从节点

    名称( node?node_name )和命名空间( node?node_namesoace )中有名称扣除

    的节点。名称扣除的规则:

      如果节点不支持节点命名空间(如 XML 中的文本节点),那么这个指令名仅仅是节

    点的名称( node?node_name )。如果 getNodeNamespace 方法返回 null

    时节点就不支持节点命名空间了。

      如果节点支持节点命名空间(如 XML 中的元素节点),那么从节点命名空间中的前

    缀扣除可能在节点名称前和一个做为分隔符(比如 e:book )的冒号追加上去。

    前缀,以及是否使用前缀,依赖于何种前缀在 FTL 命名空间中用 ftl 指令的

    ns_prefixes 参数注册的,那里 visit 寻找控制器指令(visit 调用的相

    同 FTL 命名空间不是重要的,后面你将会看到)。具体来说,如果没有用

    ns_prefixes 注册默认的命名空间,那么对于不属于任何命名空间( 当

    getNodeNamespace 返回"" )的节点 来说 就 不使 用前 缀。 如果 使 用

    ns_prefixes 给不属于任意命名空间的节点注册了默认命名空间,那么就使用

    前缀 N ,而对于属于默认节点命名空间的节点就不使用前缀了。否则,这两种情况

    下,用 ns_prefixes 关联节点命名空间的前缀已经被使用了。如果没有关联

    节点命名空间的节点前缀,那么 visit 仅仅就好像没有以合适的名称发现指令。

    自定义指令调用的节点对于特殊变量 .node 是可用的。比如:

    <#-- 假设nodeWithNameX?node_name是"x" -->

    <#visit nodeWithNameX>

    Done.

    <#macro x>

    Now I'm handling a node that has the name"x".

    Just to show how to access this node: thisnode has

    ${.node?children?size} children.

    </#macro>

    输出就像:

    Now I'm handling a node that has the name"x".

    Just to show how to access this node: thisnode has

    ${.node?children?size} children.

    </#macro>

    如果使用可选的 using 从句来指定一个或多个命名空间,那么 visit 就会在那么命

    名空间中寻找指令,和先前列表中指定的命名空间都获得优先级。如果指定 using 从句,

    对最后一个未完成的 visit 调用的用 using 从句指定命名空间的命名空间或序列被重用

    了。如果没有这样挂起的 visit 调用,那么当前的命名空间就被使用。比如,如果你执行

    这个模板:

    <#import "n1.ftl" as n1>

    <#import "n2.ftl" as n2>

    <#-- 这会调用 n2.x (因为没有 n1.x):-->

    <#visit nodeWithNameX using [n1, n2]>

    <#-- 这会调用当前命名空间中的 x:-->

    <#visit nodeWithNameX>

    <#macro x>

    Simply x

    </#macro>

    这是 n1.ftl :

    <#macro y>

    n1.y

    </#macro>

    这是 n2.ftl :

    <#macro x>

    n2.x

    <#-- 这会调用call n1.y,因为它 从等待访问调用中继承了"using[n1,

    n2]"-->

    <#visit nodeWithNameY>

    <#-- 这会调用n2.y: -->

    <#visit nodeWithNameY using.namespace>

    </#macro>

    <#macro y>

    n2.y

    </#macro>

    这会打印:

    n2.x

    n1.y

    n2.y

    Simply x

    如果 visit 既没有在和之前描述规则的名称扣除相同名字的FTL 命名空间发现自定义

    指令,那么它会尝试用名称 @node_type 来查找,又如果节点不支持节点类型属性(也

    就是 node?node_type 返回未定义变量),那么使用名称 @default 。对于查找来说,

    它使用和之前描述相同的机制。如果仍然没有找到处理节点的自定义指令,那么 visit 停

    止模板执行,并抛出错误。一些 XML 特定的节点类型在这方面有特殊的处理;参考:XML

    处理指南/声明的 XML 处理/详细内容部分。示例:

    <#-- 假设nodeWithNameX?node_name是"x" -->

    <#visit nodeWithNameX>

    <#-- 假设nodeWithNameY?node_type 是"foo" -->

    <#visit nodeWithNameY>

    <#macro x>

    Handling node x

    </#macro>

    <#macro @foo>

    There was no specific handler for node${node?node_name}

    </#macro>

    这会打印:

    Handling node x

    There was no specific handler for node y

    2.22.2.2 recurse  指令

    <#recurse> 指令是真正纯语义上的指令。它访问节点的所有子节点(而没有节点本

    身)。所以来写:

    <#recurse someNode using someLib>

    和这个是相等的:

    <#list someNode?children aschild><#visit child using

    someLib></#list>

    而目标节点在 recurse 指令中是可选的。如果目标节点没有指定,那就仅仅使

    用 .node 。因此, <#recurse> 这个精炼的指令和下面这个是相同的:

    <#list .node?children aschild><#visit child></#list>

    对于 熟 悉 XSLT 的 用 户 的 评 论 , <#recurse> 是 和 XSLT 中

    <xsl:apply-templates/> 指令相当类似的。

    2.22.2.3 fallback  指令

    正如前面所学的,在 visit 指令的文档中,自定义指令控制的节点也许在多个 FTL 命

    名空间中被搜索。 fallback 指令可以被用在自定义指令中被调用处理节点。它指挥

    FreeMarker 在更多的命名空间(也就是,在当前调用列表中自定义指令命名空间之后的命名

    空间)中来继续搜索自定义指令。如果节点处理器被发现,那么就被调用,否则 fallback

    不会做任何事情。

    这个指令的典型用法是在处理程序库之上写定制层,有时传递控制到定制的库中:

    <#import "/lib/docbook.ftl" asdocbook>

    <#-- 我们使用 docbook 类库,但是我们覆盖一些这个命名空间中的处理器-->

    <#visit document using [.namespace,docbook]>

    <#-- 覆盖"programlisting"处理器,但是要注意它的"role"属性是"java"

    -->

    <#macro programlisting>

    <#if .node.@role[0]!"" =="java">

    <#-- 在这里做一些特殊的事情... -->

    ...

    <#else>

    <#-- 仅仅使用原来的(覆盖的)处理器-->

    <#fallback>

    </#if>

    </#macro>

    展开全文
  • Apache的配置指令概述

    万次阅读 2006-09-25 23:03:00
    //本文是《Apache源代码全景分析》第二卷《体系结构和核心模块》的第八章《配置文件管理》的草稿部分,主要描述Apache指令概念,在后续的章节我们将继续深入Apache的配置文件的处理细节,包括Apache如何...
  • ZPL指令

    万次阅读 2015-03-11 16:14:38
    ^CC,~CC 改变格式指令前缀  ^CC,~CC(改变脱字符)指令是用于改变指令前缀。缺省前缀是脱字符(^)。 ^CC,~CC指令格式  ^CCx,~CCx ^CC,~CC = 改变脱字符 x = 任何...
  • 8086指令格式

    2019-10-16 19:47:18
    8086指令格式 1.组成 8086的指令使用的是1-6Byte(s)的变长指令。由3个部分组成: 操作码(1 Byte) ...先来翻译一下上面的各个部分:D(Destination)目的操作数,W(word)字,MOD 模式/方式,REG(...
  • GCCSIMD指令的应用方法

    千次阅读 2006-12-01 12:22:00
    X86架构上的多媒体应用开发,如果能够使用SIMD指令进行优化, 性能将大大提高。目前,IA-32的SIMD指令包括MMX,... X86的SIMD指令 ...simd instrucitons in X86IA-32 Intel体系结构的指令主要分为以下几类 [1]: 通用
  • 指令系统,寻址方式

    千次阅读 多人点赞 2018-12-23 19:01:52
    用高级语言或者是汇编语言编写的程序,如果要在计算机上执行,必须要利用编译程序或者是汇编程序把高级语言编写的程序,指令,或者是汇编指令变成由0,1代码组成的机器指令,才能够在计算机由计算机的硬件按序进行...
  • 8086汇编指令全集

    万次阅读 多人点赞 2016-04-07 15:08:39
    学习汇编语言,最关键的就在于汇编指令集的掌握以及计算机工作方式的理解,以下是80X86汇编过程经常用到的一些汇编指令。 从功能分类上来说,一共可分为 一、 数据传送指令:MOV、XCHG、LEA、LDS、LES、PUSH、POP...
  • JSP指令标签

    千次阅读 2012-07-17 00:27:39
    指令元素 1.page指令 import session contentType buffer isTreadSafe info errorPage isErrorPage 2.include指令 3.taglib指令 二.脚本元素 1.声明元素 2.表达式元素 3.脚本元素 4.注释元素 三....
  • MIPS cache指令

    千次阅读 2016-03-25 16:33:43
    MIPS cache指令指令编码: 31...26 25...21 20...16 15......0 CACHE(101111) base op offset 格式: cache op,offset(base) 执行op制定的cace操作,16位的offset经符号位扩展,添加到base寄存器以...
  • 指令流水线

    千次阅读 2013-06-05 15:25:44
    最近在看反汇编与逆向的书,有关一些编译优化的知识得到了增长,查阅相关资料,...指令流水线(英语:Instruction pipeline)是为了让计算机和其它数字电子设备能够加速指令的通过速度(单位时间内被运行的指令数量)
  • ARM指令系统

    万次阅读 2017-08-19 22:22:24
    第3章 ARM指令系统 3.1 ARM处理器的指令格式 ...ARM内核属于RISC结构,所以其指令集有着一些独特的特点:指令长度固定,指令格式的种类少,寻址方式简单。由于ARM处理器采用固定长度的32位指令,因此处理器内部
  • 在《Java代码的编译与反编译》,有过关于Java语言的编译和反编译的介绍。我们可以通过javac命令将Java程序的源代码编译成Java字节码,即我们常说的class文件。这是我们通常意义上理解的编译。 但是,字节码并不是...
  • 汇编指令全集

    千次阅读 2016-03-17 14:18:26
    转自:http://blog.sina.com.cn/s/blog_542a1e4a01011nyx.html学习汇编语言,最关键的就在于汇编指令集的掌握以及计算机工作方式的理解,以下是80X86汇编过程经常用到的一些汇编指令。从功能分类上来说,一共可...
  • 学习汇编语言,最关键的就在于汇编指令集的掌握以及计算机工作方式的理解,以下是80X86汇编过程经常用到的一些汇编指令。 从功能分类上来说,一共可分为 一、 数据传送指令:MOV、XCHG、LEA、LDS、LES、PUSH、...
  • Nginx与Lua编写脚本的基本构建块是指令指令用于指定何时运行用户Lua代码以及如何使用结果。 下面是显示指令执行顺序的图。 当一个请求发起一个“子请求”的时候,按照 Nginx 的术语,习惯把前者称为后者的“父...
  • MIPS汇编指令

    2013-10-14 20:17:48
    16位的目标地址意味着,指令的跳转或子函数的位置必须在64K以内(上下32K); 3、所有的动作原理上要求必须在1个时钟周期内完成,一个动作一个阶段; 4、有32个通用寄存器,每个寄存器32位(对32位机)或64位(对64...
  • 浅谈指令流水线

    千次阅读 2020-08-05 14:41:38
      指令流水线作为计算机组成原理一个重要的组成部分,弄清指令流水线的操作步骤对制作CPU有着很大的帮助。本文主要讲述下指令流水线的相关知识。   指令流水线是较单周期指令和多周期指令更有效率的一种方式。...
  • 转载:...Ubootstart.S源码的指令级的详尽解析 版本:v1.7 Crifan Li 摘要
  • 详解VS2010ASP.NET @Page指令属性

    千次阅读 2010-05-11 17:04:00
    @Page指令位于每个ASP.NET页面的顶部,告诉ASP.NET这个具体页面使用什么属性,以及该页面继承的用户控件。ASP.NET页面@Page指令属性有:AspCompat、Async、AsyncTimeout、AutoEventWireup、Buffer、ClassName、...
  • 《计算机组成原理》— 指令系统

    万次阅读 多人点赞 2017-05-29 10:54:45
    基本知识点:指令系统和指令的基本概念,指令格式,指令操作码扩展技术,各种寻址方式及其特点,RISC 和 CISC 指令系统的特点。 重 点:指令格式,指令操作码扩展技术,各种寻址方式及其特点。 难 点:指令格式,...
  • 汇编伪指令

    千次阅读 2012-05-04 21:32:46
    段定义伪指令    段定义伪指令是表示一个段开始和结束的命令,80x86有两种段定义的方式:完整段定义和简化段定义,分别使用不同的段定义伪指令来表示各种段。  1 完整的段定义伪指令  完整段定义伪指令的格式...
  • 指令系统4.1 指令系统的基本概念4.1.1 指令的基本格式1) 四地址指令2) 三地址指令3) 二地址指令4) 一地址指令5) 零地址指令4.1.2 定长操作码指令格式4.1.3 扩展操作码指令格式4.1.4 多字长指令格式4.1.5 指令格式的...
  • 汇编语言指令大全

    万次阅读 多人点赞 2018-06-04 13:48:47
    1.MOV(传送) 指令写法:MOV target,source功能描述:将源操作数source的值复制到target去,source值不变注意事项:1)target不能是CS(代码段寄存器),我的理解是代码段不可写,只可读,所以相应这地方也不能...
  • <%@ Page%> page指令属性

    千次阅读 2014-02-26 13:50:20
    以前只知道会用就行,今天boss让我去理解一下里面的各个属性的用法,于是就有了这篇东凑西凑的文章! 是页面指令,作用于当前页面,指定使用语言、代码页等等。...在.aspx文件使用的页面指令一般有以下几种:

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 77,324
精华内容 30,929
关键字:

以下各个指令中正确的是