Ordinary, But Passional

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
  27 随笔 :: 0 文章 :: 24 评论 :: 26 引用

2008年8月16日 #

我要在MSDN里搜Windows API DrawText函数,如图:

Google: 第一条就是。

Live Search:  找了半天没找到

posted @ 2008-08-16 12:22 彭小虎(Tigerkin) 阅读(89) | 评论 (1)编辑

以往写Windows程序,用的较多的是Delphi的VCL,MFC用的很少,总觉得不习惯,相比MFC我倒宁愿用清新简单的Windows API。呵呵。于是乎,我萌生了一个想法,自己来封装Windows API。开始动手。。

首先我找了一个比较简单的Window API程序,试着把他转换成面向对象的形式。程序尽管简单,但刚上来一个棘手的问题就出现了。。消息机制的封装。

我们都知道,Windows中比如点击按钮,移动窗口等等的交互操作都是由消息机制来完成的。每做一个动作,例如点击一个按钮,Windows便会产生一个相应的消息,在这里就是BN_CLICKED,假设点击按钮后会弹出一个窗口,里面显示若干文字,而这些点击按钮后产生的效果就需要由我们程序员来编写。体现在Windows API中便是“消息处理函数”,如下:

 

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
     
switch (message)
     {
     
case BN_CLICKED:

          
//相关代码
          return 0 ;

     
case WM_DESTROY:
          PostQuitMessage (
0) ;
          
return 0 ;
     }
     
return DefWindowProc (hwnd, message, wParam, lParam) ;
}

 

每次都要写这么多case的确是挺麻烦的,于是我想用面向对象的方法解决他。

 

class MessageMap
{
public:
     
int count;  // 消息映射的数目
      Message* message[10];  // 可以存放10对消息映射
      template <typename T>
     
void Add(MessageType 消息名称, T* 对应的处理函数);  // 增加一对消息映射
};

template 
<typename T>
class Message
{
public:
     MessageType 消息名称;
     T
* 对应的处理函数;
     
void Run()
     {
          对应的处理函数();
     }
};

enum MessageType{}

 

这样做其实是有问题的,请看这一段:

 

class MessageMap
{
public:
     
      Message
* message[10];  // 由于Message是模板类,这里应该提供模板参数。这样就失去了模板的作用。
                                
// 我们使用模板就是为了能让他存储不同的函数指针。
      
};

 

想一想,如何用一个统一的接口来调用不同的函数?对了,那就是多态。

我们可以增加一个抽象的接口类来提供调用接口,而具体实现的类则由他派生。

 

class MessageInterface
{
public:
    MessageType type;
    
virtual ~MessageInterface() {}
    
virtual void Run() = 0;
}
;

template 
<typename T>
class Message : public MessageInterface
{
public:
    T
* classPtr;
    
void (T::* funcPtr)();
    Message(MessageType _type, T
* _classPtr, void (T::* _funcPtr)()) : classPtr(_classPtr), funcPtr(_funcPtr)
    
{
        type 
= _type;
    }

    
virtual void Run()
    
{
        
if (classPtr)
        
{
            (classPtr
->*funcPtr)();        // 注意调用函数指针时括号的用法
        }

    }
    
}
;

class MessageMap
{
public:
    
int count;
    MessageMap()
    
{
        count 
= 0;
        Add(DESTROY, 
this&MessageMap::OnDestroy); 
    }

    MessageInterface
* message[10];
    
void OnDestroy()
    
{
        PostQuitMessage(
0);
    }

