每天早晨叫醒你的不是闹钟,而是梦想

  C++博客 :: 首页 :: 联系 :: 聚合  :: 管理
  62 Posts :: 0 Stories :: 5 Comments :: 0 Trackbacks

常用链接

留言簿(1)

我参与的团队

搜索

  •  

最新评论

阅读排行榜

评论排行榜

#

题目:二叉树的结点定义如下:

struct TreeNode

{

        int m_nValue;

        TreeNode* m_pLeft;

        TreeNode* m_pRight;

};

输入两棵二叉树AB,判断树B是不是A的子结构。

例如,下图中的两棵树AB,由于A中有一部分子树的结构和B是一样的,因此B就是A的子结构。

                 1                                                   8
               /    \                                               /    \
              8    7                                             9    2
            /    \
           9    2
                /  \
               4  7

分析:这是2010年微软校园招聘时的一道题目。二叉树一直是微软面试题中经常出现的数据结构。对微软有兴趣的读者一定要重点关注二叉树。

                回到这个题目的本身。要查找树A中是否存在和树B结构一样的子树,我们可以分为两步:第一步在树A中找到和B的根结点的值一样的结点N,第二步再判断树A中以N为根结点的子树是不是包括和树B一样的结构。

                第一步在树A中查找与根结点的值一样的结点。这实际上就是树的遍历。对二叉树这种数据结构熟悉的读者自然知道我们可以用递归的方法去遍历,也可以用循环的方法去遍历。由于递归的代码实现比较简洁,面试时如果没有特别要求,我们通常都会采用递归的方式。下面是参考代码:

bool HasSubtree(TreeNode* pTreeHead1, TreeNode* pTreeHead2)

{

        if((pTreeHead1 == NULL && pTreeHead2 != NULL) ||

                (pTreeHead1 != NULL && pTreeHead2 == NULL))

                return false;

 

        if(pTreeHead1 == NULL && pTreeHead2 == NULL)

                return true;

 

        return HasSubtreeCore(pTreeHead1, pTreeHead2);

}

bool HasSubtreeCore(TreeNode* pTreeHead1, TreeNode* pTreeHead2)

{

        bool result = false;

        if(pTreeHead1->m_nValue == pTreeHead2->m_nValue)

        {

                result = DoesTree1HaveAllNodesOfTree2(pTreeHead1, pTreeHead2);

        }

 

        if(!result && pTreeHead1->m_pLeft != NULL)

                result = HasSubtreeCore(pTreeHead1->m_pLeft, pTreeHead2);

 

        if(!result && pTreeHead1->m_pRight != NULL)

                result = HasSubtreeCore(pTreeHead1->m_pRight, pTreeHead2);

 

        return result;

}

在上述代码中,我们递归调用hasSubtreeCore遍历二叉树A。如果发现某一结点的值和树B的头结点的值相同,则调用DoesTree1HaveAllNodeOfTree2,做第二步判断。

在面试的时候,我们一定要注意边界条件的检查,即检查空指针。当树A或树B为空的时候,定义相应的输出。如果没有检查并做相应的处理,程序非常容易崩溃,这是面试时非常忌讳的事情。由于没有必要在每一次递归中做边界检查(每一次递归都做检查,增加了不必要的时间开销),上述代码只在HasSubtree中作了边界检查后,在HasSubtreeCore中作递归遍历。

接下来考虑第二步,判断以树A中以N为根结点的子树是不是和树B具有相同的结构。同样,我们也可以用递归的思路来考虑:如果结点N的值和树B的根结点不相同,则以N为根结点的子树和树B肯定不具有相同的结点;如果他们的值相同,则递归地判断他们的各自的左右结点的值是不是相同。递归的终止条件是我们到达了树A或者树B的叶结点。参考代码如下:

bool DoesTree1HaveAllNodesOfTree2(TreeNode* pTreeHead1, TreeNode* pTreeHead2)

{

        if(pTreeHead2 == NULL)

                return true;

 

        if(pTreeHead1 == NULL)

                return false;

 

        if(pTreeHead1->m_nValue != pTreeHead2->m_nValue)

                return false;

 

        return DoesTree1HaveAllNodesOfTree2(pTreeHead1->m_pLeft, pTreeHead2->m_pLeft) &&

                DoesTree1HaveAllNodesOfTree2(pTreeHead1->m_pRight, pTreeHead2->m_pRight);

}

 

  博主何海涛对本博客文章享有版权。网络转载请注明出处http://zhedahht.blog.163.com/

posted @ 2011-04-29 14:03 沛沛 阅读(348) | 评论 (0)编辑 收藏

     摘要:   题目(一):我们可以用static修饰一个类的成员函数,也可以用const修饰类的成员函数(写在函数的最后表示不能修改成员变量,不是指写在前面表示返回值为常量)。请问:能不能同时用static和const修饰类的成员函数? 分析:答案是不可以。C++编译器在实现const的成员函数的时候为了确保该函数不能修改类的实例的状态,会在函数中添加一个隐式的参数const this*。但当...  阅读全文
posted @ 2011-04-29 13:44 沛沛 阅读(703) | 评论 (0)编辑 收藏

一、介绍字符编码、内码,顺带介绍汉字编码
 
字符必须编码后才能被计算机处理。计算机使用的缺省编码方式就是计算机的内码。早
期的计算机使用7位的ASCII编码,为了处理汉字,程序员设计了用于简体中文的GB2312
和用于繁体中文的big5。
 
GB2312(1980年)一共收录了7445个字符,包括6763个汉字和682个其它符号。汉字区的内
码范围高字节从B0-F7,低字节从A1-FE,占用的码位是72*94=6768。其中有5个空位是
D7FA-D7FE。
 
GB2312支持的汉字太少。1995年的汉字扩展规范GBK1.0收录了21886个符号,它分为汉字
区和图形符号区。汉字区包括21003个字符。2000年的GB18030是取代GBK1.0的正式国家
标准。该标准收录了27484个汉字,同时还收录了藏文、蒙文、维吾尔文等主要的少数民
族文字。现在的PC平台必须支持GB18030,对嵌入式产品暂不作要求。所以手机、MP3一
般只支持GB2312。
 
从ASCII、GB2312、GBK到GB18030,这些编码方法是向下兼容的,即同一个字符在这些方
案中总是有相同的编码,后面的标准支持更多的字符。在这些编码中,英文和中文可以
统一地处理。区分中文编码的方法是高字节的最高位不为0。按照程序员的称呼,
GB2312、GBK到GB18030都属于双字节字符集 (DBCS)。
 
有的中文Windows的缺省内码还是GBK,可以通过GB18030升级包升级到GB18030。不过
GB18030相对GBK增加的字符,普通人是很难用到的,通常我们还是用GBK指代中文
Windows内码。
 
这里还有一些细节:
 
GB2312的原文还是区位码,从区位码到内码,需要在高字节和低字节上分别加上A0。
 
在DBCS中,GB内码的存储格式始终是big endian,即高位在前。
 
GB2312的两个字节的最高位都是1。但符合这个条件的码位只有128*128=16384个。所以
GBK和GB18030的低字节最高位都可能不是1。不过这不影响DBCS字符流的解析:在读取
DBCS字符流时,只要遇到高位为1的字节,就可以将下两个字节作为一个双字节编码,而
不用管低字节的高位是什么。
 
 
 
二、关于编码
 
所谓编码,是以固定的顺序排列字符,并以此做为记录、存贮、传递、交换的统一内部
特征,这个字符排列顺序被称为“编码”。和中文字库有关的常见编码有:大陆GB码、
GBK码、港台BIG-5码等。下面简要介绍一下。
 
GB码
 
全称是GB2312-80《信息交换用汉字编码字符集 基本集》,1980年发布,是中文信息处
理的国家标准,在大陆及海外使用简体中文的地区(如新加坡等)是强制使用的唯一中
文编码。P-Windows3.2和苹果OS就是以GB2312为基本汉字编码, Windows 95/98则以GBK
为基本汉字编码、但兼容支持GB2312。
 
GB码共收录6763个简体汉字、682个符号,其中汉字部分:一级字3755,以拼音排序,二
级字3008,以偏旁排序。该标准的制定和应用为规范、推动中文信息化进程起了很大作
用。
 
1990年又制定了繁体字的编码标准GB12345-90《信息交换用汉字编码字符集 第一辅助
集》,目的在于规范必须使用繁体字的各种场合,以及古籍整理等。该标准共收录6866
个汉字(比GB2312多103个字,其它厂商的字库大多不包括这些字),纯繁体的字大概有
2200余个。
 
Unicode编码(Universal Multiple Octet Coded Character Set)
 
国际标准组织于1984年4月成立ISO/IEC JTC1/SC2/WG2工作组,针对各国文字、符号进行
统一性编码。1991年美国跨国公司成立Unicode Consortium,并于1991年10月与WG2达成
协议,采用同一编码字集。目前Unicode是采用16位编码体系,其字符集内容与ISO10646
的BMP(Basic Multilingual Plane)相同。Unicode于1992年6月通过DIS(Draf
International Standard),目前版本V2.0于1996公布,内容包含符号6811个,汉字
20902个,韩文拼音11172个,造字区6400个,保留20249个,共计65534个。
 
GBK编码(Chinese Internal Code Specification)
 
GBK编码是中国大陆制订的、等同于UCS的新的中文编码扩展国家标准。GBK工作小组于
1995年10月,同年12月完成GBK规范。该编码标准兼容GB2312,共收录汉字21003个、符
号883个,并提供1894个造字码位,简、繁体字融于一库。
 
Windows95/98简体中文版的字库表层编码就采用的是GBK,通过GBK与UCS之间一一对应的
码表与底层字库联系。

 
BIG5编码
 
是目前台湾、香港地区普遍使用的一种繁体汉字的编码标准,包括440个符号,一级汉字
5401个、二级汉字7652个,共计13060个汉字。
 
方正748编码
 
所谓748编码,是指方正系统在长期应用过程中实施、制定的简、繁体字库编码方式,简
体兼容GB2312且有所扩展,共7156字;繁体兼容GB12345并扩展全部BIG-5汉字,计14943
字。此外,方正748编码还含有丰富的符号库。748编码仅用于方正软件和系统。
 

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/coffeemay/archive/2006/04/17/666213.aspx

posted @ 2011-04-29 11:22 沛沛 阅读(449) | 评论 (0)编辑 收藏

Socket(套接字)

◆先看定义:
typedef unsigned int u_int;
typedef u_int SOCKET;◆Socket相当于进行网络通信两端的插座,只要对方的Socket和自己的Socket有通信联接,双方就可以发送和接收数据了。其定义类似于文件句柄的定义。

◆Socket有五种不同的类型:

1、流式套接字(stream socket)
定义:

#define SOCK_STREAM 1 流式套接字提供了双向、有序的、无重复的以及无记录边界的数据流服务,适合处理大量数据。它是面向联结的,必须建立数据传输链路,同时还必须对传输的数据进行验证,确保数据的准确性。因此,系统开销较大。

2、 数据报套接字(datagram socket)

定义:

#define SOCK_DGRAM 2 数据报套接字也支持双向的数据流,但不保证传输数据的准确性,但保留了记录边界。由于数据报套接字是无联接的,例如广播时的联接,所以并不保证接收端是否正在侦听。数据报套接字传输效率比较高。

3、原始套接字(raw-protocol interface)

定义:

#define SOCK_RAW 3 原始套接字保存了数据包中的完整IP头,前面两种套接字只能收到用户数据。因此可以通过原始套接字对数据进行分析。
其它两种套接字不常用,这里就不介绍了。

◆Socket开发所必须需要的文件(以WinSock V2.0为例):

头文件:Winsock2.h

库文件:WS2_32.LIB

