迭代器

看了一下nihui的博客,看到了QList的使用,于是有点好奇,然后 csslayer 的讨论更挑起我的兴趣,于是去搜索QList和QMutableListIterator类,找到http://doc.trolltech.com/qq/qq12-qt4-iterators.html,Qt 4 的迭代器的使用。

其实我一直觉得 STL 的迭代器很奇怪,使用*、[]、++、--等运算符来操作,一开始我竟找不到如何使用这一套看似十分强大的库函数的方法,而且编程思路和很多调用不一致。

vector<int> a;
vector<int>::iterator iter = a.begin ();
while (iter != a.end ())
{
  cout << *iter << endl;
  iter ++;
}

为什么就想到用 * 来取元素呢?既然 iter 不是指针,这个操作符就不应该用啊……不过 C++ 还是用了,而且用得也很巧妙……

呃,我觉得很大程度上是因为我一开始学的是 Java 吧,现在越来越觉得其实编程语言的不同本质上是编程思想的不同,编程思想类似的语言,像 Python 和 C++ ,如果你知道怎么用 C++ 会很快就找到如何使用 Python 的。

很好奇为什么 Qt 的 QTL 居然可以和 STL 共存且兼容,可以使用 STL 里 agorithm 头文件里面声明的算法。然后那篇文章里提到的一点

The STL iterator syntax is modeled after the syntax of pointers into an array. This makes it possible to use STL's (or Qt's) generic algorithms on a plain C++ array.

然后我明白了,原来 C++ STL 里面的迭代器模型就是指针,所以所有关于指针取值、前后运算的操作符都可以用在迭代器上,而且是那么自然,更重要的是 C++ 的模板是没有类型检查的,只要最终类型支持在模板函数里用到的那些方法即可。

那篇文章里面提到的有关迭代器内容修改、遍历的问题我觉得都说的很有道理,不一一翻译了,推荐一看

更详细的 Qt 4 文档:http://developer.qt.nokia.com/doc/qt-4.8/containers.html

[笔记]计算机网络·链路层

 

对计算机网络还很不了解,最近要考试了,于是翻了一下笔记,复习一下,不知道笔记内容有没有错?


线路上的基本传输单元是数据帧[data frame],简称Frame。

 
数据链路层的主要任务是将一个原始传输设施转变成一条逻辑的传输线路,在这条传输线路上,所有未检测出来的错误会反映到网络层上。
数据链路层完成这项任务的做法是:让发送方将输入的数据拆开,分装到数据帧(Data frame,通常为几百个或者几千个字节)中,然后顺序地传送这些数据帧。
如果是可靠的服务,则接收方必须确认每一帧都已经正确地接收到了,即给发送方送回一个确认帧(acknowledgement frame)。
 
数据链路(data link) 除了物理线路外,还必须有通信协议来控制这些数据的传输。若把实现这些协议的硬件和软件加到链路上,就构成了数据链路。
 
解决成帧过程中的透明传输问题
• 发送端的数据链路层在数据中出现控制字符“SOH”或“EOT”的前面插入一个转义字符“ESC”(其十六进制编码是1B)。
• 字节填充(byte stuffing)或字符填充(character stuffing)——接收端的数据链路层在将数据送往网络层之前删除插入的转义字符。
如果转义字符也出现数据当中,那么应在转义字
符前面插入一个转义字符。当接收端收到连续的
两个转义字符时,就删除其中前面的一个。
 
数据链路层两个子层:
• 逻辑链路控制(LLC: Logical Link Control)子层
• 媒体接入控制(MAC: Medium Access Control)子层。
 
以太网提供的服务
• 以太网提供的服务是不可靠的交付,即尽最大努力的交付。
• 当目的站收到有差错的数据帧时就丢弃此帧,其他什么也不做。差错的纠正由高层来决定。
• 如果高层发现丢失了一些数据而进行重传,但以太网并不知道这是一个重传的帧,而是当作一个新的数据帧来发送。
 
载波监听多点接入/碰撞检测(CSMA/CD)
Carrier Sense Multiple Access with Collision Detection
载波监听:总线上并没有什么“载波”。因此, “载波监听”就是用电子技术检测总线上有没有其他计算机发送的数据信号。
“碰撞检测”就是计算机边发送数据边检测信道上的信号电压大小。当几个站同时在总线上发送数据时,总线上的信号电压摆动值将会增大(互相叠加)。
 