    template 
<typename T>
    
void Add(MessageType type, T* classPtr, void (T::* funcPtr)())
    
{
        message[count
++= new Message<T>(type, classPtr, funcPtr);
    }

}
;

 

问题解决了!哈哈,测试成功!

具体的代码在这里,结构什么的还很不完善,仅仅是处理了消息机制而已,当作演示吧。

 

posted @ 2008-08-16 11:23 彭小虎(Tigerkin) 阅读(231) | 评论 (1)编辑

2008年8月7日 #

今天接到leader布置的一个任务,从TextBox继承一个新的控件并为其增加一些功能,其中一个功能如下:

Add a property: FormatString, when the text box lost focus, the content will be replace with string.Format(FormatString, actualContent),我在做第二个:“当textbox失去焦点时自动格式化文本”的地方遇到了问题。。。

简单起见,简化成“当textbox失去焦点时改变文本内容”。

一种方法是:

重写OnLeave()方法,如下:

protected override void OnLeave(EventArgs e)
{
        
base.OnLeave(e);
        
this.Text = "Override OnLeave";
}

另一种是给Leave事件增加一个订阅者:

private void NewReceiver(object sender, EventArgs e)
{
        
this.text = "NewReceiver";
        Invalidate();
}

//constructor
public MyTextBox()
{
        
this.Leave += new EventHandler(NewReceiver);
}

经测试,均可运行。

现在,把两段代码和在一起,并注释掉第一种方法:

protected override void OnLeave(EventArgs e)
{
    
base.OnLeave(e);
    
//this.Text = "Override OnLeave";
}

private void NewReceiver(object sender, EventArgs e)
{
    
this.text = "NewReceiver";
    Invalidate();
}

//constuctor
public MyTextBox()
{
    
this.Leave += new EventHandler(NewReceiver);
}

运行程序,测试,显示NewReceiver。

接下来再把base.OnLeave(e)注释掉:

protected override void OnLeave(EventArgs e)
{
    
//base.OnLeave(e);
    
//this.Text = "Override OnLeave";
}

private void NewReceiver(object sender, EventArgs e)
{
    
this.text = "NewReceiver";
    Invalidate();
}

//constuctor
public MyTextBox()
{
    
this.Leave += new EventHandler(NewReceiver);
}

运行,发现文本框失去焦点后不会改变内容,仍未空。由此,第二种方法失效了。为何?我们来看一下base.OnLeave()的内容:

protected virtual void OnLeave(EventArgs e)
{
    EventHandler handler 
= (EventHandler) base.Events[EventLeave];
    
if (handler != null)
    {
        handler(
this, e);
    }
}

原来,注释掉这段以后,handler(this, e)无法得到执行,也就没法激发事件,文本框内容当然也就没法改变了。

 

由此,我猜测,C#的事件触发过程大致是这样的:

文本框失去焦点 --> 触发Leave事件 --> 调用OnLeave()函数(这里是因为什么机制调用的?) --> 调用base.OnLeave() --> 再次触发Leave事件 --> 调用NewReceiver() --> 返回,执行this.text = "Override OnLeave"。

红色字,大家谁知道的,帮我解个惑吧~~

posted @ 2008-08-07 19:22 彭小虎(Tigerkin) 阅读(621) | 评论 (8)编辑

2008年8月2日 #

很多东西,不是你不会做,而是你不敢去做。久而久之,庸庸碌碌。。

要这么想:有什么东西是天生就会的呢!

posted @ 2008-08-02 17:35 彭小虎(Tigerkin) 阅读(57) | 评论 (1)编辑

2008年7月30日 #

一,公钥私钥
1,公钥和私钥成对出现
2,公开的密钥叫公钥,只有自己知道的叫私钥
3,用公钥加密的数据只有对应的私钥可以解密
4,用私钥加密的数据只有对应的公钥可以解密
5,如果可以用公钥解密,则必然是对应的私钥加的密
6,如果可以用私钥解密,则必然是对应的公钥加的密
明白了?

假设一下,我找了两个数字,一个是1,一个是2。我喜欢2这个数字,就保留起来,不告诉你们,然后我告诉大家,1是我的公钥。

我有一个文件,不能让别人看,我就用1加密了。别人找到了这个文件,但是他不知道2就是解密的私钥啊,所以他解不开,只有我可以用数字2,就是我的私钥,来解密。这样我就可以保护数据了。

我的好朋友x用我的公钥1加密了字符a,加密后成了b,放在网上。别人偷到了这个文件,但是别人解不开,因为别人不知道2就是我的私钥,只有我才能解密,解密后就得到a。这样,我们就可以传送加密的数据了。

现在我们知道用公钥加密,然后用私钥来解密,就可以解决安全传输的问题了。如果我用私钥加密一段数据(当然只有我可以用私钥加密,因为只有我知道2是我的私钥),结果所有的人都看到我的内容了,因为他们都知道我的公钥是1,那么这种加密有什么用处呢?

但是我的好朋友x说有人冒充我给他发信。怎么办呢?我把我要发的信,内容是c,用我的私钥2,加密,加密后的内容是d,发给x,再告诉他解密看是不是c。他用我的公钥1解密,发现果然是c。这个时候,他会想到,能够用我的公钥解密的数据,必然是用我的私钥加的密。只有我知道我得私钥,因此他就可以确认确实是我发的东西。这样我们就能确认发送方身份了。这个过程叫做数字签名。当然具体的过程要稍微复杂一些。用私钥来加密数据,用途就是数字签名。

好,我们复习一下:
1,公钥私钥成对出现
2,私钥只有我知道
3,大家可以用我的公钥给我发加密的信了
4,大家用我的公钥解密信的内容,看看能不能解开,能解开,说明是经过我的私钥加密了,就可以确认确实是我发的了。

总结一下结论:
1,用公钥加密数据,用私钥来解密数据
2,用私钥加密数据(数字签名),用公钥来验证数字签名。

在实际的使用中,公钥不会单独出现,总是以数字证书的方式出现,这样是为了公钥的安全性和有效性。

二,SSL
我和我得好朋友x,要进行安全的通信。这种通信可以是QQ聊天,很频繁的。用我的公钥加密数据就不行了,因为:
1,我的好朋友x没有公私钥对,我怎么给他发加密的消息啊? (注:实际情况中,可以双方都有公私钥对)
2,用公私钥加密运算很费时间,很慢,影响QQ效果。

好了,好朋友x,找了一个数字3,用我的公钥1,加密后发给我,说,我们以后就用这个数字来加密信息吧。我解开后,得到了数字3。这样,只有我们两个人知道这个秘密的数字3,别的人都不知道,因为他们既不知x挑了一个什么数字,加密后的内容他们也无法解开,我们把这个秘密的数字叫做会话密钥。

然后,我们选择一种对称密钥算法,比如DES,(对称算法是说,加密过程和解密过程是对称的,用一个密钥加密,可以用同一个密钥解密。使用公私钥的算法是非对称加密算法),来加密我们之间的通信内容。别人因为不知道3是我们的会话密钥,因而无法解密。

好,复习一下:
1,SSL实现安全的通信
2,通信双方使用一方或者双方的公钥来传递和约定会话密钥 (这个过程叫做握手)
3,双方使用会话密钥,来加密双方的通信内容

上面说的是原理。大家可能觉得比较复杂了,实际使用中,比这还要复杂。不过庆幸的是,好心的先行者们在操作系统或者相关的软件中实现了这层(Layer),并且起了一个难听的名字叫做SSL,(Secure Socket Layer)。

posted @ 2008-07-30 11:16 彭小虎(Tigerkin) 阅读(104) | 评论 (0)编辑

2008年3月16日 #

 

class String
{
public:
    String( 
const char *str = "" );
    
~String();
    String( 
const String &another )
    
{
        
const char *str = another.m_data;  // 可以正常访问
        /*省略*/
private:
    
char *m_data;
}
;

c++里,类的访问权限是class level,不是object level的。
访问权限只在编译时对编译器有效,在运行时,不存在访问权限这道栅栏。  

以下转自网上:

/**  
    *   请大家看看下面这段程序,它是能够正常编译连接运行的。但是为什么  
    *   assign成员函数没有错呢?谁能告诉我c++中关于这种情况的内幕?谢谢!  
    */  
   