动态库:W32_32.DLL

 一些重要的定义

1、数据类型的基本定义:这个大家一看就懂。

typedef unsigned char u_char;
typedef unsigned short u_short;
typedef unsigned int u_int;
typedef unsigned long u_long;2、 网络地址的数据结构,有一个老的和一个新的的,请大家留意,如果想知道为什么,
请发邮件给Bill Gate。其实就是计算机的IP地址,不过一般不用用点分开的IP地
址,当然也提供一些转换函数。

◆ 旧的网络地址结构的定义,为一个4字节的联合:

struct in_addr {
union {
struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;
struct { u_short s_w1,s_w2; } S_un_w;
u_long S_addr;
} S_un;
#define s_addr S_un.S_addr /* can be used for most tcp & ip code */
//下面几行省略,反正没什么用处。
};其实完全不用这么麻烦,请看下面:

◆ 新的网络地址结构的定义:
非常简单,就是一个无符号长整数 unsigned long。举个例子:IP地址为127.0.0.1的网络地址是什么呢?请看定义:

#define INADDR_LOOPBACK 0x7f0000013、 套接字地址结构

(1)、sockaddr结构:

struct sockaddr {
u_short sa_family; /* address family */
char sa_data[14]; /* up to 14 bytes of direct address */
};sa_family为网络地址类型,一般为AF_INET,表示该socket在Internet域中进行通信,该地址结构随选择的协议的不同而变化,因此一般情况下另一个与该地址结构大小相同的sockaddr_in结构更为常用,sockaddr_in结构用来标识TCP/IP协议下的地址。换句话说,这个结构是通用socket地址结构,而下面的sockaddr_in是专门针对Internet域的socket地址结构。

(2)、sockaddr_in结构

struct sockaddr_in {
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};sin _family为网络地址类型,必须设定为AF_INET。sin_port为服务端口,注意不要使用已固定的服务端口,如HTTP的端口80等。如果端口设置为0,则系统会自动分配一个唯一端口。sin_addr为一个unsigned long的IP地址。sin_zero为填充字段,纯粹用来保证结构的大小。

◆ 将常用的用点分开的IP地址转换为unsigned long类型的IP地址的函数:

unsigned long inet_addr(const char FAR * cp )用法:

unsigned long addr=inet_addr("192.1.8.84")◆ 如果将sin_addr设置为INADDR_ANY,则表示所有的IP地址,也即所有的计算机。

#define INADDR_ANY (u_long)0x000000004、 主机地址:

先看定义:

struct hostent {
char FAR * h_name; /* official name of host */
char FAR * FAR * h_aliases; /* alias list */
short h_addrtype; /* host address type */
short h_length; /* length of address */
char FAR * FAR * h_addr_list; /* list of addresses */
#define h_addr h_addr_list[0] /* address, for backward compat */
};
h_name为主机名字。
h_aliases为主机别名列表。
h_addrtype为地址类型。
h_length为地址类型。
h_addr_list为IP地址,如果该主机有多个网卡,就包括地址的列表。另外还有几个类似的结构,这里就不一一介绍了。

5、 常见TCP/IP协议的定义:

#define IPPROTO_IP 0
#define IPPROTO_ICMP 1
#define IPPROTO_IGMP 2
#define IPPROTO_TCP 6
#define IPPROTO_UDP 17
#define IPPROTO_RAW 255 具体是什么协议,大家一看就知道了。

 套接字的属性

为了灵活使用套接字,我们可以对它的属性进行设定。

1、 属性内容:

//允许调试输出
#define SO_DEBUG 0x0001 /* turn on debugging info recording */
//是否监听模式
#define SO_ACCEPTCONN 0x0002 /* socket has had listen() */
//套接字与其他套接字的地址绑定
#define SO_REUSEADDR 0x0004 /* allow local address reuse */
//保持连接
#define SO_KEEPALIVE 0x0008 /* keep connections alive */
//不要路由出去
#define SO_DONTROUTE 0x0010 /* just use interface addresses */
//设置为广播
#define SO_BROADCAST 0x0020 /* permit sending of broadcast msgs */
//使用环回不通过硬件
#define SO_USELOOPBACK 0x0040 /* bypass hardware when possible */
//当前拖延值
#define SO_LINGER 0x0080 /* linger on close if data present */
//是否加入带外数据
#define SO_OOBINLINE 0x0100 /* leave received OOB data in line */
//禁用LINGER选项
#define SO_DONTLINGER (int)(~SO_LINGER)
//发送缓冲区长度
#define SO_SNDBUF 0x1001 /* send buffer size */
//接收缓冲区长度
#define SO_RCVBUF 0x1002 /* receive buffer size */
//发送超时时间
#define SO_SNDTIMEO 0x1005 /* send timeout */
//接收超时时间
#define SO_RCVTIMEO 0x1006 /* receive timeout */
//错误状态
#define SO_ERROR 0x1007 /* get error status and clear */
//套接字类型
#define SO_TYPE 0x1008 /* get socket type */2、 读取socket属性:

int getsockopt(SOCKET s, int level, int optname, char FAR * optval, int FAR * optlen)s为欲读取属性的套接字。level为套接字选项的级别,大多数是特定协议和套接字专有的。如IP协议应为 IPPROTO_IP。

optname为读取选项的名称
optval为存放选项值的缓冲区指针。
optlen为缓冲区的长度用法:

int ttl=0; //读取TTL值
int rc = getsockopt( s, IPPROTO_IP, IP_TTL, (char *)&ttl, sizeof(ttl));
//来自MS platform SDK 20033、 设置socket属性:

int setsockopt(SOCKET s,int level, int optname,const char FAR * optval, int optlen)s为欲设置属性的套接字。
level为套接字选项的级别,用法同上。
optname为设置选项的名称
optval为存放选项值的缓冲区指针。
optlen为缓冲区的长度

用法:

int ttl=32; //设置TTL值
int rc = setsockopt( s, IPPROTO_IP, IP_TTL, (char *)&ttl, sizeof(ttl)); 套接字的使用步骤

1、启动Winsock:对Winsock DLL进行初始化,协商Winsock的版本支持并分配必要的
资源。(服务器端和客户端)

int WSAStartup( WORD wVersionRequested, LPWSADATA lpWSAData )

wVersionRequested为打算加载Winsock的版本,一般如下设置:
wVersionRequested=MAKEWORD(2,0)
或者直接赋值:wVersionRequested=2

LPWSADATA为初始化Socket后加载的版本的信息,定义如下:
typedef struct WSAData {
WORD wVersion;
WORD wHighVersion;
char szDescription[WSADESCRIPTION_LEN+1];
char szSystemStatus[WSASYS_STATUS_LEN+1];
unsigned short iMaxSockets;
unsigned short iMaxUdpDg;
char FAR * lpVendorInfo;
} WSADATA, FAR * LPWSADATA;如果加载成功后数据为:

wVersion=2表示加载版本为2.0。
wHighVersion=514表示当前系统支持socket最高版本为2.2。
szDescription="WinSock 2.0"
szSystemStatus="Running"表示正在运行。
iMaxSockets=0表示同时打开的socket最大数,为0表示没有限制。
iMaxUdpDg=0表示同时打开的数据报最大数,为0表示没有限制。
lpVendorInfo没有使用,为厂商指定信息预留。该函数使用方法:

WORD wVersion=MAKEWORD(2,0);
WSADATA wsData;
int nResult= WSAStartup(wVersion,&wsData);
if(nResult !=0)
{
//错误处理
}2、创建套接字:(服务器端和客户端)

SOCKET socket( int af, int type, int protocol );
af为网络地址类型,一般为AF_INET,表示在Internet域中使用。
type为套接字类型,前面已经介绍了。
protocol为指定网络协议,一般为IPPROTO_IP。用法:

SOCKET sock=socket(AF_INET,SOCK_STREAM,IPPROTO_IP);
if(sock==INVALID_SOCKET)
{
//错误处理
}3、套接字的绑定:将本地地址绑定到所创建的套接字上。(服务器端和客户端)

int bind( SOCKET s, const struct sockaddr FAR * name, int namelen )
s为已经创建的套接字。
name为socket地址结构,为sockaddr结构,如前面讨论的,我们一般使用sockaddr_in
结构,在使用再强制转换为sockaddr结构。
namelen为地址结构的长度。
用法:

sockaddr_in addr;
addr. sin_family=AF_INET;
addr. sin_port= htons(0); //保证字节顺序
addr. sin_addr.s_addr= inet_addr("192.1.8.84")
int nResult=bind(s,(sockaddr*)&addr,sizeof(sockaddr));
if(nResult==SOCKET_ERROR)
{
//错误处理
}4、 套接字的监听:(服务器端)

int listen(SOCKET s, int backlog )s为一个已绑定但未联接的套接字。
backlog为指定正在等待联接的最大队列长度,这个参数非常重要,因为服务器一般可
以提供多个连接。
用法:

int nResult=listen(s,5) //最多5个连接
if(nResult==SOCKET_ERROR)
{
//错误处理
}5、套接字等待连接::(服务器端)

SOCKET accept( SOCKET s, struct sockaddr FAR * addr, int FAR * addrlen )s为处于监听模式的套接字。
sockaddr为接收成功后返回客户端的网络地址。
addrlen为网络地址的长度。

用法:

sockaddr_in addr;
SOCKET s_d=accept(s,(sockaddr*)&addr,sizeof(sockaddr));
if(s==INVALID_SOCKET)
{
//错误处理
}6、套接字的连结:将两个套接字连结起来准备通信。(客户端)

int connect(SOCKET s, const struct sockaddr FAR * name, int namelen )s为欲连结的已创建的套接字。
name为欲连结的socket地址。
namelen为socket地址的结构的长度。

用法:

sockaddr_in addr;
addr. sin_family=AF_INET;
addr. sin_port=htons(0); //保证字节顺序
addr. sin_addr.s_addr= htonl(INADDR_ANY) //保证字节顺序
int nResult=connect(s,(sockaddr*)&addr,sizeof(sockaddr));
if(nResult==SOCKET_ERROR)
{
//错误处理
}7、套接字发送数据:(服务器端和客户端)

int send(SOCKET s, const char FAR * buf, int len, int flags )s为服务器端监听的套接字。
buf为欲发送数据缓冲区的指针。
len为发送数据缓冲区的长度。
flags为数据发送标记。
返回值为发送数据的字符数。

◆这里讲一下这个发送标记,下面8中讨论的接收标记也一样:

flag取值必须为0或者如下定义的组合:0表示没有特殊行为。

#define MSG_OOB 0x1 /* process out-of-band data */
#define MSG_PEEK 0x2 /* peek at incoming message */
#define MSG_DONTROUTE 0x4 /* send without using routing tables */
MSG_OOB表示数据应该带外发送,所谓带外数据就是TCP紧急数据。
MSG_PEEK表示使有用的数据复制到缓冲区内,但并不从系统缓冲区内删除。
MSG_DONTROUTE表示不要将包路由出去。

用法:

char buf[]="xiaojin";
int nResult=send(s,buf,strlen(buf));
if(nResult==SOCKET_ERROR)
{
//错误处理
}8、 套接字的数据接收:(客户端)

int recv( SOCKET s, char FAR * buf, int len, int flags )s为准备接收数据的套接字。
buf为准备接收数据的缓冲区。
len为准备接收数据缓冲区的大小。
flags为数据接收标记。
返回值为接收的数据的字符数。

用法:

char mess[1000];
int nResult =recv(s,mess,1000,0);
if(nResult==SOCKET_ERROR)
{
//错误处理
}9、中断套接字连接:通知服务器端或客户端停止接收和发送数据。(服务器端和客户端)

int shutdown(SOCKET s, int how)s为欲中断连接的套接字。
How为描述禁止哪些操作,取值为:SD_RECEIVE、SD_SEND、SD_BOTH。

