又一VC6编译器BUG
NetRoc
http://www.DbgTech.net/
一个XML解析函数,用于获取XML中的子节点。Debug下面正常,而Release下面始终获取不到节点;另外,代码中有一个没有使用到的CString变量,Release下面如果定义它,则运行正常,如果没有定义,则运行出错。函数代码如下:
//到到第一个Child结点指针
Node* CXMLProtocolParse::GetFirstChild(Node* pNode)
{// AfxMessageBox("");
ASSERT (pNode != NULL);
if ( pNode == NULL )
return NULL;
Node* pElem = NULL;
try
{
pNode->get_firstChild(&pElem);
if ( pElem == NULL )
return NULL;
m_ArrayElement.Add(pElem);
//CString strError ; //不加这个变量的申明 Release中无法正确获得节点数据
DOMNodeType tagNodeType = NODE_INVALID;
pElem->get_nodeType(&tagNodeType);
if ( tagNodeType == NODE_TEXT ) //第一个子结点为文本结点, 则把其下一个兄弟结点认为是第一个结点。后来发现问题就出在这个判断对应的跳转语句上
return GetNextSibling(pElem);
}catch(...)
{
ASSERT(FALSE);
}
return pElem; //Release版执行从这里返回时就会出问题,返回值为NULL,但是pElem其实是有值的。
}
未定义CString变量时的汇编代码如下,我们可以跟踪一下出错的过程:
.text:00401D60 push ebp
.text:00401D61 mov ebp, esp
.text:00401D63 push 0FFFFFFFFh
.text:00401D65 push offset unknown_libname_11 ; Microsoft VisualC 2-8/net runtime
.text:00401D6A mov eax, large fs:0
.text:00401D70 push eax
.text:00401D71 mov large fs:0, esp
.text:00401D78 sub esp, 8
.text:00401D7B mov eax, [ebp+arg_0]
.text:00401D7E push ebx
.text:00401D7F push esi
.text:00401D80 push edi
.text:00401D81 xor edi, edi
.text:00401D83 mov [ebp+var_10], esp
.text:00401D86 cmp eax, edi
.text:00401D88 mov esi, ecx
.text:00401D8A jz short loc_401DEC
.text:00401D8A
.text:00401D8C mov ecx, [eax]
.text:00401D8E lea edx, [ebp+arg_0]
.text:00401D91 push edx
.text:00401D92 push eax
.text:00401D93 mov [ebp+arg_0], edi
.text:00401D96 mov [ebp+var_4], edi
.text:00401D99 call dword ptr [ecx+34h] ; pNode->get_firstChild(&pElem);
.text:00401D99
.text:00401D9C mov edx, [ebp+arg_0]
.text:00401D9F cmp edx, edi
.text:00401DA1 jz short loc_401DEC
.text:00401DA1
.text:00401DA3 mov eax, [esi+0Ch]
.text:00401DA6 lea ecx, [esi+4]
.text:00401DA9 push edx
.text:00401DAA push eax
.text:00401DAB call CPtrArray::SetAtGrow(int,void *) ; m_ArrayElement.Add(pElem);
.text:00401DAB
.text:00401DB0 mov eax, [ebp+arg_0]
.text:00401DB3 lea edx, [ebp+var_14]
.text:00401DB6 mov [ebp+var_14], edi
.text:00401DB9 push edx
.text:00401DBA mov ecx, [eax]
.text:00401DBC push eax
.text:00401DBD call dword ptr [ecx+28h] ; pElem->get_nodeType(&tagNodeType);
.text:00401DBD
.text:00401DC0 mov eax, [ebp+var_14]
.text:00401DC3 cmp eax, 3
;这里因为比较的结果不同,所以将pElem的值传给eax,准备返回,到这个位置都还是一切正常的。但是跳转的目标因为优化的关系,重复使用了前面return NULL;这条语句的代码,所以后面会有问题。
.text:00401DC6 mov eax, [ebp+arg_0]
.text:00401DC9 jnz short loc_401DEC
.text:00401DC9
.text:00401DCB push eax
.text:00401DCC mov ecx, esi
.text:00401DCE call CXMLProtocolParse__GetNextSibling
.text:00401DCE
.text:00401DD3 mov ecx, [ebp+var_C]
.text:00401DD6 mov large fs:0, ecx
.text:00401DDD pop edi
.text:00401DDE pop esi
.text:00401DDF pop ebx
.text:00401DE0 mov esp, ebp
.text:00401DE2 pop ebp
.text:00401DE3 retn 4
.text:00401DE3
.text:00401DE6 ; ---------------------------------------------------------------------------
.text:00401DE6
.text:00401DE6 loc_401DE6: ; DATA XREF: .rdata:stru_4049F8o
.text:00401DE6 mov eax, offset loc_401DEC
.text:00401DEB retn
.text:00401DEB
.text:00401DEC ; ---------------------------------------------------------------------------
.text:00401DEC
.text:00401DEC loc_401DEC: ; CODE XREF: CXMLProtocolParse__GetFirstChild+2Aj
.text:00401DEC ; CXMLProtocolParse__GetFirstChild+41j
.text:00401DEC ; CXMLProtocolParse__GetFirstChild+69j
.text:00401DEC ; DATA XREF: CXMLProtocolParse__GetFirstChild:loc_401DE6o
;跳转到这个地方返回,多出来的那个xor eax,eax,问题就来了!!!这条语句块本来用作return NULL;返回的,所以里面有清零eax的操作,错误重用的话出大毛病!!!
.text:00401DEC mov ecx, [ebp+var_C]
.text:00401DEF pop edi
.text:00401DF0 pop esi
.text:00401DF1 xor eax, eax
.text:00401DF3 mov large fs:0, ecx
.text:00401DFA pop ebx
.text:00401DFB mov esp, ebp
.text:00401DFD pop ebp
.text:00401DFE retn 4
.text:00401DFE
.text:00401DFE CXMLProtocolParse__GetFirstChild endp
下面再来看定义了CString变量时的汇编代码,因为多了CString类的构造和析构函数,所以代码的结构发生了改变,return NULL;这条语句的代码没有被重用:
.text:00401D60
.text:00401D60 ; Attributes: bp-based frame
.text:00401D60
.text:00401D60 CXMLProtocolParse__GetFirstChild proc near
.text:00401D60 ; CODE XREF: CXMLProtocolParse__FindNode+73p
.text:00401D60
.text:00401D60 var_18 = byte ptr -18h
.text:00401D60 var_14 = dword ptr -14h
.text:00401D60 var_10 = dword ptr -10h
.text:00401D60 var_C = dword ptr -0Ch
.text:00401D60 var_4 = dword ptr -4
.text:00401D60 arg_0 = dword ptr 8
.text:00401D60
.text:00401D60 push ebp
.text:00401D61 mov ebp, esp
.text:00401D63 push 0FFFFFFFFh
.text:00401D65 push offset unknown_libname_11 ; Microsoft VisualC 2-8/net runtime
.text:00401D6A mov eax, large fs:0
.text:00401D70 push eax
.text:00401D71 mov large fs:0, esp
.text:00401D78 sub esp, 0Ch
.text:00401D7B mov eax, [ebp+arg_0]
.text:00401D7E push ebx
.text:00401D7F push esi
.text:00401D80 xor ebx, ebx
.text:00401D82 push edi
.text:00401D83 cmp eax, ebx
.text:00401D85 mov [ebp+var_10], esp
.text:00401D88 mov esi, ecx
.text:00401D8A jnz short loc_401DA1
.text:00401D8A
.text:00401D8C xor eax, eax
.text:00401D8E mov ecx, [ebp+var_C]
.text:00401D91 mov large fs:0, ecx
.text:00401D98 pop edi
.text:00401D99 pop esi
.text:00401D9A pop ebx
.text:00401D9B mov esp, ebp
.text:00401D9D pop ebp
.text:00401D9E retn 4
.text:00401D9E
.text:00401DA1 ; ---------------------------------------------------------------------------
.text:00401DA1
.text:00401DA1 loc_401DA1: ; CODE XREF: CXMLProtocolParse__GetFirstChild+2Aj
.text:00401DA1 mov ecx, [eax]
.text:00401DA3 lea edx, [ebp+arg_0]
.text:00401DA6 push edx
.text:00401DA7 push eax
.text:00401DA8 mov [ebp+arg_0], ebx
.text:00401DAB mov [ebp+var_4], ebx
.text:00401DAE call dword ptr [ecx+34h] ; pNode->get_firstChild(&pElem);
.text:00401DAE
.text:00401DB1 mov edx, [ebp+arg_0]
.text:00401DB4 cmp edx, ebx
.text:00401DB6 jnz short loc_401DCD
.text:00401DB6
;这里return NULL;
.text:00401DB8 xor eax, eax
.text:00401DBA mov ecx, [ebp+var_C]
.text:00401DBD mov large fs:0, ecx
.text:00401DC4 pop edi
.text:00401DC5 pop esi
.text:00401DC6 pop ebx
.text:00401DC7 mov esp, ebp
.text:00401DC9 pop ebp
.text:00401DCA retn 4
.text:00401DCA
.text:00401DCD ; ---------------------------------------------------------------------------
.text:00401DCD
.text:00401DCD loc_401DCD: ; CODE XREF: CXMLProtocolParse__GetFirstChild+56j
.text:00401DCD mov eax, [esi+0Ch]
.text:00401DD0 lea ecx, [esi+4]
.text:00401DD3 push edx
.text:00401DD4 push eax
.text:00401DD5 call CPtrArray::SetAtGrow(int,void *) ; m_ArrayElement.Add(pElem);
.text:00401DD5
.text:00401DDA lea ecx, [ebp+var_18]
.text:00401DDD call CString::CString(void) ; 初始化CString
.text:00401DDD
.text:00401DE2 mov eax, [ebp+arg_0]
.text:00401DE5 lea edx, [ebp+var_14]
.text:00401DE8 mov [ebp+var_14], ebx
.text:00401DEB push edx
.text:00401DEC mov ecx, [eax]
.text:00401DEE push eax
.text:00401DEF mov byte ptr [ebp+var_4], 1
.text:00401DF3 call dword ptr [ecx+28h] ; pElem->get_nodeType(&tagNodeType);
.text:00401DF3
;这里比较并跳转
.text:00401DF6 cmp [ebp+var_14], 3
.text:00401DFA jnz short loc_401E29
.text:00401DFA
.text:00401DFC mov eax, [ebp+arg_0]
.text:00401DFF mov ecx, esi
.text:00401E01 push eax
.text:00401E02 call CXMLProtocolParse__GetNextSibling
.text:00401E02
.text:00401E07 lea ecx, [ebp+var_18]
.text:00401E0A mov esi, eax
.text:00401E0C mov byte ptr [ebp+var_4], bl
.text:00401E0F call CString::~CString(void)
.text:00401E0F
.text:00401E14 mov eax, esi
.text:00401E16 mov ecx, [ebp+var_C]
.text:00401E19 mov large fs:0, ecx
.text:00401E20 pop edi
.text:00401E21 pop esi
.text:00401E22 pop ebx
.text:00401E23 mov esp, ebp
.text:00401E25 pop ebp
.text:00401E26 retn 4
.text:00401E26
.text:00401E29 ; ---------------------------------------------------------------------------
.text:00401E29
.text:00401E29 loc_401E29: ; CODE XREF: CXMLProtocolParse__GetFirstChild+9Aj
;析构并返回,这里就没有再做eax清零的操作了,所以运行正常
.text:00401E29 lea ecx, [ebp+var_18]
.text:00401E2C mov byte ptr [ebp+var_4], bl
.text:00401E2F call CString::~CString(void)
.text:00401E2F
.text:00401E34
.text:00401E34 loc_401E34: ; DATA XREF: sub_401E4Ao
.text:00401E34 mov ecx, [ebp+var_C]
.text:00401E37 mov eax, [ebp+arg_0]
.text:00401E3A pop edi
.text:00401E3B pop esi
.text:00401E3C mov large fs:0, ecx
.text:00401E43 pop ebx
.text:00401E44 mov esp, ebp
.text:00401E46 pop ebp
.text:00401E47 retn 4
.text:00401E47
.text:00401E47 CXMLProtocolParse__GetFirstChild endp
刚开始以为是栈破坏或者变量未初始化的问题,没想到又发现一个VC6编译器BUG,小小的记录一下,呵呵。经测试,用VS2008编译不会有这个问题。10多年前的东西用起来还真是危险啊~~~
NetRoc
http://www.DbgTech.net
F#和很多语言不同,它不一定具有明确的程序入口点,如C语言的main函数。在单源文件程序中,代码是从头至尾执行的。在多源文件程序中,代码需要被组织为模块或者命名空间这样的单位。
模块
默认情况下,F# 将所有代码都放置在匿名模块(anonymous module)中。模块的命名为首字母大写的源文件名。因此,如果在文件file1.fs中有一个名为value1的值,那么可以通过File1.value1来引用它。
创建模块
代码中可以在源文件开始处使用module关键字来明确指定模块名。在module定义之后的所有值、函数或类型定义都属于该模块。如:
module Alpha
//在模块外部使用Alpha.x来引用下面的值:
let x = 1
模块嵌套
单个源文件中可以包含嵌套的模块。被嵌套的模块应该使用module关键字后跟一个等号来声明。嵌套模块必须比上层模块缩进对齐。如:
module Utilities
module ConversionUtils =
// Utilities.ConversionUtils.intToString
let intToString (x : int) = x.ToString()
module ConvertBase =
// Utilities.ConversionUtils.ConvertBase.convertToHex
let convertToHex x = sprintf "%x" x
// Utilities.ConversionUtils.ConvertBase.convertToOct
let convertToOct x = sprintf "%o" x
module DataTypes =
// Utilities.DataTypes.Point
type Point = Point of float * float * float
命名空间
除了模块之外还可以使用命名空间来组织代码。命名空间和模块的唯一区别在于,命名空间中不能包含值,只能包含类型定义。另外,命名空间不能像模块一样嵌套,而只需要直接在同一个源文件中定义多个命名空间。下面的例子在两个命名空间中定义了一些类型:
namespace PlayingCards
// PlayingCard.Suit
type Suit =
| Spade
| Club
| Diamond
| Heart
// PlayingCards.PlayingCard
type PlayingCard =
| Ace of Suit
| King of Suit
| Queen of Suit
| Jack of Suit
| ValueCard of int * Suit
namespace PlayingCards.Poker
// PlayingCards.Poker.PokerPlayer
type PokerPlayer = { Name : string; Money : int; Position : int }
在F#中部推荐同时使用命名空间和模块。模块主要用于快速进行原型定义和快速浏览解决方案。而命名空间主要用于使用面向对象设计的较大型的项目。
程序的启动
F#中程序是从最后一个源文件的顶部开始执行的。假如现在有包含下面代码的单文件项目:
// Program.fs
let numbers = [1 .. 10]
let square x = x * x
let squaredNumbers = List.map square numbers
printfn "SquaredNumbers = %A" squaredNumbers
open System
printfn "(press any key to continue)"
Console.ReadKey(true)
在VS 2010中再添加一个新的空F#源文件。这时,最后一个文件是新添加的空文件,因此如果此时按F5运行程序时,不会执行任何代码。
这一点需要特别注意,否则程序的执行可能会对初学者造成混乱。可以说F#这个设计真的是非常之奇怪,不但没有任何好处,还会让人非常之不爽。。。。。。。。。
更正式的做法是使用[<EntryPoint>]属性来定义一个main函数。main函数需要满足以下几个要求:
如果要明确指定main函数,那么上面那个程序应该改写为:
// Program.fs
open System
[<EntryPoint>]
let main (args : string[]) =
let numbers = [1 .. 10]
let square x = x * x
let squaredNumbers = List.map square numbers
printfn "SquaredNumbers = %A" squaredNumbers
printfn "(press any key to continue)")
Console.ReadKey(True) |> ignore
// Return 0
0