  #include   <stdio.h>  
   
  class   Test    
  {  
  private:  
          int   a;  
  public:  
          Test(int);  
          void   assign(Test   const*   source);  
  };    
   
  void   Test::assign(Test   *source)  
  {  
          a   =   source->a;  
  }  
   
  int   main()  
  {  
          Test   t1(1),   t2(2);  
          t2.assign(&t1);  
  }  
   
  1:   关于ACCESS-CONTROL的定义  
   
      <<C++98   std/chapter   11>>:  
      1   A   member   of   a   class   can   be  
   
      --private;   that   is,   its   name   can   be   used     only     by      
          member     functions,   static     data     members,     and      
          friends     of     the     class     in   which   it   is   declared.  
   
      --protected;   that   is,   its   name   can   be   used   only   by  
          member     functions,   static     data     members,     and    
          friends     of     the     class     in   which   it   is   declared    
          and   by   member   functions,   static   data   members,      
          and     friends   of   classes   derived   from   this   class.  
   
      --public;     that     is,     its     name     can     be     used    
          anywhere   without   access   restriction.  
   
  2:   一些相关的note:  
        1)   name  
        ACCESS-CONTROL   只应用到name(simple   identifier).  
        按照上面的定义,ACCESS-CONTROL针对且只针对    
        member   of   class.        
        即使是涉及到模板,也只是关心name,    
        而不关心template-argument  
         
        2)   unnamed   object  
        无名对象不考虑access-control:  
        如下:  
        struct   A  
        {  
        private:  
              struct   B    
              {  
                  int   i;  
              };  
              B   m_b;  
        public:  
              B&   f()   {   return   m_b;   }            
        };  
        int   main()  
        {  
            A   a;  
            f()->i;   //   only   names   are   f,   i,    
                            //   f   and   i   is   public,    
                            //   so   it's   legal   even    
                            //   if   B   in   private   A   member  
        }        
        3)   关于protected   member   name有一个例外情况,  
              参见(4:)中例子的new   note  
   
  3:   让人迷惑的代码段的注记  
   
        void   Test::assign(Test   *source)  
        {  
          a   =   source->a;  
          /**  
            1)   这里出现的name是  
                  void,   Test,   assign,   source,   a  
            2)   void,   Test,   source,    
                不是class   member,    
                不做access-control考虑  
            3)   assign是成员函数名,    
                  但我们只是定义它,  
                  不在这里使用,  
                  也不做access-control考虑  
   
            4)a是private   Test   member,    
                  可以在A的成员函数中使用,  
                  现在刚好是在Test的  
                  assign成员函数体内。  
                   
            5)   but   what   about   "source->a":  
                  OK,   a   in   "source->a"   means    
                  a   is   a   member   name   of   source's   class   Test  
                  and   used   in   Test   member   function,    
                  everything   is   fine!!!  
                  (still   confusing,   see   following   example   ...)  
            */  
        }  
   
  4:   更加清楚的例子(我的)注记  
       
        struct   Base  
        {  
        protected:  
            int   m_protected;  
        };  
   
        struct   Son2   :   Base   {};  
   
        struct   Son1   :   Base  
        {  
   
            void   foo(Son1&   s)  
            {  
                s.m_protected;           /**  
                                                  new   note:  
                                                  same   as   3/5)  
                                                  */  
            }  
   
            void   foo(Base&   b)  
            {  
                b.m_protected;    /**  
                                          new   note:  
                                          m_protected   in   "b.m_protected"   means:  
                                              m_protected   is   Base(b's   type)   class    
                                              member,   not   Son1   class   member  
                                          which   means:  
                                              Base's   protected   member   is   used  
                                              in   Son1's   member   function    
                                          By   definition,   it's   legal,   but  
                                          因为C++标准规定一个关于  
                                          proected   member的例外情况,  
                                              When   a   member   function   of   a      
                                              derived     class   references      
                                              a     protected   nonstatic   member  
                                              of   a   base   class,   an   access   check    
                                              applies   in   addition     to     those    
                                              described   earlier   in   this   clause:  
                                                Except   when   forming   a   pointer   to  
                                                member,the   access   must   be   through    
                                                          a   pointer   to,  
                                                          reference   to,      
                                                          <---我们的情况  
                                                          or   object              
                                                          <---下一个成员函数的情况  
                                                      of   the   derived   class   itself    
                                              (or   any   class   derived   from   that   class).  
                                          因为儿子不能管老子,所以结论是  
                                          illegal    
                                          */  
   
            }  
            void   foo(Base   b)  
            {  
                b.m_protected;  /**  
                                        new   note:  
                                        illegal   :   儿子不能管老子  
                                        原因同   void   foo(Base&   c)的分析。  
                                        */  
            }    
      };  