#define SD_RECEIVE 0x00
#define SD_SEND 0x01
#define SD_BOTH 0x02用法:

int nResult= shutdown(s,SD_BOTH);
if(nResult==SOCKET_ERROR)
{
//错误处理
}10、 关闭套接字:释放所占有的资源。(服务器端和客户端)

int closesocket( SOCKET s )s为欲关闭的套接字。

用法:

int nResult=closesocket(s);
if(nResult==SOCKET_ERROR)
{
//错误处理
}
 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/coffeemay/archive/2006/08/05/1023149.aspx

posted @ 2011-04-29 11:18 沛沛 阅读(428) | 评论 (0)编辑 收藏

如果您想要下一代 Windows 版本 Windows Vista、Windows Server 2003、Windows XP 或 Windows 2000 的完整 Symbol,您可以下載 Symbol 封裝然後將它安裝在您的電腦上。

在此 Symbol 下載封裝是依處理器型態 (x86、Itanium 和 x64) 以及 Build 類別 (retail 以及 checked) 來列示。幾乎所有的客戶都需要用於 Retail 版本的 Symbol。如果您正在 Debugging 一個特殊版本的 Windows 需要額外的 Debugging 資訊,那您就應該下載用於 Checked 版本的 Symbol。

Windows XP 與 Windows Server 2003 不需要當地語系化版本的 Symbol 來 Debug 當地語系化版本的產品。每一個 Windows XP 與 Windows Server™ 2003 Symbol 下載封裝皆可用於 Debugging 所有已當地語系化的版本。

每一個 x86 Symbol 可能需要 750 MB 或更大的硬碟空間,每個 Itanium Symbol 可能需要 560 MB 或更大的硬碟空間,而每個 x64 Symbol 套件可能需要 640 MB 或更大空間。此空間需求量包含下載的封裝與其所需的暫存檔案所要求的空間大小,建議您在下載與安裝任可 Symbol 套件之前至少預留 1 GB 的可用硬碟空間。

注意:若您為 MSDN Subscriber 且使用的是 Windows Vista CTP 版本,您可由 Microsoft Connect 伺服器取得 CTP 版本使用之 Symbols 封裝套件。若您目前並非 MSDN Subscriber 但想成為 MSDN Subscriber,您可以由此處加入。

檢視用於 Symbol 封裝的下載連結

   Windows Vista Release Candidate 1 (RC1)
 
  這些封裝檔案中包含了所有用於 Debug Windows Vista Release Candidate 1 (RC1) 的完整 Symbol 套件。

Windows Vista RC1 x86 retail symbols, all languages (檔案大小:270 MB - 大部份的客戶需要這個封裝套件。)
Windows Vista RC1 Itanium retail symbols, all languages (檔案大小:148 MB)
Windows Vista RC1 x64 retail symbols, all languages (檔案大小:205 MB)
Windows Vista RC1 x86 checked symbols, all languages (檔案大小:248 MB)
Windows Vista RC1 Itanium checked symbols, all languages (檔案大小:187 MB)
Windows Vista RC1 x64 checked symbols, all languages (檔案大小:224 MB)
 
   Windows Server 2003 and Windows XP x64 Edition
 
  含 Service Pack 1 的 Windows Server 2003 Symbols

此封裝包含在 Debug 己安裝 Service Pack 1 之 Windows Server 2003 的完整 Symbol 套件。其中 Windows Server 2003 的 Symbol 己被更新為符合 Windows Server 2003 Service Pack 1 更新檔案的 Symbol。

附註:Windows Server 2003 SP1 x64-based Symbol 套件亦可用於 Windows XP x64 Edition。

Windows Server 2003 with Service Pack 1 x86 retail symbols, all languages (檔案大小:153 MB - 大部份的客戶需要這個封裝套件。)
Windows Server 2003 with Service Pack 1 x86 checked symbols, all languages (檔案大小:146 MB)
Windows Server 2003 with Service Pack 1 Itanium-based retail symbols, all languages (檔案大小:102 MB)
Windows Server 2003 with Service Pack 1 Itanium-based checked symbols, all languages (檔案大小:123 MB)
Windows Server 2003 with Service Pack 1 x64-based retail symbols, all languages (檔案大小:123 MB)
Windows Server 2003 with Service Pack 1 x64-based checked symbols, all languages (檔案大小:113 MB)
減少下載的量:Windows Server 2003 Service Pack 1

此封裝比含有 Service Pack 1 的 Windows Server 2003 完整套件的下載量來的小。它僅包含隨附於 Windows Server 2003 Service Pack 1 之檔案的 Symbol。若您己經安裝了 Windows Server 2003 的 Symbol,您可以將它安裝在相同的路徑,這樣您就擁有了含有 Service Pack 1 的 Windows Server 2003 完整 Symbol 套件。

Windows Server 2003 Service Pack 1 x86 retail symbols, all languages (檔案大小:130 MB - 此為大多數客戶所需要的封裝。)
Windows Server 2003 Service Pack 1 x86 checked symbols, all languages (檔案大小:121 MB)
Windows Server 2003 Service Pack 1 Itanium-based retail symbols, all languages (檔案大小:91 MB)
Windows Server 2003 Service Pack 1 Itanium-based checked symbols, all languages (檔案大小:110 MB)
不包含 Service Pack 的 Windows Server 2003 Symbols

 

Windows Server 2003 x86 retail symbols, all languages (檔案大小:168 MB - 此為大多數客戶所需要的封裝。)
Windows Server 2003 Itanium retail symbols, all languages (檔案大小:105 MB)
Windows Server 2003 x86 checked symbols, all languages (檔案大小:163 MB)
Windows Server 2003 Itanium checked symbols, all languages (檔案大小:123 MB)
 
   Windows XP
 
  含 Service Pack 2 的 Windows XP Symbol

此封裝包含在 Debug 己安裝 Service Pack 2 之 Windows XP 的完整 Symbol 套件。其中 Windows XP 的 Symbol 己被更新為符合 Windows XP Service Pack 2 更新檔案的 Symbol。

 

Windows XP with Service Pack 2 x86 retail symbols, all languages (檔案大小:195 MB - 此為大多數客戶所需要的封裝。)
Windows XP with Service Pack 2 x86 checked symbols, all languages (檔案大小:188 MB)
減少下載的量:Windows XP Service Pack 2

此封裝比含有 Service Pack 2 的 Windows XP 完整套件的下載量來的小。它僅包含隨附於 Service Pack 2 之檔案的 Symbol。若您己經安裝了 Windows XP 的 Symbol,您可以將它安裝在相同的路徑,這樣您就擁有了含有 Service Pack 2 的 Windows XP 完整 Symbol 套件。

 

Windows XP Service Pack 2 x86 retail symbols, all languages (檔案大小:145 MB - 此為大多數客戶所需要的封裝。)
Windows XP Service Pack 2 x86 checked symbols, all languages (檔案大小:132 MB)
Windows XP with Service Pack 1 以及 Service Pack 1a Symbol

此封裝包含在 Debug 己安裝 Service Pack 1 或 Service Pack 1a 之 Windows XP 的完整 Symbol 套件。其中 Windows XP 的 Symbol 己被更新為符合 Windows XP Service Pack 1 以及 Service Pack 1a 更新檔案的 Symbol。

 

Windows XP with Service Pack 1 and Service Pack 1a x86 retail symbols, all languages (檔案大小:172 MB - 此為大多數客戶所需要的封裝。)
Windows XP with Service Pack 1 and Service Pack 1a Itanium retail symbols, all languages (檔案大小:101 MB)
Windows XP with Service Pack 1 and Service Pack 1a x86 checked symbols, all languages (檔案大小:168 MB)
Windows XP with Service Pack 1 and Service Pack 1a Itanium checked symbols, all languages (檔案大小:124 MB)
減少下載的量:Windows XP Service Pack 1 以及 Service Pack 1a Symbol

此封裝比含有 Service Pack 1 以及 Service Pack 1a 的 Windows XP 完整套件的下載量來的小。它僅包含隨附於 Service Pack 1 以及 Service Pack 1a 之檔案的 Symbol。若您己經安裝了 Windows XP 的 Symbol,您可以將它安裝在相同的路徑,這樣您就擁有了含有 Service Pack 1 以及 Service Pack 1a 的 Windows XP 完整 Symbol 套件。

 

Windows XP Service Pack 1 and Service Pack 1a x86 retail symbols, all languages (檔案大小:103 MB - Most customers want this package.)
Windows XP Service Pack 1 and Service Pack 1a Itanium retail symbols, all languages (檔案大小:50 MB)
Windows XP Service Pack 1 and Service Pack 1a x86 checked symbols, all languages (檔案大小:96 MB)
Windows XP Service Pack 1 and Service Pack 1a Itanium checked symbols, all languages (檔案大小:63 MB)
Windows XP Symbol 其中不含 Service Pack

 

Windows XP x86 retail symbols, all languages (檔案大小:149 MB - 此為大多數客戶所需要的封裝。)
Windows XP IA-64 retail symbols, all languages (檔案大小:95 MB)
Windows XP x86 checked symbols, all languages (檔案大小:147 MB)
Windows XP IA-64 checked symbols, all languages (檔案大小:116 MB)
 
   Windows 2000
 
  下列連結連接至每一個 Symbols 封裝的下載處,或是帶領您至提供關於 Windows 2000 Symbol 下載資訊的網站。

Windows 2000 SP4 的更新彙總套件 1 (Update Rollup 1)。欲安裝 Windows 2000 SP4 的更新彙總套件 1 中所包括的新增 Symbols,請在下方的下拉式選單中選擇您想要下載的語言版本然後在 "按一下這裡開始下載" 的連結按一下。欲擁有完整的 Symbol 套件,您必須先安裝 Windows 2000 Symbol,之後接著安裝 Service Pack 4 Symbol,最後再安裝 Windows 2000 SP4 更新彙總套件 1 的 Symbol。