二进制指数类型退避算法(truncated binary exponential type):碰撞的站在停止发送数据后,要推迟(退避)一个随机时间才能再发送数据。
• 确定基本退避时间,一般是取为争用期 2τ。
• 定义重传次数 k ,k ≤ 10,即k = Min[重传次数, 10]从整数集合[0,1,..., (2k −1)]中随机地取出一个数,记为 r。重传所需的时延就是 r 倍的基本退避时间。
• 当重传达 16 次仍不能成功时即丢弃该帧,并向高层报告。
 
争用期的长度
• 以太网取 51.2 μs 为争用期的长度。对于 10 Mb/s 以太网,在争用期内可发送512 bit,即 64 字节。
• 以太网在发送数据时,若前 64 字节没有发生冲突,则后续的数据就不会发生冲突。
最短有效帧长:
• 如果发生冲突,就一定是在发送的前 64 字节之内。
• 由于一检测到冲突就立即中止发送,这时已经发送出去的数据一定小于 64 字节。
• 以太网规定了最短有效帧长为 64 字节,凡长度小于 64 字节的帧都是由于冲突而异常中止的无效帧。
 
PPP属于广域网范畴,MAC是局域网范畴,按实际情况和环境就选用不同的协议,PPP支持的网络结构只能是点对点,mac支持多点对多点。
 
以太网的MAC帧
 
格式: [目的:6][源:6][类型:2][数据:46-1500][FCS:4]
类型字段用来标志上一层使用的是什么协议,以便把收到的MAC帧的数据上交给上一层的这个协议。
在帧的前面插入的 8 字节中的第一个字段共 7 个字节,是前同步码,用来迅速实现 MAC 帧的比特同步。
第二个字段是帧开始定界符,表示后面的信息就是MAC帧。
 
• 有效的 MAC 帧长度为 64 ~ 1518 字节之间。
• 对于检查出的无效 MAC 帧就简单地丢弃。以太网不负责重传丢弃的帧。
 

Glib::RefPtr<>

因为最近要做C++的作业,所以看了看Gtkmm,我觉得这个库还是挺强大的。对于个人来说,从Java到C++不习惯的就是指针的问题了。Java的“指针”是引用的概念,JVM提供一个引用计数器来对资源进行管理,因此,新建对象后就不用对这个对象的内存管理了(引用计数为0时,清除对象)。而C++的指针却不同,我们用new分配内存后,还必须delete才算把这个对象删除。

而Glib::RefPtr就是在C++中实现JVM的引用计数功能(Reference Pointer)。利用Glib::RefPtr,可以简化对共享资源的管理。

不过对Glib::RefPtr<>的理解还在很初始的阶段。只是翻译一下官方文档……


基本使用

Glib::RefPtr<>的基本功能和普通指针差不多,意味着类似复制、调用方法、自动类型转换、空指针检查等和普通指针是没有差别的。因此

Glib::RefPtr<Gdk::Bitmap> refBitmap = Gdk::Bitmap::create(window,data, width, height);
Glib::RefPtr<Gdk::Bitmap> refBitmap2 = refBitmap;
Glib::RefPtr refBitmap = Gdk::Bitmap::create(window,data, width, height);
int depth = refBitmap->get_depth();
Glib::RefPtr<Gtk::TreeStore> refStore = Gtk::TreeStore::create(columns);
Glib::RefPtr<Gtk::TreeModel> refModel = refStore;
Glib::RefPtr<Gtk::TreeModel> refModel = m_TreeView.get_model();
if(refModel)
{
    int cols_count = refModel->get_n_columns();
    ...
}

但要注意的是,不能直接对Glib::RefPtr<>进行*操作访问内在实例。否则会造成编译错误。

另外,对Glib::RefPtr<>进行强制类型转换也是需要注意的。应该用

Glib::RefPtr<类型名>::cast_dynamic(待转换变量) // or Glib::RefPtr<...>::cast_static(...)

进行强制类型转换。


Constness 

关于Constness(我看到别人把它译为常量性),我看了译文以后也不是很明白,不过指向的译文中好像翻译有点问题……一开始没看懂。

You might not realise that const Something*declares a pointer to a const Something, The pointer can be changed, but not the Something that it points to.

这句话应该说的是“你可能注意到const Something*声明了一个指向const Something的指针,指针可以变,但所指向的内容不能变。”即内容是常量而不是指针是常量。