posted @ 2008-03-16 19:29 彭小虎(Tigerkin) 阅读(49) | 评论 (1)编辑

2008年3月8日 #

很有用的,所以把它记下来

1 CString,int,string,char*之间的转换  
string 转 CString  
CString.format("%s", string.c_str());  

char 转 CString  
CString.format("%s", char*);  

char 转 string  
string s(char *);  

string 转 char *  
char *p = string.c_str();  

 //  CString转std::string
 CString str = dlg.GetPathName();
 setlocale(LC_ALL, "chs");
 char *p = new char[256];
 wcstombs( p, str, 256 );
 m_fileName = p;

1,string -> CString  
CString.format("%s", string.c_str());  
用c_str()确实比data()要好.  
2,char -> string  
string s(char *);  
你的只能初始化,在不是初始化的地方最好还是用assign().  
3,CString -> string  
string s(CString.GetBuffer());  
GetBuffer()后一定要ReleaseBuffer(),否则就没有释放缓冲区所占的空间.  


《C++标准函数库》中说的  
有三个函数可以将字符串的内容转换为字符数组和C—string  
1.data(),返回没有”\0“的字符串数组  
2,c_str(),返回有”\0“的字符串数组  
3,copy()  


CString互转int  

将字符转换为整数,可以使用atoi、_atoi64或atol。  
而将数字转换为CString变量,可以使用CString的Format函数。如  
CString s;  
int i = 64;  
s.Format("%d", i)  
Format函数的功能很强,值得你研究一下。  

void CStrDlg::OnButton1()  
{  
// TODO: Add your control notification handler code here  
CString  
ss="1212.12";  
int temp=atoi(ss);  
CString aa;  
aa.Format("%d",temp);  
AfxMessageBox("var is " + aa);  
}  

sart.Format("%s",buf);  

CString互转char*  

///char * TO cstring  
CString strtest;  
char * charpoint;  
charpoint="give string a value";  
strtest=charpoint;  


///cstring TO char *  
charpoint=strtest.GetBuffer(strtest.GetLength());  

标准C里没有string,char *==char []==string  

可以用CString.Format("%s",char *)这个方法来将char *转成CString。要把CString转成char *,用操作符(LPCSTR)CString就可以了。  


CString转换 char[100]  

char a[100];  
CString str("aaaaaa");  
strncpy(a,(LPCTSTR)str,sizeof(a));  
2 CString类型的转换成int  
CString类型的转换成int  
将字符转换为整数,可以使用atoi、_atoi64或atol。  

//CString aaa = "16" ;
//int int_chage = atoi((lpcstr)aaa) ;  


而将数字转换为CString变量,可以使用CString的Format函数。如  
CString s;  
int i = 64;  
s.Format("%d", i)  
Format函数的功能很强,值得你研究一下。  
如果是使用char数组,也可以使用sprintf函数。

//CString ss="1212.12";  
//int temp=atoi(ss);  
//CString aa;  
//aa.Format("%d",temp);  


数字->字符串除了用CString::Format,还有FormatV、sprintf和不需要借助于Afx的itoa  

3 char* 在装int  
#include <stdlib.h>
  
int atoi(const char *nptr);
long atol(const char *nptr);
long long atoll(const char *nptr);
long long atoq(const char *nptr);  

4 CString,int,string,char*之间的转换  
string aa("aaa");
char *c=aa.c_str();


cannot convert from 'const char *' to 'char *'
const char *c=aa.c_str();  

5 CString,int,string,char*之间的转换  
string.c_str()只能转换成const char *,
要转成char *这样写:

string mngName;
char t[200]; memset(t,0,200); strcpy(t,mngName.c_str());  

posted @ 2008-03-08 23:50 彭小虎(Tigerkin) 阅读(1270) | 评论 (1)编辑

2008年3月4日 #

 

typedef unsigned short WORD;
typedef unsigned 
long DWORD;
typedef unsigned 
char BYTE;
typedef 
long   LONG;

struct BITMAPFILEHEADER
{
 WORD bfType;
 DWORD bfSize;
 WORD bfReserved1;
 WORD bfReserved2;
 DWORD bfOffBits;
}
;

sizeof(WORD)==2
sizeof(DWORD)==4
但sizeof(BITMAPFILEHEADER)==16 
不等于2+4+2+2+4=14
为什么?这里存在一个字节对齐问题
以下为转帖:

一.什么是字节对齐,为什么要对齐?

    现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特 定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
    对齐的作用和原因:各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问 一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐.其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对 数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那 么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数 据。显然在读取效率上下降很多。

二.字节对齐对程序的影响:

    先让我们看几个例子吧(32bit,x86环境,gcc编译器):
设结构体如下定义:
struct A
{
    int a;
    char b;
    short c;
};
struct B
{
    char b;
    int a;
    short c;
};
现在已知32位机器上各种数据类型的长度如下:
char:1(有符号无符号同)    
short:2(有符号无符号同)    
int:4(有符号无符号同)    
long:4(有符号无符号同)    
float:4    double:8
那么上面两个结构大小如何呢?
结果是:
sizeof(strcut A)值为8
sizeof(struct B)的值却是12

结构体A中包含了4字节长度的int一个,1字节长度的char一个和2字节长度的short型数据一个,B也一样;按理说A,B大小应该都是7字节。
之所以出现上面的结果是因为编译器要对数据成员在空间上进行对齐。上面是按照编译器的默认设置进行对齐的结果,那么我们是不是可以改变编译器的这种默认对齐设置呢,当然可以.例如:
#pragma pack (2) /*指定按2字节对齐*/
struct C
{
    char b;
    int a;
    short c;
};
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
sizeof(struct C)值是8。
修改对齐值为1:
#pragma pack (1) /*指定按1字节对齐*/
struct D
{
    char b;
    int a;
    short c;
};
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
sizeof(struct D)值为7。
后面我们再讲解#pragma pack()的作用.

三.编译器是按照什么样的原则进行对齐的?

    先让我们看四个重要的基本概念:
1.数据类型自身的对齐值:
  对于char型数据,其自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,单位字节。
2.结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值。
3.指定对齐值:#pragma pack (value)时的指定对齐值value。
4.数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中小的那个值。
有 了这些值,我们就可以很方便的来讨论具体数据结构的成员和其自身的对齐方式。有效对齐值N是最终用来决定数据存放地址方式的值,最重要。有效对齐N,就是 表示“对齐在N上”,也就是说该数据的"存放起始地址%N=0".而数据结构中的数据变量都是按定义的先后顺序来排放的。第一个数据变量的起始地址就是数 据结构的起始地址。结构体的成员变量要对齐排放,结构体本身也要根据自身的有效对齐值圆整(就是结构体成员变量占用总长度需要是对结构体有效对齐值的整数 倍,结合下面例子理解)。这样就不能理解上面的几个例子的值了。
例子分析:
分析例子B;
struct B
{
    char b;
    int a;
    short c;
};
假 设B从地址空间0x0000开始排放。该例子中没有定义指定对齐值,在笔者环境下,该值默认为4。第一个成员变量b的自身对齐值是1,比指定或者默认指定 对齐值4小,所以其有效对齐值为1,所以其存放地址0x0000符合0x0000%1=0.第二个成员变量a,其自身对齐值为4,所以有效对齐值也为4, 所以只能存放在起始地址为0x0004到0x0007这四个连续的字节空间中,复核0x0004%4=0,且紧靠第一个变量。第三个变量c,自身对齐值为 2,所以有效对齐值也是2,可以存放在0x0008到0x0009这两个字节空间中,符合0x0008%2=0。所以从0x0000到0x0009存放的 都是B内容。再看数据结构B的自身对齐值为其变量中最大对齐值(这里是b)所以就是4,所以结构体的有效对齐值也是4。根据结构体圆整的要求, 0x0009到0x0000=10字节,(10+2)%4=0。所以0x0000A到0x000B也为结构体B所占用。故B从0x0000到0x000B 共有12个字节,sizeof(struct B)=12;其实如果就这一个就来说它已将满足字节对齐了, 因为它的起始地址是0,因此肯定是对齐的,之所以在后面补充2个字节,是因为编译器为了实现结构数组的存取效率,试想如果我们定义了一个结构B的数组,那 么第一个结构起始地址是0没有问题,但是第二个结构呢?按照数组的定义,数组中所有元素都是紧挨着的,如果我们不把结构的大小补充为4的整数倍,那么下一 个结构的起始地址将是0x0000A,这显然不能满足结构的地址对齐了,因此我们要把结构补充成有效对齐大小的整数倍.其实诸如:对于char型数据,其 自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,这些已有类型的自身对齐值也是基于数组考虑的,只 是因为这些类型的长度已知了,所以他们的自身对齐值也就已知了.
同理,分析上面例子C:
#pragma pack (2) /*指定按2字节对齐*/
struct C
{
    char b;
    int a;
    short c;
};
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
第 一个变量b的自身对齐值为1,指定对齐值为2,所以,其有效对齐值为1,假设C从0x0000开始,那么b存放在0x0000,符合0x0000%1= 0;第二个变量,自身对齐值为4,指定对齐值为2,所以有效对齐值为2,所以顺序存放在0x0002、0x0003、0x0004、0x0005四个连续 字节中,符合0x0002%2=0。第三个变量c的自身对齐值为2,所以有效对齐值为2,顺序存放
在0x0006、0x0007中,符合 0x0006%2=0。所以从0x0000到0x00007共八字节存放的是C的变量。又C的自身对齐值为4,所以C的有效对齐值为2。又8%2=0,C 只占用0x0000到0x0007的八个字节。所以sizeof(struct C)=8.