< language="javascript" type="text/javascript"> function ShowSelectInfoSP4UR1() { oElement = window.event.srcElement for (i=0; i 選擇所需的語言版本:
 
  Arabic Chinese (Simplified) Chinese (Traditional) Czech Danish Dutch English Finnish French German Greek Hebrew Hungarian Italian Japanese Japanese NEC98 Korean Norwegian Polish Portugese Portugese (Brazilian) Russian Spanish Swedish Turkish    按一下這裡開始下載 (下載量: 22 MB)

  按一下這裡開始下載 (下載量: 22 MB)

  按一下這裡開始下載 (下載量: 22 MB)

  按一下這裡開始下載 (下載量: 22 MB)

  按一下這裡開始下載 (下載量: 22 MB)

  按一下這裡開始下載 (下載量: 22 MB)

  按一下這裡開始下載 (下載量: 22 MB)

  按一下這裡開始下載 (下載量: 22 MB)

  按一下這裡開始下載 (下載量: 22 MB)

  按一下這裡開始下載 (下載量: 22 MB)

  按一下這裡開始下載 (下載量: 22 MB)

  按一下這裡開始下載 (下載量: 22 MB)

  按一下這裡開始下載 (下載量: 22 MB)

  按一下這裡開始下載 (下載量: 22 MB)

  按一下這裡開始下載 (下載量: 22 MB)

  按一下這裡開始下載 (下載量: 22 MB)

  按一下這裡開始下載 (下載量: 22 MB)

  按一下這裡開始下載 (下載量: 22 MB)

  按一下這裡開始下載 (下載量: 22 MB)

  按一下這裡開始下載 (下載量: 22 MB)

  按一下這裡開始下載 (下載量: 22 MB)

  按一下這裡開始下載 (下載量: 22 MB)

  按一下這裡開始下載 (下載量: 22 MB)

  按一下這裡開始下載 (下載量: 22 MB)

  按一下這裡開始下載 (下載量: 22 MB)
 

Windows 2000 Service Pack 4 。安裝 Service Pack 4 所提供的額外 Symbol,請在下面的下拉式表單中選擇您想要下載的語言然後按一下 "按一下這裡開始下載" 的連結項目。欲擁有完整的 Symbol 套件,您必須在安裝 Service Pack 4 Symbol 前先安裝 Windows 2000 Symbol。

< language="javascript" type="text/javascript"> function ShowSelectInfoSP4() { oElement = window.event.srcElement for (i=0; i 選擇所需的語言版本:
 
  Arabic Chinese (Simplified) Chinese (Traditional) Chinese (Hong Kong SAR) Czech Danish Dutch English Finnish French German Greek Hebrew Hungarian Italian Japanese Japanese NEC98 Korean Norwegian Polish Portugese Portugese (Brazilian) Russian Spanish Swedish Turkish    按一下這裡開始下載 (下載量:72 MB)

  按一下這裡開始下載 (下載量:72 MB)

  按一下這裡開始下載 (下載量:74 MB)

  按一下這裡開始下載 (下載量:74 MB)

  按一下這裡開始下載 (下載量:74 MB)

  按一下這裡開始下載 (下載量:74 MB)

  按一下這裡開始下載 (下載量:74 MB)

  按一下這裡開始下載 (下載量:74 MB)

  按一下這裡開始下載 (下載量:74 MB)

  按一下這裡開始下載 (下載量:74 MB)

  按一下這裡開始下載 (下載量:74 MB)

  按一下這裡開始下載 (下載量:74 MB)

  按一下這裡開始下載 (下載量:74 MB)

  按一下這裡開始下載 (下載量:74 MB)

  按一下這裡開始下載 (下載量:74 MB)

  按一下這裡開始下載 (下載量:74 MB)

  按一下這裡開始下載 (下載量:74 MB)

  按一下這裡開始下載 (下載量:74 MB)

  按一下這裡開始下載 (下載量:74 MB)

  按一下這裡開始下載 (下載量:74 MB)

  按一下這裡開始下載 (下載量:74 MB)

  按一下這裡開始下載 (下載量:74 MB)

  按一下這裡開始下載 (下載量:74 MB)

  按一下這裡開始下載 (下載量:74 MB)

  按一下這裡開始下載 (下載量:74 MB)

  按一下這裡開始下載 (下載量:74 MB)
 

若您有特殊版本的 Service Pack 4 且需要更多的 Debugging 資訊,那您應該下載用於 Checked 版本的 Symbol。下載英文版的 Service Pack 4 Checked 版本,請按一下這裡 (下載量: 66 MB)。

Windows 2000 Service Pack 3。安裝 Service Pack 3 所提供的額外 Symbol,請在下面的下拉式表單中選擇您想要下載的語言然後按一下 "按一下這裡開始下載" 的連結項目。欲擁有完整的 Symbol 套件,您必須在安裝 Service Pack 3 Symbol 前先安裝 Windows 2000 Symbol。

< language="javascript" type="text/javascript"> function ShowSelectInfo() { oElement = window.event.srcElement for (i=0; i 選擇所需的語言:
 
  English French German Italian Japanese Japanese NEC98 Portugese (Brazilian) Spanish    按一下這裡開始下載 (下載量:67 MB)

  按一下這裡開始下載 (下載量:67 MB)

  按一下這裡開始下載 (下載量:67 MB)

  按一下這裡開始下載 (下載量:67 MB)

  按一下這裡開始下載 (下載量:67 MB)

  按一下這裡開始下載 (下載量:67 MB)

  按一下這裡開始下載 (下載量:67 MB)

  按一下這裡開始下載 (下載量:67 MB)
 

若您有特殊版本的 Service Pack 3 且需要更多的 Debugging 資訊,那您應該下載用於 Checked 版本的 Symbol。下載英文版的 Service Pack 3 Checked 版本,請按一下此處。

Windows 2000 Service Pack 2 Security Rollup Package 1。安裝 Service Pack 2 Security Rollup Package 1 所提供的額外 Symbol,請在下面的下拉式表單中選擇您想要下載的語言然後按一下 "按一下這裡開始下載" 的連結項目。欲擁有完整的 Symbol 套件,您必須先安裝 Windows 2000 Symbol,再安裝 Service Pack 2 Symbol,最後再安裝 Service Pack 2 Security Rollup Package 1 Symbol。

選擇所需的語言:
 
  English Arabic Chinese (Simplified) Chinese (Traditional) Czech Danish Dutch Finnish French German Greek Hebrew Hungarian Italian Japanese Japanese NEC98 Korean Norwegian Polish Portugese Portugese (Brazilian) Russian Spanish Swedish Turkish    按一下這裡開始下載 (下載量:19 MB)

  按一下這裡開始下載 (下載量:19 MB)

  按一下這裡開始下載 (下載量:19 MB)

  按一下這裡開始下載 (下載量:19 MB)

  按一下這裡開始下載 (下載量:19 MB)

  按一下這裡開始下載 (下載量:19 MB)

  按一下這裡開始下載 (下載量:19 MB)

  按一下這裡開始下載 (下載量:19 MB)

  按一下這裡開始下載 (下載量:19 MB)

  按一下這裡開始下載 (下載量:19 MB)

  按一下這裡開始下載 (下載量:19 MB)

  按一下這裡開始下載 (下載量:19 MB)

  按一下這裡開始下載 (下載量:19 MB)

  按一下這裡開始下載 (下載量:19 MB)

  按一下這裡開始下載 (下載量:19 MB)

  按一下這裡開始下載 (下載量:19 MB)

  按一下這裡開始下載 (下載量:19 MB)

  按一下這裡開始下載 (下載量:19 MB)

  按一下這裡開始下載 (下載量:19 MB)

  按一下這裡開始下載 (下載量:19 MB)

  按一下這裡開始下載 (下載量:19 MB)

  按一下這裡開始下載 (下載量:19 MB)

  按一下這裡開始下載 (下載量:19 MB)

  按一下這裡開始下載 (下載量:19 MB)

  按一下這裡開始下載 (下載量:19 MB)
 

Windows 2000 Service Pack 2 Symbols - 跟隨 SP2 所新增的 Symbol。欲擁有用於含有 Service Pack 2 之 Windows 2000 的完整 Symbol 套件,您必須先安裝 Windows 2000 Symbo 然後再安裝 Service Pack 2 Symbol。

Windows 2000 Service Pack 1 Symbols - 跟隨 SP1 所新增的 Symbol。欲擁有用於含有 Service Pack 1 之 Windows 2000 的完整 Symbol 套件,您必須先安裝 Windows 2000 Symbo 然後再安裝 Service Pack 1 Symbol。

Windows 2000 Symbols - 用於 Debugging Windows 2000 所需的檔案。
 


 


Debugging 說明檔 - 為了幫助您 Debugging 問題,請提送線上說明檔的要求或是使用附加的資源於 DDK 開發人員支援。

意見反應 - 我們期待得到您闗於 Symbol 的意見反應。請將您的建議或是錯誤報告 windbgfb@microsoft.com。 雖然您無法從此處取得技術支援,但是您的意見將可以幫助我們未來在 Symbol 的變更計畫並且使得它在未來更合於您的需求。

Last Updated: 2006 年 9 月 13 日


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/coffeemay/archive/2006/10/12/1331356.aspx

posted @ 2011-04-29 11:13 沛沛 阅读(757) | 评论 (0)编辑 收藏

1、 在介绍静态对象、全局对象与程序的运行机制之间的关系之前,我们首先看一下atexit函数。

atexit函数的声明为:int atexit( void ( __cdecl *func )( void ) );

参数为函数指针,返回值为整型,0表示成功,其他表示失败。当程序运行结束时,他调用atexit函数注册的所有函数。如果多次调用atexit函数,那么系统将以LIFO(last-in-first-out)的方式调用所有的注册函数。

举例如下(代码摘自MSDN):

 1#include <stdlib.h>
 2#include <stdio.h>
 3 
 4void fn1( void ), fn2( void ), fn3( void ), fn4( void );
 5 
 6void main( void )
 7{
 8  atexit( fn1 );
 9  atexit( fn2 );
10  atexit( fn3 );
11  atexit( fn4 );
12  printf( "This is executed first.\n" );
13}

14 
15void fn1()
16{
17  printf( "next.\n" );
18}

19 
20void fn2()
21{
22  printf( "executed " );
23}

24 
25void fn3()
26{
27  printf( "is " );
28}

29 
30void fn4()
31{
32  printf( "This " );
33}

34



编译、运行程序后,程序的输出为:
This is executed first.
This is executed next.
注册函数的顺序为:fn1、fn2、fn3、fn4,但是调用顺序为fn4、fn3、fn2、fn1。

2、理解了atexit函数之后,我们就可以来看看局部静态对象了。

1 class AAA{ … } ;
2 AAA* createAAA() 
3 {
4         static AAA a ;
5         return &a ;
6 }
7 


在调试状态下,汇编代码如下(请观察蓝色标记出来的代码):
AAA* createAAA()
{
     …
     static AAA a ;

[1] 00401056 call        AAA::AAA (4010A0h)
[2] 0040105B push      offset `createAAA'::`2'::a::`dynamic atexit destructor' (442410h)
[3] 00401060 call        atexit (409A50h)
00401065 add            esp,4
00401068 mov           dword ptr [ebp-4],0FFFFFFFFh
     return &a ;
0040106F mov           eax,offset a (452620h)
}

00401091 ret  
注:[1]、[2]、[3]为方便说明加入的字符,实际代码中并不存在。
[1]语句很明显的调用AAA的构造函数。
[2]语句将442410h压入栈中。
[3]语句调用atexit函数,根据我们的了解,atexit的参数应该是函数指针。那么我们来分析一下442410h处的代码,从注释来看,我们看到了destructor。代码如下:
`createAAA'::`2'::a::`dynamic atexit destructor':

[1] 0044242E mov         ecx,offset a (452620h)
[2] 00442433 call        AAA::~AAA (403A90h)

0044244B ret           
[1]语句将a的地址放在ecx寄存器中,这是this调用规范的风格。
[2]语句调用AAA的析构函数。

程序结束时,将调用atexit函数注册的442410h处的函数,进而调用了AAA的析构函数。从而保证了析构函数的调用。

 
3、   了解了局部静态对象之后,我们来看看全局对象。
我们知道全局对象必须在main函数前已经被构造。为了弄清楚全局对象何时被构造,我在全局对象的实例化处设置了断点,调用堆栈如下:
static.exe!aaaa::`dynamic initializer'() Line 22 C++
static.exe!_initterm(void (void)* * pfbegin=0x00451038, void (void)* * pfend=0x00451064) Line 707 C
static.exe!_cinit(int initFloatingPrecision=1) Line 208 + 0xf bytes C
static.exe!mainCRTStartup() Line 266 + 0x7 bytes C


作为对比,我在AAA的析构函数出设置了断点,调用堆栈如下:
     static.exe!AAA::~AAA() Line 19  C++
     static.exe!aaaa::`dynamic atexit destructor'() + 0x28 bytes  C++
     static.exe!doexit(int code=0, int quick=0, int retcaller=0) Line 451  C
     static.exe!exit(int code=0) Line 311 + 0xd bytes  C
     static.exe!mainCRTStartup() Line 289  C


由此我们可以看出程序的实际入口点位mainCRTStartup而不是main函数(相对于ANSI的控制台程序而言)。
我们来分析一下_cinit函数:
注释中有一句[3. General C initializer routines],看来该函数的功能之一是完成C的初始化例程。
函数的核心代码如下:
/*
         * do initializations
         */
        initret = _initterm_e( __xi_a, __xi_z );
/*
         * do C++ initializations
         */
        _initterm( __xc_a, __xc_z );
看来该函数主要进行C、C++的初始化。我们进一步分析函数_initterm_e和_initterm,两个函数的功能进本相同,都是遍历函数指针(由参数指定函数指针的开始位置[__xi_a、__xi_z]、结束位置[__xc_a、__xc_z]),如果函数指针不为null,那么调用该函数。
那么__xi_a、__xi_z和__xc_a、__xc_z到底代表了什么呢?在cinitexe.c文件中有如下代码:
#pragma data_seg(".CRT$XIA")
_CRTALLOC(".CRT$XIA") _PVFV __xi_a[] = { NULL };
 
#pragma data_seg(".CRT$XIZ")
_CRTALLOC(".CRT$XIZ") _PVFV __xi_z[] = { NULL };/* C initializers */
 
#pragma data_seg(".CRT$XCA")
_CRTALLOC(".CRT$XCA") _PVFV __xc_a[] = { NULL };
 
#pragma data_seg(".CRT$XCZ")
_CRTALLOC(".CRT$XCZ") _PVFV __xc_z[] = { NULL };/* C++ initializers */
#pragma comment(linker, "/merge:.CRT=.data")
可以看出这四个变量分别在数据段.CRT$XIA、.CRT$XIZ、.CRT$XCA、.CRT$XCZ中。当连接器布局代码时,它按根据的名称,按照字母排序的规则,排列所有段。这样在段.CRT$XIA中的变量出现在段.CRT$XIZ所有变量之前,从而形成链表。对于.CRT$XCA、.CRT$XCZ数据段同理。最后这四个数据段被合并到.data数据段中。
再看看这些变量的类型,typedef void (__cdecl *_PVFV)(void); 所以这些变量组成了2个初始化函数指针链表。
调试过程中,看到__xc_a、__xc_z链表中,指向的初始化函数很多是构造函数,如:
static std::_Init_locks initlocks;
static filebuf fout(_cpp_stdout);
extern _CRTDATA2 ostream cout(&fout);
cout对象也在此时被构造。
对于析构函数的调用也是采用相同的方式,只是此时每一种初始化,都有一种终止函数与之对应。

4、 总结
l         编译、连接程序时,编译器将所有全局对象的初始化函数放入.CRT$Xx中,连接器将所有的.CRT$XCx段合并成为.rdata数据段。在.CRT$XCA 到 .CRT$XCZ的所有段的数据组成初始化函数指针列表。
l   函数执行时,_initterm( __xc_a, __xc_z )函数调用所有的初始化函数。构造全局对象。构造对象完毕,调用atexit函数来保证析构函数的调用。Modern C++ Design就是通过控制调用atexit函数来决定对象的析构顺序的。
l   对于静态对象使用atexit来保证析构函数的调用。
l    程序结束时,调用exit来析构全局对象或静态对象。


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/coffeemay/archive/2006/11/19/1395250.aspx

posted @ 2011-04-29 11:06 沛沛 阅读(810) | 评论 (0)编辑 收藏

     摘要: 标 题: 【翻译】“PE文件格式”1.9版 完整译文(附注释)作 者: ah007时 间: 2006-02-28,13:32链 接: http://bbs.pediy.com/showthread.php?threadid=21932 $Id: pe.txt,v 1.9 1999/03/20 23:55:09 LUEVELSMEYER Exp $ PE文件格式系列译文之...  阅读全文
posted @ 2011-04-29 11:03 沛沛 阅读(296) | 评论 (0)编辑 收藏

我们知道,在NT/2K/XP中,操作系统利用虚拟内存管理技术来维护地址空间映像,每个进程分配一个4GB的虚拟地址空间。运行在用户态的应用程序,不能直接访问物理内存地址;而运行在核心态的驱动程序,能将虚拟地址空间映射为物理地址空间,从而访问物理内存地址。

如果要在应用程序中以物理地址方式访问内存,自然而然的办法,是编写一个专用的驱动程序(如大家熟悉的WinIO),里面设置一定的IOCTL码,应用程序通过调用DeviceIoCtrol()来实现这样的功能。

那么,有没有一种方法,省去编写专用驱动程序这一步,很方便地就能访问物理内存呢?答案是肯定的。实际上,微软早就给我们准备好了一套办法,只是他们秘而不宣罢了。系统内建一个叫做PhysicalMemory的内核对象,可以通过系统核心文件NTDLL.DLL中的有关API进行操纵,从而实现物理内存的直接访问。微软声称这些API是用于驱动程序开发的,在VC/.NET中未提供原型说明和库文件,然而事实证明在应用程序中调用它们是没有问题的。我们感兴趣的API主要包括:

ZwOpenSection 或 NtOpenSection - 打开内核对象
ZwMapViewOfSection 或 NtMapViewOfSection - 映射虚拟地址空间
ZwUnmapViewOfSection 或 NtUnmapViewOfSection - 取消地址空间映射
RtlInitUnicodeString - 用UNICODE串初始化UNICODE描述的结构
以下的代码描述了如何利用NTDLL.DLL中的上述几个API,实现对物理内存的读取。需要指出的是,只有system拥有读写权限,administrator只有读权限,而user连读权限都没有。这一点,是不能与专用驱动程序方法向相比的。

在VC/.NET中,由于没有相应的原型说明和库文件,我们用GetProcAddress()进行DLL显式调用。前面大段的代码,用于说明必需的类型和结构。读取物理内存的主要步骤为:打开内核对象 → 映射虚拟地址空间 → 读取(复制)内存 → 取消地址空间映射。

typedef LONG    NTSTATUS;
 
typedef struct _UNICODE_STRING
{
    USHORT Length;
    USHORT MaximumLength;
    PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;
 
typedef enum _SECTION_INHERIT
{
    ViewShare = 1,
    ViewUnmap = 2
} SECTION_INHERIT, *PSECTION_INHERIT;
 
typedef struct _OBJECT_ATTRIBUTES
{
    ULONG Length;
    HANDLE RootDirectory;
    PUNICODE_STRING ObjectName;
    ULONG Attributes;
    PVOID SecurityDescriptor;
    PVOID SecurityQualityOfService;
} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;
 
#define InitializeObjectAttributes( p, n, a, r, s ) { \
    (p)->Length = sizeof( OBJECT_ATTRIBUTES ); \
    (p)->RootDirectory = r; \
    (p)->Attributes = a; \
    (p)->ObjectName = n; \
    (p)->SecurityDescriptor = s; \
    (p)->SecurityQualityOfService = NULL; \
}
 
// Interesting functions in NTDLL
typedef NTSTATUS (WINAPI *ZwOpenSectionProc)
(
    PHANDLE SectionHandle,
    DWORD DesiredAccess,
    POBJECT_ATTRIBUTES ObjectAttributes
);
typedef NTSTATUS (WINAPI *ZwMapViewOfSectionProc)
(
    HANDLE SectionHandle,
    HANDLE ProcessHandle,
    PVOID *BaseAddress,
    ULONG ZeroBits,
    ULONG CommitSize,
    PLARGE_INTEGER SectionOffset,
    PULONG ViewSize,
    SECTION_INHERIT InheritDisposition,
    ULONG AllocationType,
    ULONG Protect
);
typedef NTSTATUS (WINAPI *ZwUnmapViewOfSectionProc)
(
    HANDLE ProcessHandle,
    PVOID BaseAddress
);
typedef VOID (WINAPI *RtlInitUnicodeStringProc)
(
    IN OUT PUNICODE_STRING DestinationString,
    IN PCWSTR SourceString
);
 
// Global variables
static HMODULE hModule = NULL;
static HANDLE hPhysicalMemory = NULL;
static ZwOpenSectionProc ZwOpenSection;
static ZwMapViewOfSectionProc ZwMapViewOfSection;
static ZwUnmapViewOfSectionProc ZwUnmapViewOfSection;
static RtlInitUnicodeStringProc RtlInitUnicodeString;
 
// initialize
BOOL InitPhysicalMemory()
{
    if (!(hModule = LoadLibrary("ntdll.dll")))
    {
        return FALSE;
    }
 
    // 以下从NTDLL获取我们需要的几个函数指针
    if (!(ZwOpenSection = (ZwOpenSectionProc)GetProcAddress(hModule, "ZwOpenSection")))
    {
        return FALSE;
    }
 
    if (!(ZwMapViewOfSection = (ZwMapViewOfSectionProc)GetProcAddress(hModule, "ZwMapViewOfSection")))
    {
        return FALSE;
    }
 
    if (!(ZwUnmapViewOfSection = (ZwUnmapViewOfSectionProc)GetProcAddress(hModule, "ZwUnmapViewOfSection")))
    {
        return FALSE;
    }
 
    if (!(RtlInitUnicodeString = (RtlInitUnicodeStringProc)GetProcAddress(hModule, "RtlInitUnicodeString")))
    {
        return FALSE;
    }
 
    // 以下打开内核对象
    WCHAR PhysicalMemoryName[] = L"\\Device\\PhysicalMemory";
    UNICODE_STRING PhysicalMemoryString;
    OBJECT_ATTRIBUTES attributes;
    RtlInitUnicodeString(&PhysicalMemoryString, PhysicalMemoryName);
    InitializeObjectAttributes(&attributes, &PhysicalMemoryString, 0, NULL, NULL);
    NTSTATUS status = ZwOpenSection(&hPhysicalMemory, SECTION_MAP_READ, &attributes );
 
    return (status >= 0);
}
 
// terminate -- free handles
void ExitPhysicalMemory()
{
    if (hPhysicalMemory != NULL)
    {
        CloseHandle(hPhysicalMemory);
    }
 
    if (hModule != NULL)
    {
        FreeLibrary(hModule);
    }
}
 
BOOL ReadPhysicalMemory(PVOID buffer, DWORD address, DWORD length)
{
    DWORD outlen;            // 输出长度,根据内存分页大小可能大于要求的长度
    PVOID vaddress;          // 映射的虚地址
    NTSTATUS status;         // NTDLL函数返回的状态
    LARGE_INTEGER base;      // 物理内存地址
 
    vaddress = 0;
    outlen = length;
    base.QuadPart = (ULONGLONG)(address);
 
    // 映射物理内存地址到当前进程的虚地址空间
    status = ZwMapViewOfSection(hPhysicalMemory,
        (HANDLE) -1,
        (PVOID *)&vaddress,
        0,
        length,
        &base,
        &outlen,
        ViewShare,
        0,
        PAGE_READONLY);
 
    if (status < 0)
    {
        return FALSE;
    }
 
    // 当前进程的虚地址空间中,复制数据到输出缓冲区
    memmove(buffer, vaddress, length);
 
    // 完成访问,取消地址映射
    status = ZwUnmapViewOfSection((HANDLE)-1, (PVOID)vaddress);
 
    return (status >= 0);
}
 
// 一个测试函数,从物理地址0xfe000开始,读取4096个字节
// 对于Award BIOS,可以从这段数据找到序列号等信息
BOOL test()
{
    UCHAR buf[4096];
 
    if (!InitPhysicalMemory())
    {
        return FALSE;
    }
 
    if (!ReadPhysicalMemory(buf, 0xfe000, 4096))
    {
        // ... 成功读取了指定数据
        ExitPhysicalMemory();
        return FALSE;
    }
 
    ExitPhysicalMemory();
 
    return TRUE;
}

补充说明一点,由于Windows虚拟内存页面大小默认是4KB,NtMapViewOfSection()返回的虚拟空间基址是按照4KB对齐的,返回的

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/coffeemay/archive/2006/10/28/1354465.aspx

posted @ 2011-04-29 11:00 沛沛 阅读(605) | 评论 (0)编辑 收藏

Debug和Release有什么区别?怎么把Debug转成Release ?
1。Debug和Release有什么区别,为什么要使用Release版本! 
2。怎么把Debug转成Release 

转载: 

Debug版本包括调试信息,所以要比Release版本大很多(可能大数百K至数M)。至于是否需要DLL支持,主要看你采用的编译选项。如果是基于ATL的,则Debug和Release版本对DLL的要求差不多。如果采用的编译选项为使用MFC动态库,则需要MFC42D.DLL等库支持,而Release版本需要MFC42.DLL支持。Release   Build不对源代码进行调试,不考虑MFC的诊断宏,使用的是MFC   Release库,编译十对应用程序的速度进行优化,而Debug   Build则正好相反,它允许对源代码进行调试,可以定义和使用MFC的诊断宏,采用MFC   Debug库,对速度没有优化。   


一、Debug   和   Release   编译方式的本质区别 

Debug   通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序。Release   称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好地使用。 
Debug   和   Release   的真正秘密,在于一组编译选项。下面列出了分别针对二者的选项(当然除此之外还有其他一些,如/Fd   /Fo,但区别并不重要,通常他们也不会引起   Release   版错误,在此不讨论) 

Debug   版本: 
/MDd   /MLd   或   /MTd   使用   Debug   runtime   library(调试版本的运行时刻函数库) 
/Od   关闭优化开关 
/D   "_DEBUG"   相当于   #define   _DEBUG,打开编译调试代码开关(主要针对 
assert函数) 
/ZI   创建   Edit   and   continue(编辑继续)数据库,这样在调试过 
程中如果修改了源代码不需重新编译 
/GZ   可以帮助捕获内存错误 
/Gm   打开最小化重链接开关,减少链接时间 

Release   版本:   
/MD   /ML   或   /MT   使用发布版本的运行时刻函数库 
/O1   或   /O2   优化开关,使程序最小或最快 
/D   "NDEBUG"   关闭条件编译调试代码开关(即不编译assert函数) 
/GF   合并重复的字符串,并将字符串常量放到只读内存,防止 
被修改 

实际上,Debug   和   Release   并没有本质的界限,他们只是一组编译选项的集合,编译器只是按照预定的选项行动。事实上,我们甚至可以修改这些选项,从而得到优化过的调试版本或是带跟踪语句的发布版本。 

二、哪些情况下   Release   版会出错 

有了上面的介绍,我们再来逐个对照这些选项看看   Release   版错误是怎样产生的 

1.   Runtime   Library:链接哪种运行时刻函数库通常只对程序的性能产生影响。调试版本的   Runtime   Library   包含了调试信息,并采用了一些保护机制以帮助发现错误,因此性能不如发布版本。编译器提供的   Runtime   Library   通常很稳定,不会造成   Release   版错误;倒是由于   Debug   的   Runtime   Library   加强了对错误的检测,如堆内存分配,有时会出现   Debug   有错但   Release   正常的现象。应当指出的是,如果   Debug   有错,即使   Release   正常,程序肯定是有   Bug   的,只不过可能是   Release   版的某次运行没有表现出来而已。 

2.   优化:这是造成错误的主要原因,因为关闭优化时源程序基本上是直接翻译的,而打开优化后编译器会作出一系列假设。这类错误主要有以下几种: 

(1)   帧指针(Frame   Pointer)省略(简称   FPO   ):在函数调用过程中,所有调用信息(返回地址、参数)以及自动变量都是放在栈中的。若函数的声明与实现不同(参数、返回值、调用方式),就会产生错误————但   Debug   方式下,栈的访问通过   EBP   寄存器保存的地址实现,如果没有发生数组越界之类的错误(或是越界“不多”),函数通常能正常执行;Release   方式下,优化会省略   EBP   栈基址指针,这样通过一个全局指针访问栈就会造成返回地址错误是程序崩溃。C++   的强类型特性能检查出大多数这样的错误,但如果用了强制类型转换,就不行了。你可以在   Release   版本中强制加入   /Oy-   编译选项来关掉帧指针省略,以确定是否此类错误。此类错误通常有: 

●   MFC   消息响应函数书写错误。正确的应为 
afx_msg   LRESULT   OnMessageOwn(WPARAM   wparam,   LPARAM   lparam); 
ON_MESSAGE   宏包含强制类型转换。防止这种错误的方法之一是重定义   ON_MESSAGE   宏,把下列代码加到   stdafx.h   中(在#include   "afxwin.h"之后),函数原形错误时编译会报错 
#undef   ON_MESSAGE 
#define   ON_MESSAGE(message,   memberFxn)   \ 
{   message,   0,   0,   0,   AfxSig_lwl,   \ 
(AFX_PMSG)(AFX_PMSGW)(static_cast<   LRESULT   (AFX_MSG_CALL   \ 
CWnd::*)(WPARAM,   LPARAM)   >   (&memberFxn)   }, 

(2)   volatile   型变量:volatile   告诉编译器该变量可能被程序之外的未知方式修改(如系统、其他进程和线程)。优化程序为了使程序性能提高,常把一些变量放在寄存器中(类似于   register   关键字),而其他进程只能对该变量所在的内存进行修改,而寄存器中的值没变。如果你的程序是多线程的,或者你发现某个变量的值与预期的不符而你确信已正确的设置了,则很可能遇到这样的问题。这种错误有时会表现为程序在最快优化出错而最小优化正常。把你认为可疑的变量加上   volatile   试试。 

(3)   变量优化:优化程序会根据变量的使用情况优化变量。例如,函数中有一个未被使用的变量,在   Debug   版中它有可能掩盖一个数组越界,而在   Release   版中,这个变量很可能被优化调,此时数组越界会破坏栈中有用的数据。当然,实际的情况会比这复杂得多。与此有关的错误有: 
●   非法访问,包括数组越界、指针错误等。例如 
void   fn(void) 

int   i; 
i   =   1; 
int   a[4]; 

int   j; 
j   =   1; 

a[-1]   =   1;//当然错误不会这么明显,例如下标是变量 
a[4]   =   1; 

j   虽然在数组越界时已出了作用域,但其空间并未收回,因而   i   和   j   就会掩盖越界。而   Release   版由于   i、j   并未其很大作用可能会被优化掉,从而使栈被破坏。 

3.   _DEBUG   与   NDEBUG   :当定义了   _DEBUG   时,assert()   函数会被编译,而   NDEBUG   时不被编译。除此之外,VC++中还有一系列断言宏。这包括: 

ANSI   C   断言   void   assert(int   expression   ); 
C   Runtime   Lib   断言   _ASSERT(   booleanExpression   ); 
_ASSERTE(   booleanExpression   ); 
MFC   断言   ASSERT(   booleanExpression   ); 
VERIFY(   booleanExpression   ); 
ASSERT_VALID(   pObject   ); 
ASSERT_KINDOF(   classname,   pobject   ); 
ATL   断言   ATLASSERT(   booleanExpression   ); 
此外,TRACE()   宏的编译也受   _DEBUG   控制。 

所有这些断言都只在   Debug版中才被编译,而在   Release   版中被忽略。唯一的例外是   VERIFY()   。事实上,这些宏都是调用了   assert()   函数,只不过附加了一些与库有关的调试代码。如果你在这些宏中加入了任何程序代码,而不只是布尔表达式(例如赋值、能改变变量值的函数调用   等),那么   Release   版都不会执行这些操作,从而造成错误。初学者很容易犯这类错误,查找的方法也很简单,因为这些宏都已在上面列出,只要利用   VC++   的   Find   in   Files   功能在工程所有文件中找到用这些宏的地方再一一检查即可。另外,有些高手可能还会加入   #ifdef   _DEBUG   之类的条件编译,也要注意一下。 
顺便值得一提的是   VERIFY()   宏,这个宏允许你将程序代码放在布尔表达式里。这个宏通常用来检查   Windows   API   的返回值。有些人可能为这个原因而滥用   VERIFY()   ,事实上这是危险的,因为   VERIFY()   违反了断言的思想,不能使程序代码和调试代码完全分离,最终可能会带来很多麻烦。因此,专家们建议尽量少用这个宏。 

4.   /GZ   选项:这个选项会做以下这些事 

(1)   初始化内存和变量。包括用   0xCC   初始化所有自动变量,0xCD   (   Cleared   Data   )   初始化堆中分配的内存(即动态分配的内存,例如   new   ),0xDD   (   Dead   Data   )   填充已被释放的堆内存(例如   delete   ),0xFD(   deFencde   Data   )   初始化受保护的内存(debug   版在动态分配内存的前后加入保护内存以防止越界访问),其中括号中的词是微软建议的助记词。这样做的好处是这些值都很大,作为指针是不可能的(而且   32   位系统中指针很少是奇数值,在有些系统中奇数的指针会产生运行时错误),作为数值也很少遇到,而且这些值也很容易辨认,因此这很有利于在   Debug   版中发现   Release   版才会遇到的错误。要特别注意的是,很多人认为编译器会用   0   来初始化变量,这是错误的(而且这样很不利于查找错误)。 
(2)   通过函数指针调用函数时,会通过检查栈指针验证函数调用的匹配性。(防止原形不匹配) 
(3)   函数返回前检查栈指针,确认未被修改。(防止越界访问和原形不匹配,与第二项合在一起可大致模拟帧指针省略   FPO   ) 

通常   /GZ   选项会造成   Debug   版出错而   Release   版正常的现象,因为   Release   版中未初始化的变量是随机的,这有可能使指针指向一个有效地址而掩盖了非法访问。 

除此之外,/Gm   /GF   等选项造成错误的情况比较少,而且他们的效果显而易见,比较容易发现。 
-------------------------------------------------------------- 
Release是发行版本,比Debug版本有一些优化,文件比Debug文件小 
Debug是调试版本,包括的程序信息更多 
Release方法: 
build->batch   build->build就OK. 

----------------------------------------------------- 

一、"Debug是调试版本,包括的程序信息更多" 

补充:只有DEBUG版的程序才能设置断点、单步执行、使用TRACE/ASSERT等调试输出语句。REALEASE不包含任何调试信息,所以体积小、运行速度快。 

二、一般发布release的方法除了hzh_shat(水)   所说的之外,还可以project->Set   Active   Config,选中release版本。此后,按F5或F7编译所得的结果就是release版本。


Trackback: http://tb.donews.net/TrackBack.aspx?PostId=131016

----------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------
VC下关于debug和release的不同的讨论
在使用VC开发软件的过程中,正当要享受那种兴奋的时候突然发现:release与debug运行结果不一致,甚至出错,而release又不方便调试,真的是当头一棒啊,可是疼归疼,问题总要解决,下面将讲述一下我的几点经验,看看是不是其中之一:

1. 变量。
大家都知道,debug跟release在初始化变量时所做的操作是不同的,debug是将每个字节位都赋成0xcc(注1),而release的赋值近似于随机(我想是直接从内存中分配的,没有初始化过)。这样就明确了,如果你的程序中的某个变量没被初始化就被引用,就很有可能出现异常:用作控制变量将导致流程导向不一致;用作数组下标将会使程序崩溃;更加可能是造成其他变量的不准确而引起其他的错误。所以在声明变量后马上对其初始化一个默认的值是最简单有效的办法,否则项目大了你找都没地方找。代码存在错误在debug方式下可能会忽略而不被察觉到,如debug方式下数组越界也大多不会出错,在release中就暴露出来了,这个找起来就比较难了:( 还是自己多加注意吧

2. 自定义消息的消息参数。
MFC为我们提供了很好的消息机制,更增加了自定义消息,好处我就不用多说了。这也存在debug跟release的问题吗?答案是肯定的。在自定义消息的函数体声明时,时常会看到这样的写法:afx_msg LRESULT OnMessageOwn(); Debug情况下一般不会有任何问题,而当你在Release下且多线程或进程间使用了消息传递时就会导致无效句柄之类的错误。导致这个错误直接原因是消息体的参数没有添加,即应该写成:afx_msg LRESULT OnMessageOwn(WPARAM wparam, LPARAM lparam); (注2)

3. release模式下不出错,但debug模式下报错。
这种情况下大多也是因为代码书写不正确引起的,查看MFC的源码,可以发现好多ASSERT的语句(断言),这个宏只是在debug模式下才有效,那么就清楚了,release版不报错是忽略了错误而不是没有错误,这可能存在很大的隐患,因为是Debug模式下,比较方便调试,好好的检查自己的代码,再此就不多说了。

4. ASSERT, VERIFY, TRACE……….调试宏
这种情况很容易解释。举个例子:请在VC下输入ASSERT然后选中按F12跳到宏定义的地方,这里你就能够发现Debug中ASSERT要执行AfxAssertFailedLine,而Release下的宏定义却为”#define ASSERT(f) ((void)0)”。所以注意在这些调试宏的语句不要用程序相关变量如i++写操作的语句。VERIFY是个例外,”#define VERIFY(f) ((void)(f))”,即执行,这里的作用就不多追究了,有兴趣可自己研究:)。

总结:
Debug与Release不同的问题在刚开始编写代码时会经常发生,99%是因为你的代码书写错误而导致的,所以不要动不动就说系统问题或编译器问题,努力找找自己的原因才是根本。我从前就常常遇到这情况,经历过一次次的教训后我就开始注意了,现在我所写过的代码我已经好久没遇到这种问题了。下面是几个避免的方面,即使没有这种问题也应注意一下:

1. 注意变量的初始化,尤其是指针变量,数组变量的初始化(很大的情况下另作考虑了)。
2. 自定义消息及其他声明的标准写法
3. 使用调试宏时使用后最好注释掉
4. 尽量使用try - catch(…)
5. 尽量使用模块,不但表达清楚而且方便调试。
注1:
afc(afc) 网友提供:
debug版初始化成0xcc是因为0xcc在x86下是一条int 3单步中断指令,这样程序如果跑飞了遇到0xcc就会停下来,这和单片机编程时一般将没用的代码空间填入jmp 0000语句是一样地
注2:
不知大家有没有遇到过这种情况,具体原因我也不太清楚,是不是调用时按着默认的参数多分配了WPARAM+LPARAM的空间而破坏了应用程序的内存空间?还请高手来补充。
NightWolf 网友提供:我遇见过,好像是在函数调用的时候参数入栈的问题。因为MFC的消息使用宏写的,所以如果定义了OnMessage()的函数,编译能够通过,但是调用一次后,堆栈指针发生了偏移。然后就。。。
------------------------------------------------------------
_________________________________________________________
---------------------------------------------------------
DEBUG和RELEASE 版本差异及调试相关问题:

I.          内存分配问题

1.           变量未初始化。下面的程序在debug中运行的很好。

       thing * search(thing * something)
         BOOL found;
         for(int i = 0; i < whatever.GetSize(); i++)
           {
           if(whatever[i]->field == something->field)
              { /* found it */
               found = TRUE;
               break;
              } /* found it */
            }
     if(found)
              return whatever[i];
     else
              return NULL;
而在release中却不行,因为debug中会自动给变量初始化found=FALSE,而在release版中则不会。所以尽可能的给变量、类或结构初始化。

2.             数据溢出的问题 

         如:char buffer[10];
              int counter;

        lstrcpy(buffer, "abcdefghik");

在debug版中buffer的NULL覆盖了counter的高位,但是除非counter>16M,什么问题也没有。但是在release版中,counter可能被放在寄存器中,这样NULL就覆盖了buffer下面的空间,可能就是函数的返回地址,这将导致ACCESS ERROR。

3.          DEBUG版和RELEASE版的内存分配方式是不同的 。如果你在DEBUG版中申请    ele 为 6*sizeof(DWORD)=24bytes,实际上分配给你的是32bytes(debug版以32bytes为单位分配), 而在release版,分配给你的就是24bytes(release版以8bytes为单位),所以在debug版中如果你写ele[6],可能不会有什么问题,而在release版中,就有ACCESS VIOLATE。

II.       ASSERT和VERIFY

1.          ASSERT在Release版本中是不会被编译的。

ASSERT宏是这样定义的

         #ifdef _DEBUG
         #define ASSERT(x) if( (x) == 0) report_assert_failure()
         #else
         #define ASSERT(x)
         #endif
         实际上复杂一些,但无关紧要。假如你在这些语句中加了程序中必须要有的代码
比如

ASSERT(pNewObj = new CMyClass);

pNewObj->MyFunction();

这种时候Release版本中的pNewObj不会分配到空间

所以执行到下一个语句的时候程序会报该程序执行了非法操作的错误。这时可以用VERIFY :

         #ifdef _DEBUG
         #define VERIFY(x) if( (x) == 0) report_assert_failure()
     #else
         #define VERIFY(x) (x)
         #endif
这样的话,代码在release版中就可以执行了。

III.    参数问题:

自定义消息的处理函数,必须定义如下:

afx_msg LRESULT OnMyMessage(WPARAM, LPARAM);

返回值必须是HRESULT型,否则Debug会过,而Release出错

IV.   内存分配

保证数据创建和清除的统一性:如果一个DLL提供一个能够创建数据的函数,那么这个DLL同时应该提供一个函数销毁这些数据。数据的创建和清除应该在同一个层次上。

V.      DLL的灾难

人们将不同版本DLL混合造成的不一致性形象的称为 “动态连接库的地狱“(DLL Hell) ,甚至微软自己也这么说(http://msdn.microsoft.com/library/techart/dlldanger1.htm)。

        如果你的程序使用你自己的DLL时请注意:

1.        不能将debug和release版的DLL混合在一起使用。debug都是debug版,release版都是release版。

解决办法是将debug和release的程序分别放在主程序的debug和release目录下


2.          千万不要以为静态连接库会解决问题,那只会使情况更糟糕。

VI.   RELEASE板中的调试 :

1.          将ASSERT() 改为 VERIFY() 。找出定义在"#ifdef _DEBUG"中的代码,如果在RELEASE版本中需要这些代码请将他们移到定义外。查找TRACE(...)中代码,因为这些代码在RELEASE中也不被编译。 请认真检查那些在RELEASE中需要的代码是否并没有被便宜。

2.          变量的初始化所带来的不同,在不同的系统,或是在DEBUG/RELEASE版本间都存在这样的差异,所以请对变量进行初始化。

3.          是否在编译时已经有了警告?请将警告级别设置为3或4,然后保证在编译时没有警告出现.

VII.    将Project Settings" 中 "C++/C " 项目下优化选项改为Disbale(Debug)。编译器的优化可能导致许多意想不到的错误,请参考http://www.pgh.net/~newcomer/debug_release.htm

1.          此外对RELEASE版本的软件也可以进行调试,请做如下改动:

在"Project Settings" 中 "C++/C " 项目下设置 "category" 为 "General" 并且将"Debug Info"设置为 "Program Database"。

在"Link"项目下选中"Generate Debug Info"检查框。

"Rebuild All"

如此做法会产生的一些限制:

无法获得在MFC DLL中的变量的值。

必须对该软件所使用的所有DLL工程都进行改动。

另:

MS BUG:MS的一份技术文档中表明,在VC5中对于DLL的"Maximize Speed"优化选项并未被完全支持,因此这将会引起内存错误并导致程序崩溃。

2.         www.sysinternals.com有一个程序DebugView,用来捕捉OutputDebugString的输出,运行起来后(估计是自设为system debugger)就可以观看所有程序的OutputDebugString的输出。此后,你可以脱离VC来运行你的程序并观看调试信息。

3.          有一个叫Gimpel Lint的静态代码检查工具,据说比较好用。

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/Jiao2_vc/archive/2009/03/07/3967364.aspx

posted @ 2011-04-29 10:48 沛沛 阅读(338) | 评论 (0)编辑 收藏


    _stdcall将参数压栈是按C语言的顺序(从右到左),但与C 语言不同的是它是由被调用者将参数从栈中清除的,所以它的编译文件比_cdecl小。_stdcall是Windows API函数中默认的调用约定,VB、VFP等也采用这个约定。 
    _cdecl是C语言采用的默认调用方法,它的优点是支持printf这样的可变参数调用。 
    另外,VC++对于两种调用方法的名称转换方法也不同。

 在C语言中,假设我们有这样的一个函数:
  
  int function(int a,int b)
  
  调用时只要用result = function(1,2)这样的方式就可以使用这个函数。但是,当高级语言被编译成计算机可以识别的机器码时,有一个问题就凸现出来:在CPU中,计算机没有办法知道一个函数调用需要多少个、什么样的参数,也没有硬件可以保存这些参数。也就是说,计算机不知道怎么给这个函数传递参数,传递参数的工作必须由函数调用者和函数本身来协调。为此,计算机提供了一种被称为栈的数据结构来支持参数传递。

  栈是一种先进后出的数据结构,栈有一个存储区、一个栈顶指针。栈顶指针指向堆栈中第一个可用的数据项(被称为栈顶)。用户可以在栈顶上方向栈中加入数据,这个操作被称为压栈(Push),压栈以后,栈顶自动变成新加入数据项的位置,栈顶指针也随之修改。用户也可以从堆栈中取走栈顶,称为弹出栈(pop),弹出栈后,栈顶下的一个元素变成栈顶,栈顶指针随之修改。

  函数调用时,调用者依次把参数压栈,然后调用函数,函数被调用以后,在堆栈中取得数据,并进行计算。函数计算结束以后,或者调用者、或者函数本身修改堆栈,使堆栈恢复原装。

  在参数传递中,有两个很重要的问题必须得到明确说明:
  
  当参数个数多于一个时,按照什么顺序把参数压入堆栈 
  函数调用后,由谁来把堆栈恢复原装 
  在高级语言中,通过函数调用约定来说明这两个问题。常见的调用约定有:

  stdcall 
  cdecl 
  fastcall 
  thiscall 
  naked call

  stdcall调用约定
  stdcall很多时候被称为pascal调用约定,因为pascal是早期很常见的一种教学用计算机程序设计语言,其语法严谨,使用的函数调用约定就是stdcall。在Microsoft C++系列的C/C++编译器中,常常用PASCAL宏来声明这个调用约定,类似的宏还有WINAPI和CALLBACK。

  stdcall调用约定声明的语法为(以前文的那个函数为例):
  
  int __stdcall function(int a,int b)
  
  stdcall的调用约定意味着:1)参数从右向左压入堆栈,2)函数自身修改堆栈 3)函数名自动加前导的下划线,后面紧跟一个@符号,其后紧跟着参数的尺寸

  以上述这个函数为例,参数b首先被压栈,然后是参数a,函数调用function(1,2)调用处翻译成汇编语言将变成:

  push 2        第二个参数入栈
  push 1        第一个参数入栈
  call function    调用参数,注意此时自动把cs:eip入栈

  而对于函数自身,则可以翻译为: 
  push ebp       保存ebp寄存器,该寄存器将用来保存堆栈的栈顶指针,可以在函数退出时恢复
  mov ebp, esp    保存堆栈指针
  mov eax,[ebp + 8H] 堆栈中ebp指向位置之前依次保存有ebp, cs:eip, a, b, ebp +8指向a
  add eax,[ebp + 0CH] 堆栈中ebp + 12处保存了b
  mov esp, ebp    恢复esp
  pop ebp
  ret 8

  而在编译时,这个函数的名字被翻译成_function@8

  注意不同编译器会插入自己的汇编代码以提供编译的通用性,但是大体代码如此。其中在函数开始处保留esp到ebp中,在函数结束恢复是编译器常用的方法。

  从函数调用看,2和1依次被push进堆栈,而在函数中又通过相对于ebp(即刚进函数时的堆栈指针)的偏移量存取参数。函数结束后,ret 8表示清理8个字节的堆栈,函数自己恢复了堆栈。

  
  cdecl调用约定
  cdecl调用约定又称为C调用约定,是C语言缺省的调用约定,它的定义语法是:

  int function (int a ,int b) //不加修饰就是C调用约定
  int __cdecl function(int a,int b)//明确指出C调用约定

  在写本文时,出乎我的意料,发现cdecl调用约定的参数压栈顺序是和stdcall是一样的,参数首先由右向左压入堆栈。所不同的是,函数本身不清理堆栈,调用者负责清理堆栈。由于这种变化,C调用约定允许函数的参数的个数是不固定的,这也是C语言的一大特色。对于前面的function函数,使用cdecl后的汇编码变成:

  调用处
  push 1
  push 2
  call function
  add esp, 8     注意:这里调用者在恢复堆栈

  被调用函数_function处
  push ebp       保存ebp寄存器,该寄存器将用来保存堆栈的栈顶指针,可以在函数退出时恢复
  mov ebp,esp     保存堆栈指针
  mov eax,[ebp + 8H] 堆栈中ebp指向位置之前依次保存有ebp,cs:eip,a,b,ebp +8指向a
  add eax,[ebp + 0CH] 堆栈中ebp + 12处保存了b
  mov esp,ebp     恢复esp
  pop ebp
  ret         注意,这里没有修改堆栈

  MSDN中说,该修饰自动在函数名前加前导的下划线,因此函数名在符号表中被记录为_function,但是我在编译时似乎没有看到这种变化。

  由于参数按照从右向左顺序压栈,因此最开始的参数在最接近栈顶的位置,因此当采用不定个数参数时,第一个参数在栈中的位置肯定能知道,只要不定的参数个数能够根据第一个后者后续的明确的参数确定下来,就可以使用不定参数,例如对于CRT中的sprintf函数,定义为: 
  int sprintf(char* buffer,const char* format,...)
  由于所有的不定参数都可以通过format确定,因此使用不定个数的参数是没有问题的。

  fastcall
  fastcall调用约定和stdcall类似,它意味着: 
  
  函数的第一个和第二个DWORD参数(或者尺寸更小的)通过ecx和edx传递,其他参数通过从右向左的顺序压栈 
  被调用函数清理堆栈 
  函数名修改规则同stdcall 
  其声明语法为:int fastcall function(int a, int b)

  thiscall
  thiscall是唯一一个不能明确指明的函数修饰,因为thiscall不是关键字。它是C++类成员函数缺省的调用约定。由于成员函数调用还有一个this指针,因此必须特殊处理,thiscall意味着:

  参数从右向左入栈 
  如果参数个数确定,this指针通过ecx传递给被调用者;如果参数个数不确定,this指针在所有参数压栈后被压入堆栈。对参数个数不定的,调用者清理堆栈,否则函数自己清理堆栈为了说明这个调用约定,定义如下类和使用代码:

  class A
  {
  public:
    int function1(int a,int b);
    int function2(int a,...);
  };

  int A::function1 (int a,int b)
  {
    return a+b;
  }

  #include <stdarg.h>
  int A::function2(int a,...)
  {
    va_list ap;
    va_start(ap,a);
    int i;
    int result = 0;
    for(i = 0 ; i < a ; i ++)
    {
     result += va_arg(ap,int);
    }
    return result;
  }

  void callee()
  {
    A a;
    a.function1(1, 2);
    a.function2(3, 1, 2, 3);
  }

callee函数被翻译成汇编后就变成: 
  //函数function1调用
  00401C1D  push    2
  00401C1F  push    1
  00401C21  lea     ecx,[ebp-8]
  00401C24  call    function1     注意,这里this没有被入栈

  //函数function2调用
  00401C29  push    3
  00401C2B  push    2
  00401C2D  push    1
  00401C2F  push    3
  00401C31  lea     eax, [ebp-8]    这里引入this指针
  00401C34  push    eax
  00401C35  call    function2
  00401C3A  add     esp, 14h
  
  可见,对于参数个数固定情况下,它类似于stdcall,不定时则类似cdecl

  naked call
  这是一个很少见的调用约定,一般程序设计者建议不要使用。编译器不会给这种函数增加初始化和清理代码,更特殊的是,你不能用return返回返回值,只能用插入汇编返回结果。这一般用于实模式驱动程序设计,假设定义一个求和的加法程序,可以定义为:

  __declspec(naked) int add(int a,int b)
  {
    __asm mov eax,a
    __asm add eax,b
    __asm ret 
  }

  注意,这个函数没有显式的return返回值,返回通过修改eax寄存器实现,而且连退出函数的ret指令都必须显式插入。上面代码被翻译成汇编以后变成:

  mov eax,[ebp+8]
  add eax,[ebp+12]
  ret 8

  注意这个修饰是和__stdcall及cdecl结合使用的,前面是它和cdecl结合使用的代码,对于和stdcall结合的代码,则变成:

  __declspec(naked) int __stdcall function(int a,int b)
  {
    __asm mov eax,a
    __asm add eax,b
    __asm ret 8    //注意后面的8
  }

  至于这种函数被调用,则和普通的cdecl及stdcall调用函数一致。

  函数调用约定导致的常见问题
  如果定义的约定和使用的约定不一致,则将导致堆栈被破坏,导致严重问题,下面是两种常见的问题:

  函数原型声明和函数体定义不一致 
  DLL导入函数时声明了不同的函数约定 
  以后者为例,假设我们在dll种声明了一种函数为:

  __declspec(dllexport) int func(int a,int b);//注意,这里没有stdcall,使用的是cdecl
  使用时代码为:

  typedef int (*WINAPI DLLFUNC)func(int a,int b);
  hLib = LoadLibrary(...);

  DLLFUNC func = (DLLFUNC)GetProcAddress(...)//这里修改了调用约定
  result = func(1,2);//导致错误

  由于调用者没有理解WINAPI的含义错误的增加了这个修饰,上述代码必然导致堆栈被破坏,MFC在编译时插入的checkesp函数将告诉你,堆栈被破坏

1)调用约定(Calling convention)

    决定函数参数传送时入栈和出栈的顺序,由调用者还是被调用者把参数弹出栈,以及编译器用来识别函数名字的修饰约定。函数调用约定有多种:

1、__stdcall调用约定

    相当于16位动态库中经常使用的 PASCAL 调用约定。在32位的 VC++5.0 中PASCAL 调用约定不再被支持(实际上它已被定义为__stdcall。函数的参数自右向左通过栈传递,被调用的函数在返回前清理传送参数的内存栈。

    _stdcall 是 Pascal 程序的缺省调用方式,通常用于 Win32 API 中,函数采用从右到左的压栈方式,自己在退出时清空堆栈。VC 将函数编译后会在函数名前面加上下划线前缀,在函数名后加上 "@" 和参数的字节数。

2、C 调用约定(即用__cdecl)

    按从右至左的顺序压参数入栈,由调用者把参数弹出栈。对于传送参数的内存栈是由调用者来维护的(正因为如此,实现可变参数的函数只能使用该调用约定)。

    _cdecl 是 C 和 C++ 程序缺省的调用方式。每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用 _stdcall 函数的大。函数采用从右到左的压栈方式。VC 将函数编译后会在函数名前面加上下划线前缀。 它是 MFC 缺省调用约定。

3、__fastcall 调用约定

    "人" 如其名,它的主要特点就是快,因为它是通过寄存器来传送参数的(实际上,它用 ECX 和 EDX 传送前两个双字(DWORD)或更小的参数,剩下的参数仍旧自右向左压栈传送,被调用的函数在返回前清理传送参数的内存栈)。

    _fastcall方式的函数采用寄存器传递参数,VC 将函数编译后会在函数名前面加上"@"前缀,在函数名后加上"@"和参数的字节数。

4、thiscall调用约定

    仅仅应用于 "C++" 成员函数。this 指针存放于 CX 寄存器,参数从右到左压。thiscall 不是关键词,因此不能被程序员指定。

5、naked call调用约定

    采用 1-4 的调用约定时,如果必要的话,进入函数时编译器会产生代码来保存ESI,EDI,EBX,EBP寄存器,退出函数时则产生代码恢复这些寄存器的内容。

    naked call不产生这样的代码。naked call不是类型修饰符,故必须和_declspec 共同使用。

    关键字 __stdcall、__cdecl 和 __fastcall 可以直接加在要输出的函数前,也可以在编译环境的 Setting...\C/C++ \Code Generation 项选择。当加在输出函数前的关键字与编译环境中的选择不同时,直接加在输出函数前的关键字有效。它们对应的命令行参数分别为/Gz、/Gd 和 /Gr。缺省状态为/Gd,即__cdecl。


2)名字修饰约定

1、修饰名(Decoration name)

    "C" 或者 "C++" 函数在内部(编译和链接)通过修饰名识别。修饰名是编译器在编译函数定义或者原型时生成的字符串。有些情况下使用函数的修饰名是必要的,如在模块定义文件里头指定输出"C++"重载函数、构造函数、析构函数,又如在汇编代码里调用"C""或"C++"函数等。修饰名由函数名、类名、调用约定、返回类型、参数等共同决定。

2、函数名修饰约定随编译种类(C或C++)和调用约定的不同而不同,下面分别说明。

a、C编译时函数名修饰约定规则:

__stdcall调用约定:

    在输出函数名前加上一个下划线前缀,后面加上一个"@"符号和其参数的字节数,格式为 _functionname@number

__cdecl调用约定:

    仅在输出函数名前加上一个下划线前缀,格式为 _functionname。

__fastcall调用约定:

    在输出函数名前加上一个"@"符号,后面也是一个"@"符号和其参数的字节数,格式为@functionname@number。

它们均不改变输出函数名中的字符大小写,这和PASCAL调用约定不同,PASCAL约定输出的函数名无任何修饰且全部大写。

b、C++编译时函数名修饰约定规则:

__stdcall调用约定:

以"?"标识函数名的开始,后跟函数名;
函数名后面以"@@YG"标识参数表的开始,后跟参数表;
参数表以代号表示:
    X——void,

    D——char,

    E——unsigned char,

    F——short,

    H——int,

    I——unsigned int,

    J——long,

    K——unsigned long,

    M——float,

    N——double,

    _N——bool,

    ....

    PA——表示指针,后面的代号表明指针类型,如果相同类型的指针连    续出现,以"0"代替,一个"0"代表一次重复;


参数表的第一项为该函数的返回值类型,其后依次为参数的数据类型,指针标识在其所指数据类型前;
参数表后以"@Z"标识整个名字的结束,如果该函数无参数,则以"Z"标识结束。其格式为
    "?functionname@@YG*****@Z"或

    "?functionname@@YG*XZ",

    例如

    int Test1(char *var1,unsigned long)    -----“?Test1@@YGHPADK@Z
    void Test2()                    -----“?Test2@@YGXXZ

__cdecl调用约定:

    规则同上面的_stdcall调用约定,只是参数表的开始标识由上面的"@@YG"变为"@@YA"。VC++对函数的省缺声明是"__cedcl",将只能被C/C++调用。

__fastcall调用约定:

    规则同上面的_stdcall调用约定,只是参数表的开始标识由上面的"@@YG"变为"@@YI"。


文章出处:http://www.diybl.com/course/3_program/c++/cppjs/2008617/126024.html

posted @ 2011-04-29 10:45 沛沛 阅读(323) | 评论 (0)编辑 收藏

仅列出标题
共7页: 1 2 3 4 5 6 7