对于Glib::RefPtr<>来说也是那样,const Something*等价于const Glib::RefPtr<const Something>&,突出const Something应该当作一个整体类型。

不过我也还没怎么看懂官方文档关于常量性的描述的用处,大概在于描述怎样设计参数传递吧,也没怎么弄明白,或许我现在还在简单地进行开发阶段吧。

Glib::RefPtr<>的官方文档:http://developer.gnome.org.sixxs.org/gtkmm-tutorial/unstable/chapter-refptr.html.zh_CN

日记

虽然以前说过不很想写日记,但其实除了日记以外,我也不知道有什么更合适的题目。

 

[稿]NASM 文档 - Section 3

注意:这个翻译并不正式,如果有什么错误,请指出!


第三节:NASM语言结构

3.1 NASM 源码的单行结构

和很多汇编器一样,每一行 NASM 源码都包含四部分的组合(除了宏、预处理指令、汇编指令外,参看 第四节 和 第六节):

标签: 操作指令 操作数 ; 注释

像一些语言那样,大多数部分都是可选的;标签、指令和注释部分出现是允许的。当然,操作数部分是否出现视操作指令格式而定。

NASM 使用反斜线作为换行字符(直译是“行继续字符”(line continuation character));如果一行以反斜线结束,下一行会被认为上一行的一部分。

NASM 没有限制一行中空白部分空白字符的数目,标签前或者指令前可以有任意个空白字符或者没有空白字符。标签后的冒号也是可选的。(也就是说,如果你在一行中仅仅写上了 lodsb 或者意外地输入了 lodab,那么这行仍然会被认为是有效的源码行,不过编译器会认为这行代码定义了一个标签。为了编免这种情况,运行 NASM 的时候加入参数 -w+orphan-labels 会使 NASM 编译器在遇到这种情况的时候输出一句警告。)
× 注意:这里的“空白字符”指 tab 或者 空格。

标签中的有效字符包括字母、数字、_、$、#、@、~、.以及?(注意都是半角英文字符)。但一个标识符必须以字母或者“.”、“_”、“?”开始(以“.”开始的标识符有特殊意义,参见 3.9 节)。在标识符前面加上$可以指示它是标识符而不是一个保留字符;因此,如果你的源码要链接的某些模块中定义了一个叫 eax 的标识符,你可以通过 $eax 来区分这个标识符和寄存器 eax 。一个标识符最长可以有4095个字符。

指令部分可以包含任意的机器指令:奔腾指令、P6指令、浮点指令(FPU)、MMX指令甚至没有公开文档的指令。指令可以加入LOCK、REP、REPE/REPZ或者REPNE/REPNZ作为前缀。NASM也提供了显式的地址大小和操作数大小前缀 A16、A32、A64、064;在第 10 节给出了一个例子。你也可以使用一个段寄存器的名称作为一个指令的前缀:编写 es mov [bx],ax 等价于 编写 mov [es:bx],ax 。我们推荐后者的语法,因为它和 NASM 其他一些语法特征一致,但对于 LODSB 那些没有操作数的指令来说,这种写法是必须的。

一条指令不一定被要求使用一个前缀:对于某些操作,NASM 会制动生成这些前缀的字节码。

相对于机器码, NASM 也支持相当数量的伪指令,在 3.2 节中有描述。

指令操作数可能有一下集中形式:它们可以是寄存器、直接用名字来操作( NASM 对寄存器名字的处理和 Intel MASM的是一致的)或者,它们可以是有效的地址(参见 3.3 节),常量(参见 3.4 节)或者是表达式(参见 3.5 节)。

对于 x87 浮点运算指令,NASM 接受很多种语法:你可以使用像其它 MASM 支持的双操作数指令,或者,在很多情况下,你也可以使用 NASM 原生的单操作数指令。举例来说:

fadd st1 ; 把寄存器 st0 的值加上寄存器 st1 的值的和再传回 st0
fadd st0, st1 ; 效果和上面一句一样

fadd st1, st0 ; 把寄存器 st1 的值加上寄存器 st0 的值的和再传回 st1
fadd to st1 ; 效果和上一句一样

几乎所有 x87 浮点操作都必须使用前缀 DWORD 、 QWORD 或者 TWORD 来指明操作数的内存的大小。

3.2 伪操作指令

伪操作指令是指那些相对于真实的 x86 机器指令外的附加指令,它们被用在语句的指令部分只是因为那样比较方便。