四.如何修改编译器的默认对齐值?

1.在VC IDE中,可以这样修改:[Project]|[Settings],c/c++选项卡Category的Code Generation选项的Struct Member Alignment中修改,默认是8字节。
2.在编码时,可以这样动态修改:#pragma pack .注意:是pragma而不是progma.

五.针对字节对齐,我们在编程中如何考虑?


    如果在编程的时候要考虑节约空间的话,那么我们只需要假定结构的首地址是0,然后各个变量按照上面的原则进行排列即可,基本的原则就是把结构中的变量按照 类型大小从小到大声明,尽量减少中间的填补空间.还有一种就是为了以空间换取时间的效率,我们显示的进行填补空间进行对齐,比如:有一种使用空间换时间做 法是显式的插入reserved成员:
         struct A{
           char a;
           char reserved[3];//使用空间换时间
           int b;
}

reserved成员对我们的程序没有什么意义,它只是起到填补空间以达到字节对齐的目的,当然即使不加这个成员通常编译器也会给我们自动填补对齐,我们自己加上它只是起到显式的提醒作用.

六.字节对齐可能带来的隐患:

    代码中关于对齐的隐患,很多是隐式的。比如在强制类型转换的时候。例如:
unsigned int i = 0x12345678;
unsigned char *p=NULL;
unsigned short *p1=NULL;

p=&i;
*p=0x00;
p1=(unsigned short *)(p+1);
*p1=0x0000;
最后两句代码,从奇数边界去访问unsignedshort型变量,显然不符合对齐的规定。
在x86上,类似的操作只会影响效率,但是在MIPS或者sparc上,可能就是一个error,因为它们要求必须字节对齐.

七.如何查找与字节对齐方面的问题:

如果出现对齐或者赋值问题首先查看
1. 编译器的big little端设置
2. 看这种体系本身是否支持非对齐访问
3. 如果支持看设置了对齐与否,如果没有则看访问时需要加某些特殊的修饰来标志其特殊访问操作。

八.其他

当数据定义中出现__declspec( align() )时,指定类型的对齐长度还要用自身长度和这里指定的数值比较,然后取其中较大的。最终类/结构的对齐长度也需要和这个数值比较,然后取其中较大的。

可以这样理解, __declspec( align() ) 和 #pragma pack是一对兄弟,前者规定了对齐的最小值,后者规定了对齐的最大值,两者同时出现时,前者拥有更高的优先级。
__declspec( align() )的一个特点是,它仅仅规定了数据对齐的位置,而没有规定数据实际占用的内存长度,当指定的数据被放置在确定的位置之后,其后的数据填充仍然是按照#pragma pack规定的方式填充的,这时候类/结构的实际大小和内存格局的规则是这样的:
在__declspec( align() )之前,数据按照#pragma pack规定的方式填充,如前所述。当遇到__declspec( align() )的时候,首先寻找距离当前偏移向后最近的对齐点(满足对齐长度为max(数据自身长度,指定值) ),然后把被指定的数据类型从这个点开始填充,其后的数据类型从它的后面开始,仍然按照#pragma pack填充,直到遇到下一个__declspec( align() )。
当所有数据填充完毕,把结构的整体对齐数值和__declspec( align() )规定的值做比较,取其中较大的作为整个结构的对齐长度。
特别的,当__declspec( align() )指定的数值比对应类型长度小的时候,这个指定不起作用。

posted @ 2008-03-04 19:49 彭小虎(Tigerkin) 阅读(149) | 评论 (0)编辑

2008年2月25日 #

《交互式计算机图形学--基于OpenGL的自顶向下方法》中第2章中的Sierpinski镂垫程序,编译时出现错误:
error C2381: “exit”: 重定义;__declspec(noreturn) 不同 c:\program files\microsoft visual studio 8\vc\include\stdlib.h 406

不是copy的代码么,会有错?我只是加了个#include <stdlib.h>,难道这有问题?
于是,把#include <GL/glut.h>
           #include <stdlib.h>

换下位置#include <stdlib.h>
           #include <GL/glut.h> 
居然OK了,难道是RP

posted @ 2008-02-25 19:42 彭小虎(Tigerkin) 阅读(52) | 评论 (0)编辑

2008年2月1日 #

这篇文章一年多前看过了,今天再拿出来读读发现确有新的体会。发表到blog上,作为收藏。
//=============================================================

由C#风潮想起的-给初学编程者的忠告
我始终认为,对一个初学者来说,IT界的技术风潮是不可以追赶的,而且也没有能力去追赶。我时常看见自己的DDMM们把课本扔了,去卖些价格不菲的诸如C#, VB.Net 这样的大部头,这让我感到非常痛心。而许多搞不清指针是咋回事的BBS站友眉飞色舞的讨论C#里面可以不用指针等等则让我觉得好笑。C#就象当年的ASP一样,“忽如一夜春风来,千树万树梨花开”,结果许多学校的信息学院成了“Web 学院”。96,97级的不少大学生都去做Web 了。当然我没有任何歧视某一行业的意识。我只是觉得如果他们把追赶这些时髦技术的时间多花一点在基础的课程上应该是可以走得更远的。

【几个误区】初学者对C#风潮的追赶其实也只是学习过程中经常遇到的几个误区之一。我将用一些实际的例子来说明这些现象,你可以按部就班的看看自己是不是属于其中的一种或者几种:
〖认为计算机技术等于编程技术: 〗
有些人即使没有这个想法,在潜意识中也有这样的冲动。让我奇怪的是,许多信息学院的学生也有这样的念头。认为计算机专业就是编程专业,与编程无关的,或者不太相关的课程他统统都不管,极端的学生只要书上没带“编程”两个字他就不看。
其实编程只是计算机技术应用过程中一种复杂性最低的劳动,这就是为什么IT业最底层的人是程序员(CODER)。计算机技术包括了多媒体,计算机网络,人工智能,模式识别,管理信息系统等等这些方面。编程工作只是在这些具体技术在理论研究或者工程实践的过程中表达算法的过程。编程的人不一定对计算机技术的了解就一定很高。而一个有趣的现象是,不少大师级的计算机技术研究者是不懂编程的。网上的炒作和现实中良好的工作待遇把编程这种劳动神秘化了。其实每一个程序员心里都明白,自己这些东西,学的时候并不比其它专业难,所以自然也不会高档到哪里去。 "

〖咬文嚼字的孔已己作风: 〗
我见过一本女生的《 计算机网络原理 》教材,这个女生象小学生一样在书上划满了横杠杠,笔记做得满满的,打印出来一定比教材还厚。我不明白的是,象计算机网络原理这样的课程有必要做笔记?我们的应试教育的确害了不少学生,在上《原理》这一类课程的时候许多学生象学《马列原理》一样逐字背诵记忆。这乃是我见过的最愚蠢的行为。所谓《原理》,即是需要掌握它为什么这样做,学习why,而不是how(怎样做)。极端认真的学生背下以太网的网线最大长度,数据帧的长度,每个字段的意义,IP报头的格式等等,但是忘了路由的原则,忘了TCP/IP协议设计的宗旨。总之许多人花了大量的时间把书背得滚瓜烂熟却等于什么也没学。
在学习编程的时候这些学生也是这样,他们确切的记得C++语法的各个细节。看完了C++教程后看《 C++ 编程思想 第1卷: 标准C++导引 》(确实是好书),《 深度探索C++对象模型 》,《 C++参考大全(第四版) 》,this C++, that C++……,然后是网上各种各样的关于C++语法的奇闻逸事,然后发现自己又忘了C++的一些语法,最后回头继续恶补…。有个师弟就跟我说:“C++ 太难了,学了这里忘了那里,学了继承忘了模板。”我的回答道:“你不去学就容易了”。我并没有教坏他,只是告诉他,死抠C++的语法就和孔已己炫耀茴香豆的茴字有几种写法一样毫无意义。你根本不需要对的C++语法太关心,动手编程就是了,有不记得的地方一查MSDN就立马搞定。我有个结论就是,实际的开发过程中对程序语法的了解是最微不足道的知识。这是为什么我在为同学用Basic(我以前从没有学过它)写一个小程序的时候,只花了半个小时看了看语法,然后再用半个小时完成了程序,而一个小时后我又完全忘记了Basic 的所有关键字。