当前编译器预定义的伪指令有 DB 、 DW、DD、DQ、DT、DO 和 DY(指明声明的数据在内存分配的空间大小);相似地,如果声明的是未初始化的数据,那么对应的指令是 RESB、RESW、RESD、RESQ、REST、RESO 和 RESY;INCBIN 命令、EQU 命令和 TIMES 前缀都是当前预定义的伪指令(参见下文)。

3.2.1 DB 以及其它类型:声明初始化的数据

DB 、 DW、DD、DQ、DT、DO 和 DY伪指令在 NASM 中的用法和 MASM 中大部分是一致的,就是用来在输出文件中声明初始化的数据。可以通过下面的例子来看看 NASM 支持的多种语法:

db 0x55 ; 像 byte 0x55
db 0x55,0x56,0x57 ; 连续声明 3 个字节
db 0x55 ; 就像 byte 0x55
db 0x55,0x56,0x57 ; 声明 3 个连续的字节
db 'a',0x55 ; 可以使用字符常量
db 'hello',13,10,'$' ; 字符串常量也可以
dw 0x1234 ; 解释为 0x34 0x12
dw 'a' ; 0x61 0x00 (只是数字)
dw 'ab' ; 0x61 0x62 (字符常量)
dw 'abc' ; 0x61 0x62 0x63 0x00 (字符串)
dd 0x12345678 ; 0x78 0x56 0x34 0x12
dd 1.234567e20 ; 浮点数常量
dq 0x123456789abcdef0 ; 8 字节的常量
dq 1.234567e20 ; 双精度浮点数
dt 1.234567e20 ; 拓展精度的浮点数

注意的是 DT、 DO 和 DY 不接受树脂常量作为操作数。

3.2.2 RESB 以及其它: 声明未初始化数据

RESB、RESW、RESD、RESQ、REST、RESO 和 RESY 是用在一个模块的 BSS 段的:它们声明一些未初始化的存储空间。它们中的每一个都需要一个单操作来指明空间大小。像 2.2.7 节中指出的那样,NASM 不支持 MASM/TASM 的预留未初始化空间的语法(DW ?或者其它相似的语法)。RESB类型的伪指令操作数是一个重要的表达式(见 3.8 节)

举例来说:

buffer: resb 64 ; 保留 64 字节
wordvar: resw 1 ; 保留一个 word 类型空间
realarray resq 10 ; 10 个 real 类型的数据
ymmval: resy 1 ; 一个 YMM 寄存器

3.2.3 INCBIN:引入外部二进制文件

INCBIN 命令是老的 Amiga 汇编器 DevPac 中借来的:这条命令引入一个二进制文件到输出文件中。举例来说,这样可以很轻易地直接把一些图形和音频信息包含到一个游戏的可执行文件中。可以像下面三种方式之一来使用这条指令:

incbin "file.dat" ; 引入整个文件
incbin "file.dat",1024 ; 跳过前 1024 字节
incbin "file.dat",1024,512 ; 跳过前 1024 字节和引入随后的最多到 512 字节内容

3.2.4 EQU:定义常量

EQU 命令把一个符号定义为常量:当 EQU 使用时,源码行必须包含一个标签。 EQU 命令就是把这个标签名定义为一个常量(即命令后的操作数)。这个定义是绝对的,以后将不能更改这个标签的值。所以,像下面:

message db 'hello, world'
msglen equ $-message

这样把 msglen 定义为一个常量 12。 msglen 可能不会被接下俩的代码重新定义。但这也不是一个预处理定义:msglen 的值被运算一次,在定义的时候使用 $ 的值(参见 3.5 节对 $ 符号的解释)而不是每次使用这个常量的时候都把当前的 $ 的值代入计算一次。

3.2.5 TIMES:重复指令

TIMES 前缀可以使使指令重复汇编多次。这条指令的行为就像 MASM 兼容汇编器支持的 DUP 语法,在 NASM 你以这样编写:

zeorobuf: times 64 db 0

或者相似的写法;但 TIMES 的用法不仅仅局限于数据声明。TIMES 的参数不仅可以是一个数值常量,还可以是一个数值表达式,因此,你还可以这样写:

buffer: db 'hello, world'
times 64-$+buffer db ' '

这句话将把 buffer 的内存空间定义为正好是 64 字节。最后,TIMES 还可以应用到普通的指令中,所以,你可以在其中写单调展开的循环:

times 100 movsb