〖不顾基础,盲目追赶时髦技术:〗
终于点到题目上来了。大多数的人都希望自己的东西能够马上跑起来,变成钱。这种想法对一个已经进入职业领域的程序员或者项目经理来说是合理的,而且IT技术进步是如此的快,不跟进就是失业。但是对于初学者来说(尤其是时间充裕的大中专在校生),这种想法是另人费解的。一个并未进入到行业竞争中来的初学者最大的资本便是他有足够的时间沉下心来学习基础性的东西,学习why 而不是how。时髦的技术往往容易掌握,而且越来越容易掌握,这是商业利益的驱使,为了最大化的降低软件开发的成本。但在IT领域内的现实就是这样,越容易掌握的东西,学习的人越多,而且淘汰得越快。每一次新的技术出来,都有许多初学者跟进,这些初学者由于缺乏必要的基础而使得自己在跟进的过程中花费大量的时间,而等他学会了,这种技术也快淘汰了。基础的课程,比方数据结构,操作系统原理等等虽然不能让你立马就实现一个linux(这是许多人嘲笑理论课程无用的原因),但它们能够显著的减少你在学习新技术时学习曲线的坡度。而且对于许多关键的技术(比方Win32 SDK 程序的设计,DDK的编程)来说甚至是不可或缺的。
一个活生生的例子是我和我的一个同学,在大一时我还找不到开机按纽,他已经会写些简单的汇编程序了。我把大二的所有时间花在了汇编,计算机体系结构,数据结构,操作系统原理等等这些课程的学习上,而他则开始学习HTML和VB,并追赶ASP的潮流。大三的时候我开始学习Windows 操作系统原理,学习SDK编程,时间是漫长的,这时我才能够用VC开发出象模象样的应用程序。我曾一度因为同学的程序已经能够运行而自己还在学习如何创建对话框而懊恼不已,但临到毕业才发现自己的选择是何等的正确。和我谈判的公司开出的薪水是他的两倍还多。下面有一个不很恰当的比方:假设学习VB编程需要4个月,学习基础课程和VC的程序设计需要1年。那么如果你先学VB,再来学习后者,时间不会减少,还是1年,而反过来,如果先学习后者,再来学VB,也许你只需要1个星期就能学得非常熟练。
如果你是学生,或者如果你有充足的时间。我建议你仔细的掌握下面的知识。我的建议是针对那些希望在IT技术上有所成就的初学者。同时我还列出了一些书目,这些书应该都还可以在书店买到。说实在的,我在读其他人的文章时最大的心愿就是希望作者列出一个书单。
[大学英语]-不要觉得好笑。我极力推荐这门课程是因为没有专业文档的阅读能力是不可想象的。中文的翻译往往在猴年马月才会出来,而现在的许多出版社干脆就直接把E文印刷上去。学习的方法是强迫自己看原版的教材,开始会看不懂,用多了自然熟练。吃得苦下得狠心绝对是任何行业都需要的品质。
[计算机体系结构和汇编语言]-关于体系结构的书遍地都是,而且也大同小异,倒是汇编有一本非常好的书。《 80X86汇编语言程序设计教程 》(清华大学出版社,黑色封面,杨季文著)。你需要着重学习386后保护模式的程序设计。否则你在学习现代操作系统底层的一些东西的时候会觉得是在看天书。
[计算机操作系统原理]-我们的开发总是在特定的操作系统上进行,如果不是,只有一种可能:你在自己实现一个操作系统。无论如何,操作系统原理是必读的。这就象我们为一个芯片制作外围设备时,芯片基本的工作时序是必需了解的。这一类书也很多,我没有发现哪一本书非常出众。只是觉得在看完了这些书后如果有空就应该看看《 Windows 2000内部揭密 》(微软出版社,我看的是E文版的,中文的书名想必是Windows 2000 技术内幕之类吧)。关于学习它的必要性,ZDNET上的另一篇文章已经有过论述。
[数据结构和算法]-这门课程能够决定一个人程序设计水平的高低,是一门核心课程。我首选的是清华版的(朱战立,刘天时)。很多人喜欢买C++版的,但我觉得没有必要。C++的语法让算法实现过程变得复杂多了,而且许多老师喜欢用模块这一东西让算法变得更复杂。倒是在学完了C版的书以后再来浏览一下C++的版的书是最好的。
[软件工程]-这门课程是越到后来就越发现它的重要,虽然刚开始看时就象看马哲一样不知所云。我的建议是看《 实用软件工程(第二版) 》(黄色,清华)。不要花太多的时间去记条条框框,看不懂就跳过去。在每次自己完成了一个软件设计任务(不管是练习还是工作)以后再来回顾回顾,每次都会有收获。
[ Windows 程序设计(第5版)(上、下册) ]-《北京大学出版社,Petzold著》我建议任何企图设计Windows 程序的人在学习VC以前仔细的学完它。而且前面的那本《 Windows 2000内部揭密 》也最好放到这本书的后面读。在这本书中,没有C++,没有GUI,没有控件。有的就是如何用原始的C语言来完成Windows 程序设计。在学完了它以后,你才会发现VC其实是很容易学的。千万不要在没有看完这本书以前提前学习VC,你最好碰都不要碰。我知道的许多名校甚至都已经用它作为教材进行授课。可见其重要。

上面的几门课程我认为是必学的重要课程(如果你想做Windows 程序员)。
对于其它的课程有这样简单的选择方法:如果你是计算机系的,请学好你所有的专业基础课。如果不是,请参照计算机系的课程表。如果你发现自己看一本书时无法看下去了,请翻到书的最后,看看它的参考文献,找到它们并学习它们,再回头看这本书。如果一本书的书名中带有“原理”两个字,你一定不要去记忆它其中的细节,你应该以一天至少50页的速度掌握其要领。尽可能多的在计算机上实践一种理论或者算法。
你还可以在CSDN上阅读到许多书评。这些书评能够帮助你决定读什么样的书。

〖日三省乎己〗
每天读的书太多,容易让人迷失方向。一定要在每天晚上想想自己学了些什么,还有些什么相关的东西需要掌握,自己对什么最感兴趣,在一本书上花的时间太长还是不够等等。同时也应该多想想未来最有可能出现的应用,这样能够让你不是追赶技术潮流而是引领技术潮流。同时,努力使用现在已经掌握的技术和理论去制作具有一定新意的东西。坚持这样做能够让你真正成为一个软件“研发者”而不仅仅是一个CODER。

〖把最多的时间花在学习上〗 这是对初学者最后的忠告。把每个星期玩SC或者CS的时间压缩到最少,不玩它们是最好的。同时,如果你的ASP技术已经能够来钱,甚至有公司请你兼职的话,这就证明你的天份能够保证你在努力的学习之后取得更好的收益,你应该去做更复杂的东西。眼光放长远一些,这无论是对谁都是适用的。

相信你已经能够决定是否学习C#或者什么时候去学它了。

posted @ 2008-02-01 14:33 彭小虎(Tigerkin) 阅读(51) | 评论 (0)编辑