要注意的是 times 100 resb 1 和 resb 100 是没有任何效果上的差别的,但由于汇编器内部实现的差异,后者汇编以后相对前者运行起来快 100 倍。

TIMES 命令后跟的操作数也是一个重要的表达式(见第 3.8 节)。

注意 TIMES 不能应用在宏中:原因是 TIMES 在宏预处理以后才被汇编,使得像 64-$+buffer这样的表达式作为 TIMES 命令的参数。要重复执行多行代码,或者重复执行一个复杂的宏,使用预处理命令 %rep。

3.3 有效寻址(这部分翻译很可能并不准确)

一个有效的地址指的是对于某个指令的指向内存的操作数。在NASM中,有效地址的语法非常简单:括在方括号内的表达式就是指向的内存地址。例如:

wordvar dw 123
mov ax,[wordvar]
mov ax,[wordvar+1]
mov ax,[es:wordvar+bx]

在 NASM 中,与这个简单系统的不一致的表达式都不是对内存的有效引用。例如:es: wordvar[bx]。

更复杂的有效地址寻址,例如那些有多于一个寄存器参与的命令,原理也是相同的:

mov eax,[ebx*2+ecx+offset]
mov ax,[bp+di+8]

NASM 支持在地址表达式中进行代数计算。所以有些看起来不那么“合法”的表达式还是完全正确的:

mov eax,[ebx*5] ; 编译为 [ebx*4+ebx]
mov eax,[label1*2-label2] ; 等效于 [label1+(label1-label2)]

一些有效地址的形式等效于不只一种汇编形式;在大多数情况下,NASM 会生成可能的最小形式。例如,两条完全不同的汇编形式 [eax*2+0] 和 [eax+eax] 进行寻址的话,NASM 会生成后者的形式,因为前者要求 4 个字节来存储一个 0 偏移量。

NASM 有一个提示机制可以把 [eax+ebx] 和 [ebx+eax] 生成不同的操作码;在某些时候,这会很有用,因为 [esi+ebp] 和 [ebp+esi] 有不同的默认端寄存器。

但是你也可以强制 NASM 生成一个特定格形式的有效地址,通过使用关键字 BYTE、WORD、DWORD 和 NOSPLIT 。如果你需要把 [eax+3] 变为使用双字类型的偏移而不是默认的单字节形式,则只需要编写 [dword eax+3] 来指明。相似地,要强制 NASM 对一些初次使用的比较小的数值使用单字节的偏移(参见 3.8 节的一段代码例子),可以使用 [bye eax+offset] 。特殊地, [byte eax] 等价于写 [eax+0] 把一个字节的 0 偏移编码进去,而 [dword eax] 会把一个双字的 0 偏移编码进去。而正常的形式,[eax] 则不会额外编码任何的偏移量。

3.4 常量

NASM 支持 4 种类型的常量:数字、字符、字符串和浮点数。

3.4.1 数值常量

一种数值常量就是一个简单的整数。NASM 允许程序员书写多种进制的数值。你可以在后面加 H 或者 X 来指定十六进制数,D 或者 T 指定十进制数,Q 或者 O 指定八进制数,B 或者 Y 指定二进制数。或者,你可以用 0x 前缀来书写十六进制数(C风格),前缀 $ 来写十六进制数(Pascal 风格或者 Motorola汇编风格)。注意的是,这里的 $ 作为前缀的时候有是嗯场含义。(看 3.1 节),所以一个十六进制数加一个 $ 前缀是,紧跟着 $ 的必须是数字而不是字母。另外,当前版本的 NASM 接受 0h 作为十六进制数的前缀,0d 或者 0t为十进制数前缀,0o 或者 oq 为八进制数前缀,以及 0b 或者 0y 作为二进制数的前缀。请 C 程序员注意,0 后面跟一个数并不代表这个常量数值是八进制的。

对于位较长的数值,NASM 支持用下划线(_)来分隔位数,而在数值中的下划线没有什么特别意义。

一些有效的常量:

mov ax,200 ; 十进制
mov ax,0200 ; 十进制
mov ax,0200d ; 显式声明十进制
mov ax,0d200 ; 也是十进制
mov ax,0c8h ; 十六进制
mov ax,$0c8 ; 十六进制,注意 $ 后面要跟一个数
mov ax,0xc8 ; 十六进制
mov ax,0hc8 ; 还是十六进制
mov ax,310q ; 八进制
mov ax,310o ; 八进制
mov ax,0o310 ; 也是八进制
mov ax,0q310 ; 还是八进制
mov ax,11001000b ; 二进制
mov ax,1100_1000b ; 二进制
mov ax,1100_1000y ; 二进制
mov ax,0b1100_1000 ; 二进制
mov ax,0y1100_1000 ; 二进制,且数值和上面一样

3.4.2 字符串

一个字符串由最多八个字符组成,可以被单引号(')、双引号(")或者反引号(`)括起来。在 NASM 中,单引号和双引号是等价的(不过,单引号括起来的字符串允许双引号出现,而双引号括起来的字符串允许单引号出现);而它们内容的单个字符就是代表单个字符。而在反引号括起来的字符串支持 C 语言类似的转义字符。

以 \u 或者 \U 指定的 Unicode 字符会被转义为 UTF-8 。举例来说,下面的字符串是等价的:

db `\u263a` ; UTF-8 smiley face
db `\xe2\x98\xba` ; UTF-8 smiley face
db 0E2h, 098h, 0BAh ; UTF-8 smiley face

3.4.3 字符常量

一个字符常量由一个最多 8 字节长的字符串组成,用于一个表达式上下文中。它会被视为一个整数。

一个多于一个字节长的字符常量会用小字序重新整理。如果你这样写

mov eax,'abcd'

那么这个常量并不等于 0x61626364 而等于 0x6636261 ,所以如果你把这些内容存储到内存中,你得到的是 dcba 而不是 abcd 。Pentium 的 CPUID 指令也是同样处理的字符串的。

3.4.4 字符串常量

字符串常量指的是在一些伪指令上下文中使用字符串,主要是 DB 系列伪指令和 INCBIN (被用作文件名)。它们也在一些预处理指令中使用。

一个字符串常量想一个字符常量,只是更长了。它被处理为一系列的最大长度的字符常量。

db 'hello' ;
db 'h','e','l','l','o' ; 等价字符串

下面的字符串也是等价的:

dd 'ninechars'
dd 'nine','char','s' ; 三个双字空间
db 'ninechars',0,0,0 ; 上面的内容实际上像这个定义

当在一个支持字符串常量的上下文中,括起来的字符串会被解释为字符串常量即使它们像字符常量那么短。因为,如果不是那样的话, db 'ab' 会和 db 'a' 一样,这样并不是我们所要得到的结果。相似地,三个字符或者四个字符常量会被视作字符串当它们是 DW 的操作数等等。

3.4.5 Unicode 字符串

特殊操作符 __utf16__ 和 __utf32__ 支持 Unicode 字符串的定义。它们把二进制按照 UTF-8 的规则来读取然后对应地转换为 UTF-16 或者 UTF-32 (小尾段)。

%define u(x) __utf16__(x)
%define w(x) __utf32__(x)
dw u('C:\WINDOWS'), 0 ; utf-16 形式的字符串
dd w(`A + B = \u206a`), 0 ; utf-32 形式的字符串

__utf16__ 和 __utf32__ 可以被应用在传递给 DB 系列伪指令的字符串参数或者在一个表达式中的字符常量。

3.4.6 浮点数常量

浮点数常量只能作为 DB、DWDD、DQ、DT 和 DO,或者作为特殊的操作符 __float8__、__float16__、__float32__、float64__、float80m__ 、__float80e___、__float128l__ 以及 __float128h__ 的参数。

浮点数常量会用传统意义的形式——数位、点然后是任意数位来表达,或者用 E 来表达一个科学记数法形式的实数。小数点是用于标示一个浮点数的,也就是说,dd 1 标示声明一个整数常量而 dd 1.0 声明一个浮点常量。

NASM 也支持 C99 形式的十六进制浮点数:0x + 十六进制数字 + 小数点 + 小数位 + P + 二进制表达的十进制数。额外地,NASM 也支持 0h 和 $ 前缀作为十六进制数,以及 0b 、 0y 和 Oo、Oq 相应地作为二进制、八进制前缀。

在 NASM 中,在浮点数间加下划线也是允许的。

下面的表达都是正确的:

db -0.2 ; "Quarter precision"
dw -0.5 ; IEEE 754r/SSE5 half precision
dd 1.2 ; an easy one
dd 1.222_222_222 ; underscores are permitted
dd 0x1p+2 ; 1.0x2^2 = 4.0
dq 0x1p+32 ; 1.0x2^32 = 4 294 967 296.0
dq 1.e10 ; 10 000 000 000.0
dq 1.e+10 ; synonymous with 1.e10
dq 1.e-10 ; 0.000 000 000 1
dt 3.141592653589793238462 ; pi
do 1.e+4000 ; IEEE 754r quad precision

对于 8 位的 "quarter-precision" 浮点数格式是 符号:指数:位数 = 1:4:3 以及一个指数。这是最常用的 8 位浮点数格式,虽然它并没有被任何正式标准涵盖。它有时也被称为一个”mini float“。

在某些环境下,产生一个浮点数要用一些特殊操作符。它们把二进制的浮点数标示形式转换为一个整数,然后可以被所有支持整数参数的指令使用。__float80m__ 和 __float80e__ 产生 64位尾数和16位指数的的 80 位浮点数,而 __float128l__ 和 __float128h__ 产生一个平分 64 位的 128 位浮点数。(这里其实我也不很明白 128 位的浮点数是怎样组成的)

mov rax,__float64__(3.141592653589793238462)

会把二进制的 pi 解释为一个 64 位的浮点数,保存到 RAX 寄存器中。这行语句实际上等价于:

mov rax,0x400921fb54442d18

NASM 不对浮点常量做编译时的数学逻辑检查。这是因为 NASM 在设计的时候就考虑到可移植问题。虽然它总是产生在 x86 架构 CPU 下的代码,汇编器本身可以在任何支持 ANSI C 的系统上运行。因此,汇编器不保证哪个浮点数单元对 Intel 所定义的数字格式的能力,所以,要使 NASM 支持浮点数的运算,汇编器要有自己的一套完整的浮点数处理方法进行浮点数的计算,这样用很大的工作量来得到很少的利益并不值得。

一些特殊的字段如 __Infinity__、__QNaN__(或者 __NaN__)和 __SNaN__ 可以对应地被用于生成极限数、无效数和 Signalling NaN。 一般在宏中会用到:

%define Inf __Infinity__
%define NaN __QNaN__

dq +1.5, -Inf, NaN ; Double-precision constants

%use fp 标准宏报包含了一系列约定的宏定义。参见 5.3 节。

3.4.7 BCD 码常量

x87 风格的 BCD 码常量可以在 80 位的浮点数环境中使用。它们后缀一个 p 或者 前缀 0p 。可以包含最多 18 个十进制数位。

和其它数值常量一样,下划线也可以在 BCD 码常量中使用。

例如:

dt 12_345_678_901_245_678p
dt -12_345_678_901_245_678p
dt +0p33
dt 33p

最近在学习汇编,看到 NASM 的教程很少,于是顺便翻译了,也当检验一下自己的学习是否正确。不过有些地方还是没理解好。如果有什么错误希望大家请指出!

写点什么吧

今天开学已经第二周了。不过连接 www.is-programmer.com 的网络状况似乎不是很好,校园网还没有申请。

昨天某人发的短信的确使我感动很温暖。短短的几条短信,或许真的可以改变一些看法。或许那个人终于注意到我了吧。去了剪头发,呃,现在的发型很有军训的感觉。仿佛又到军训的时候。不过晚上的时候有点无奈,或许我不应该再发短信吧。要找我的人不需要我去找……嘿嘿。

终于开始学习 C++ 和 VB.NET 了。不过,觉得高级语言似乎都类似,主要是运用语言的思想不同。不过汇编确实有点无奈啊……主要是学习的是 MASM,不支持 Linux ,而语法上和 NASM 等又不同,于是去下载用着一个 D 版的 Windows XP 吧。学习这门课程主要希望了解编译和内存储存的结构,但要考试,还是得依赖 Windows……

现在为 ylinux.org 写着部分代码,当作练习吧。不过现在要做的是先把一个网站的 mockup 做出来。先前的设计都是比较凌乱,一方面是继承原来不同的设计方案的基础上来做小修改。而现在应该做一个整体的规划了。

为了记事而记事

不知道从什么时候开始,我也会自己一个人出去,漫无目的地走。或许,这一年以来的事情真的太多了,我需要时间去理解。

想起高三毕业的时候,我写给自己的一首英文小诗:

Since the start of the summer ,
I had been waiting for something .
Around me stood many people
waiting for something
Some said its name is college .
Some said its name is jobs .
Some said its name is opportunities .
But the one who return
told us that
its name is time . . .

我的确在期待什么,但我自己却一直说不清。于是,也连累了不少的人,有些人我不知道怎样去面对,也不怎么想结识新的朋友。我觉得自己的能力确实有限。有很多事情,不是我想做就能做。
也许要放弃一些东西吧。但我现在想要的是什么呢?我似乎在浪费时间,但我又说不出我不这么浪费时间可以做些什么?

有的人在准备 ACM/ICPC ,有些人在跟着老师做一些项目,有些人去社会实践……我呢?我应该做些什么?迷失比走在路上的艰辛更可怕。

其实反思现在的我,没有高中的基础,基本上就是现在才想到学习算法,对比一些 NOI 的牛人,应该说真的差很远。

Smile

上学期末的时候写的一个小程序,用于练习 Java2D 编程。我觉得图形编程还是挺有趣的。

记得学期末的时候,某人发短信过来说对编程还是没很多兴趣。嘿嘿,其实我觉得其实编程就不过是实现生活中的事情而已。于是写了这个小程序。希望见到这个小程序也会开心一下吧。

也应该说一下。版权不在我这里,我只是用 Java 参考 Xorg 中的 Xeye 示例程序、加入一些鼠标事件处理写的。

大家可以参考一下源码吧。虽然现在看回原来写的代码,我也觉得写得不是很好(思路不清晰,但代码不长)……需要注意的是,我使用了 JRE 7 的新特性,所以,必须在 JDK 7 下编译。
实际上是因为加入了这些语句:

frame.setShape(new  
	java.awt.geom.Ellipse2D.Double(
		0, 0, 
		frame.getWidth(), 
		frame.getHeight()));
try {
	frame.setOpacity(0.8f);
} catch(Exception ex) {
}

其实还有一些动态效果的。截图就不多发了。

嗯,其实我希望大家见到这个程序都会开心。

下载:http://www.boxcn.net/shared/il58j4fm96mkj6nj0s69

源码:http://www.boxcn.net/shared/umi549k2vjsir2lzc25a

TankII update 1.2.1

考虑到以前同学说过的按键问题,现在修正了一下。不过,要更准确地控制坦克的炮筒位置,就需要两副方向键。而现在 ASDW 和 方向键 (功能和ASDW一样)都用了,于是想到 VI 的键盘模式 HJKL 。

修改记录:

  • 更改键设定,H、J、K、L 控制坦克炮筒角度。

所以,现在是,举例来说,无论现在炮筒在什么方向,只要按了 H 那么就会沿最短的路径向左( J 对应“向下”、K 对应“向上”、L 对应“向右”)。

这个修改太小了。所以就只是更新一个小版本号吧。基本上就修改了 TankKeyListener 类。

下载地址:http://www.boxcn.net/shared/1v7lhfdfumj08sxoktsl (其实下载地址没怎么变,用了 box.net 的文件版本功能)

Log 2011.8.11

昨天在回顾 POJ 3349 的时候,想起了散列表(Hash Table)的数据结构。提醒了我在 TankII 的对象碰撞的时候应该处理的一些事情,对需要检测的对象先进行粗略的分类然后再继续判断,这样的话,对 TankII 设计里可以减少实际碰撞检测的时候。这需要的就是一个散列函数了。这个散列函数应该满足的是把几个顶点坐标处理成一个数,如果所有坦克都可以通过这个数判断粗略分出可能造成碰撞的坦克的话,游戏的刷新间隔应该可以缩减到更短,有利于其他额外的绘制处理。但这个散列函数也不是容易找的。也只是我的一个很原始的想法而已。而且,散列表应该是对应静态分类(储存的对象基本是确定的)的,而我所需要的是动态分类,也是一个难题,还在探索中……

现在总算是对数据结构有些初始的理解了。

还有就是对 Java2D 的一些不满,似乎 Java2D 中的结构都是静止的结构,没有考虑到这个对象用作动画的一部分?如 Rectangle2D 这样的对象,虽然提供了 getCenterX() getCenterY() 这样的方法,但对 Rectangle2D 内顶点的修改却很少涉及。但看 OpenJDK 中 Rectangle2D 的实现,其实是可以增加额外的修改图像的方法的。而且碰撞检测也是基于变量的,这样修改也不会造成性能的问题吧?只有一个不方便的 setRect(doulbe x, double y, double w, double h) 来进行整体修改。当然,也是一个解决办法。相比起 Pygame 的 Rect 类就差远了。