跳到主要内容

49 篇博文 含有标签「码农札记」

查看所有标签

整合 wiki, blog 和 forum

· 阅读需 4 分钟

    现在,wiki、blog 和论坛都已经成为我工作中不可缺少的工具了。他们三个各有优缺点:
    wiki 上的文章,你即便不是作者也可以进行修改。并且 wiki 将把所有的修改历史都记录下来,用户可以方便地查看每个文档的历史版本。因此 wiki 比较适合一个组织发表规范、制度一类的文档。wiki 最大的缺点是无法针对某一篇文章发表评论。如果强行把自己的评论加载原文下方,则会破坏原文版本更新的记录,这不是个好方法。
    blog 解决了发表评论的问题,但同时丧失了 wiki 的优点。一个 blog 站点通常只是一个人发布文章,作者可以修改自己已发布的文章,但历史记录不能保存下来。所以 blog 适合个人使用,用于发表看法和收集反馈。当然也可用于为某个项目收集反馈。
    论坛和 blog 的区别完全在于分类方法不同,其他差别则不大。blog 是按作者分类的,论坛则是按照文章内容分类的。当你对某一个话题比较感兴趣,想查看其他人对它的看法,论坛的形式就比较合适。因此,很多论坛已经和 blog 结合在一起了。网站的数据只有一份,你想按照作者分类查看文章,那么它看上去像个 blog,如果按照文章内容分类,则看上去就像是论坛。
 
    我想肯定已经有人想过,但现在还没有见到这样的成熟产品:把这三样都整合到一起。这种服务网站应当是这样的:
    进入网站,读者可以自行选择是按照文章内容查看,还是按照组织、个人作者查看。其中组织是由若干个成员组成的:发表在组织入口下的文章,每个组织成员都可以对其进行修改。而读者不能对非自己组织发表的文章进行修改。
    读者可以对网站的所有文章加评论。但除评论发布者,其他人不能对评论进行修改。
    网站的所有文章的修改记录都应被保存下来。读者可以查看文章的旧版本内容。

查看或添加留言

使用 Google 桌面的一点感想

· 阅读需 3 分钟

    我们公司有很多软件和文件在公司的几台文件服务器上。经常有人需要安装一个软件时,不知道上哪去找。如果要根据文件内容找到一个以前放置在服务器上的文件就更困难了。

    我们公司的 Email 系统使用的是 Lotus Notes,它的数据过搜索功能非常差,比如一些我们常用的数据库,只允许搜索每个项目标题中出现的文字。如果只知道一个项目大致内容,不知道标题,就没办法把它搜索出来。

   使用 Google Desktop 企业版基本上可以解决我们所遇到的问题了。 Google Desktop 可以为所有本地盘符下的文件和指定的网路路经下的文件建立索引,使用户快速找到需要的文件。可以解析文件内容。可以检索 Lotus Notes 邮件,数据库。
    但是它也有一些缺点,比如:每个用户都需要自己安装一份,并设置需要抓取的网络路经。索引文件是比较大的,一般有几个G,需要占用每个用户的硬盘。当很多用户设置抓取网络路经后,会大大增加服务器和网络的负担。不能检索服务器上的没有在Windows下共享的 Notes 数据库。

   我觉得一个可行的改进措施是公司IT部门利用 Google Desktop 提供的 API 做一些针对性地开发。比如在在某一台服务器上设置搜索引擎,负责抓取我们本地所有文件服务器上的文件,和公共的 Lotus Notes 数据库。员工可以利用浏览器访问这台服务器查询搜索结果。

查看或添加留言

免费的 Windows XP 兼容操作系统

· 阅读需 3 分钟

    React是指 "反抗、抗拒" 的意思。这个 ReactOS 操作系统就是为了反抗 Windows 而诞生的。

    ReactOS 项目的宗旨就是为所有人提供一款免费的、开源的并且在执行代码级别与 Windows XP 完全兼容的操作系统。这意味着,任何一款运行在 Windows XP 上的软件,不需要任何改动就可以直接运行于 ReactOS。下图就是在 ReactOS 上安装 Visual Studio 2005 时的屏幕截图。

    ReactOS 不仅为穷人带来了福音,对于希望深入学习 Windows 操作系统的人更是一个惊喜。由于 Windows 的源代码是保密的,我们很难了解其内幕。而与 Windows XP 完全兼容的 ReactOS 的源代码却是公开的。通过他的代码,我们就可以清晰地了解 Windows 工作原理。

    ReactOS 目前的最新版本是0.2.9。它的安装包、VMware镜像、源代码均可从 ReactOS 的主页下载,地址是:http://www.reactos.org

ReactOS 系统截屏

<table cellspacing="0" cellpadding="10" width="100%" border="0"><tbody><tr valign="top" align="middle"><td width="33%"><a href="http://www.reactos.org/media/screenshots/2005/ros026_openoffice114_01.jpg"><img src="images/ros026_openoffice114_01_mini.jpg" border="0"></a><br><font size="2">OpenOffice 1.1.4</font></td><td width="34%"><a href="http://www.reactos.org/media/screenshots/2005/ros03_abiword_01.jpg"><img src="images/ros03_abiword_01_mini.jpg" border="0"></a><br><font size="2">Abi Word</font></td><td><p align="center"><a href="http://www.reactos.org/media/screenshots/2005/ros03_msoffice2k3_01.jpg"><img src="images/ros03_msoffice2k3_01_mini.jpg" border="0"></a>&nbsp;<br><font size="2">MS Office 2003 Setup</font></p></td></tr><tr valign="top"><td align="left"><p align="center"><font size="2">&nbsp;<a href="http://www.reactos.org/media/screenshots/2005/app_ibrowser_01.jpg"><img src="images/app_ibrowser_01_mini.jpg" border="0"></a><br><font size="2">ReactOS Internet Browser</font></font></p></td><td><p align="center">&nbsp;&nbsp;<a href="http://www.reactos.org/media/screenshots/2005/ros029_mozfirefox_01.png"><img src="images/ros029_mozfirefox_01_mini.jpg" border="0"></a><br><font size="2">Mozilla Firefox</font></p></td><td valign="center" align="middle"><p align="center"><a href="http://www.reactos.org/media/screenshots/2005/seamonkey.png"><img src="images/seamonkey_mini.png" border="0"></a><br><font size="2">Mozilla SeaMonkey</font></p></td></tr><tr valign="top"><td align="left"><p align="center">&nbsp;<a href="http://www.reactos.org/media/screenshots/2005/app_kmeleon_01.jpg"><img src="images/app_kmeleon_01_mini.jpg" border="0"></a><br><font size="2">K-Meleon</font></p></td><td><p align="center">&nbsp;&nbsp;<a href="http://www.reactos.org/media/screenshots/2005/app_flash.jpg"><img src="images/app_flash_mini.jpg" border="0"></a><br><font size="2">Flash Player</font></p></td><td valign="center" align="middle"><p align="center"><a href="http://www.reactos.org/media/screenshots/2005/app_mirc_01.jpg"><img src="images/app_mirc_01_mini.jpg" border="0"></a><br><font size="2">mIRC</font></p></td></tr><tr valign="top" align="middle"><td width="33%"><font size="2"><a href="http://www.reactos.org/media/screenshots/2005/ros026_ut1_01.jpg"><img src="images/ros026_ut1_01_mini.jpg" border="0"></a><br><font size="2">Unreal Tournament</font></font></td><td width="34%"><font size="2"><a href="http://www.reactos.org/media/screenshots/2005/ros026_deusex_01.jpg"><img src="images/ros026_deusex_01_mini.jpg" border="0"></a><br><font size="2">Deus Ex</font></font></td><td width="33%"><a href="http://www.reactos.org/media/screenshots/2005/ros027_openttd_01.jpg"><img src="images/ros027_openttd_01_mini.jpg" border="0"></a><br><font size="2">OpenTTD</font></td></tr><tr valign="top"><td align="left"><div align="center"><font size="2"><a href="http://www.reactos.org/media/screenshots/2006/ros_029_photoshop3.png"><img src="images/ros_029_photoshop3_mini.jpg" border="0"></a><br>Adobe Photoshop 3</font></div></td><td><div align="center"><font size="2"><a href="http://www.reactos.org/media/screenshots/2006/ros_029_psp4.png"><img src="images/ros_029_psp4_mini.jpg" border="0"></a><br><font size="2">Paint Shop Pro 4</font></font></div></td><td valign="center" align="middle"><font size="2"><a href="http://www.reactos.org/media/screenshots/2006/ros_029_pp95.png"><img src="images/ros_029_pp95_mini.jpg" border="0"></a><br>MS PowerPoint 7</font></td></tr><tr valign="top"><td align="left"><div align="center"><font size="2"><a href="http://www.reactos.org/media/screenshots/2006/ros_029_ibrowse.jpg"><img src="images/ros_029_ibrowse_mini.jpg" border="0"></a><br><font size="2">ReactOS Internet Browser</font></font></div></td><td><div align="center"><font size="2"><a href="http://www.reactos.org/media/screenshots/2005/app_solitaire_01.png"><img src="images/app_solitaire_01_mini.png" border="0"></a><br><font size="2">Solitaire</font></font></div></td><td valign="center" align="middle"><font size="2"><a href="http://www.reactos.org/media/screenshots/2005/app_photofiltre_01.png"><img src="images/app_photofiltre_01_mini.png" border="0"></a><br>PhotoFiltre</font></td></tr><tr valign="top"><td align="left"><div align="center"><font size="2"><a href="http://www.reactos.org/media/screenshots/2005/ros_029_processexplorer_01.png"><img src="images/ros_029_processexplorer_01_mini.png" border="0"></a><br>Process Explorer</font></div></td><td><div align="center"><font size="2"><a href="http://www.reactos.org/media/screenshots/2005/app_vb_01.png"><img src="images/app_vb_01_mini.png" border="0"></a><br>MS Visual Basic 6</font></div></td><td valign="center" align="middle"><font size="2"><a href="http://www.reactos.org/media/screenshots/2005/app_vs200529oe_01.jpg"><img src="images/app_vs200529oe_01_mini.jpg" border="0"></a><br><font size="2">MS Visual Studio 2005 Setup</font></font></td></tr><tr valign="top"><td align="left"><p align="center">&nbsp;<a href="http://www.reactos.org/media/screenshots/2005/app_putty_01.jpg"><img src="images/app_putty_01_mini.jpg" border="0"></a><br><font size="2">PuTTY</font></p></td><td><p align="center">&nbsp;<a href="http://www.reactos.org/media/screenshots/2005/ros_in_qemu.png"><img src="images/ros_in_qemu_mini.png" border="0"></a><br><font size="2">ReactOS in QEmu in ReactOS</font></p></td><td valign="center" align="middle"><p align="center">&nbsp;<a href="http://www.reactos.org/media/screenshots/2005/ros026_xemacs_01.jpg"><img src="images/ros026_xemacs_01_mini.jpg" border="0"></a><br><font size="2">XEmacs</font></p></td></tr><tr valign="top"><td valign="center" align="middle"><a href="http://www.reactos.org/media/screenshots/2005/ros029_dosbox_01.jpg"><img src="images/ros029_dosbox_01_mini.jpg" border="0"></a><br><font size="2">DOSBox Shell</font></td><td valign="center" align="middle"><a href="http://www.reactos.org/media/screenshots/2005/ros029_dosbox_doom_01.jpg"><img src="images/ros029_dosbox_doom_01_mini.jpg" border="0"></a><br><font size="2">DOSBox - DOOM</font></td><td valign="center" align="middle"><font size="2"><a href="http://www.reactos.org/media/screenshots/2005/ros_029_scummvm-dott-r19963.jpg"><img src="images/ros_029_scummvm-dott-r19963_mini.jpg" border="0"></a><br>Day of the Tentacle</font></td></tr><tr valign="top"><td valign="center" align="middle"><p align="center">&nbsp;<a href="http://www.reactos.org/media/screenshots/2005/app_7zip.jpg"><img src="images/app_7zip_mini.jpg" border="0"></a><br><font size="2">7-zip</font></p></td><td valign="center" align="middle"><p align="center">&nbsp;<a href="http://www.reactos.org/media/screenshots/2005/app_winrar.jpg"><img src="images/app_winrar_mini.jpg" border="0"></a><br><font size="2">WinRAR</font></p></td><td valign="center" align="middle"><p align="center">&nbsp;<a href="http://www.reactos.org/media/screenshots/2005/mshelpimg.png"><img src="images/mshelpimg_mini.png" border="0"></a><br><font size="2">Microsoft Help Image Editor</font></p></td></tr><tr valign="top"><td valign="center" align="middle"><p align="center">&nbsp;<a href="http://www.reactos.org/media/screenshots/2005/app_irfanview_01.jpg"><img src="images/app_irfanview_01_mini.jpg" border="0"></a><br><font size="2">IrfanView</font></p></td><td valign="center" align="middle"><p align="center">&nbsp;<a href="http://www.reactos.org/media/screenshots/2005/mcafee_stinger.png"><img src="images/mcafee_stinger_mini.png" border="0"></a><br><font size="2">McAfee Stinger tool</font></p></td><td valign="center" align="middle"><p align="center">&nbsp;<a href="http://www.reactos.org/media/screenshots/2005/ros026_tuxpaint_01.jpg"><img src="images/ros026_tuxpaint_01_mini.jpg" border="0"></a><br><font size="2">TuxPaint</font></p></td></tr><tr valign="top"><td valign="center" align="middle"><p align="center"><a href="http://www.reactos.org/media/screenshots/2005/ros_029_fraps.jpg"><img src="images/ros_029_fraps_mini.jpg" border="0"></a><br><font size="2">Fraps</font></p></td><td valign="center" align="middle"><p align="center">&nbsp;</p></td><td valign="center" align="middle"><p align="center">&nbsp;</p></td></tr><tr valign="top"><td align="left"><p align="center">&nbsp;<a href="http://www.reactos.org/media/screenshots/2005/ros03_reactos_german_01.jpg"><img src="images/ros03_reactos_german_01_mini.jpg" border="0"></a><br><font size="2">ReactOS in german language</font></p></td><td><p align="center">&nbsp;<a href="http://www.reactos.org/media/screenshots/2005/reactos_spanish_01.jpg"><img src="images/reactos_spanish_01_mini.jpg" border="0"></a><br><font size="2">ReactOS in spanish language</font></p></td><td><p align="center">&nbsp;<a href="http://www.reactos.org/media/screenshots/2005/reactos_swedish_01.jpg"><img src="images/reactos_swedish_01_mini.jpg" border="0"></a><br><font size="2">ReactOS in swedish language</font></p></td></tr></tbody></table>
查看或添加留言

VC中的字节对齐

· 阅读需 26 分钟

 
    当在C中定义了一个结构类型时,它的大小是否等于各字段(field)大小之和?编译器将如何在内存中放置这些字段?ANSI C对结构体的内存布局有什么要求?而我们的程序又能否依赖这种布局?这些问题或许对不少朋友来说还有点模糊,那么本文就试着探究它们背后的秘密。

    首先,至少有一点可以肯定,那就是ANSI C保证结构体中各字段在内存中出现的位置是随它们的声明顺序依次递增的,并且第一个字段的首地址等于整个结构体实例的首地址。比如有这样一个结构体:

  struct vector{int x,y,z;} s;  
  int \*p,\*q,\*r;
  struct vector \*ps;
 
  p = &s.x;
  q = &s.y;
  r = &s.z;
  ps = &s;

  assert(p < q);
  assert(p < r);
  assert(q < r);
  assert((int\*)ps == p);
  // 上述断言一定不会失败

    这时,有朋友可能会问:"标准是否规定相邻字段在内存中也相邻?"。 唔,对不起,ANSI C没有做出保证,你的程序在任何时候都不应该依赖这个假设。那这是否意味着我们永远无法勾勒出一幅更清晰更精确的结构体内存布局图?哦,当然不是。不过先让我们从这个问题中暂时抽身,关注一下另一个重要问题————内存对齐。

    许多实际的计算机系统对基本类型数据在内存中存放的位置有限制,它们会要求这些数据的首地址的值是某个数k(通常它为4或8)的倍数,这就是所谓的内存对齐,而这个k则被称为该数据类型的对齐模数(alignment modulus)。当一种类型S的对齐模数与另一种类型T的对齐模数的比值是大于1的整数,我们就称类型S的对齐要求比T强(严格),而称T比S弱(宽松)。这种强制的要求一来简化了处理器与内存之间传输系统的设计,二来可以提升读取数据的速度。比如这么一种处理器,它每次读写内存的时候都从某个8倍数的地址开始,一次读出或写入8个字节的数据,假如软件能保证double类型的数据都从8倍数地址开始,那么读或写一个double类型数据就只需要一次内存操作。否则,我们就可能需要两次内存操作才能完成这个动作,因为数据或许恰好横跨在两个符合对齐要求的8字节内存块上。某些处理器在数据不满足对齐要求的情况下可能会出错,但是Intel的IA32架构的处理器则不管数据是否对齐都能正确工作。不过Intel奉劝大家,如果想提升性能,那么所有的程序数据都应该尽可能地对齐。Win32平台下的微软C编译器(cl.exe for 80x86)在默认情况下采用如下的对齐规则: 任何基本数据类型T的对齐模数就是T的大小,即sizeof(T)。比如对于double类型(8字节),就要求该类型数据的地址总是8的倍数,而char类型数据(1字节)则可以从任何一个地址开始。Linux下的GCC奉行的是另外一套规则(在资料中查得,并未验证,如错误请指正):任何2字节大小(包括单字节吗?)的数据类型(比如short)的对齐模数是2,而其它所有超过2字节的数据类型(比如long,double)都以4为对齐模数。

    现在回到我们关心的struct上来。ANSI C规定一种结构类型的大小是它所有字段的大小以及字段之间或字段尾部的填充区大小之和。嗯?填充区?对,这就是为了使结构体字段满足内存对齐要求而额外分配给结构体的空间。那么结构体本身有什么对齐要求吗?有的,ANSI C标准规定结构体类型的对齐要求不能比它所有字段中要求最严格的那个宽松,可以更严格(但此非强制要求,VC7.1就仅仅是让它们一样严格)。我们来看一个例子(以下所有试验的环境是Intel Celeron 2.4G + WIN2000 PRO + vc7.1,内存对齐编译选项是"默认",即不指定/Zp与/pack选项):

  typedef struct ms1  
  {
     char a;
     int b;
  } MS1;

    假设MS1按如下方式内存布局(本文所有示意图中的内存地址从左至右递增):
       _____________________________
       |       |                   |
       |   a   |        b          |
       |       |                   |
       +---------------------------+
 Bytes:    1             4

    因为MS1中有最强对齐要求的是b字段(int),所以根据编译器的对齐规则以及ANSI C标准,MS1对象的首地址一定是4(int类型的对齐模数)的倍数。那么上述内存布局中的b字段能满足int类型的对齐要求吗?嗯,当然不能。如果你是编译器,你会如何巧妙安排来满足CPU的癖好呢?呵呵,经过1毫秒的艰苦思考,你一定得出了如下的方案:

       _______________________________________
       |       |\\\\\\\\\\\|                 |
       |   a   |\\padding\\|       b         |
       |       |\\\\\\\\\\\|                 |
       +-------------------------------------+
 Bytes:    1         3             4

    这个方案在a与b之间多分配了3个填充(padding)字节,这样当整个struct对象首地址满足4字节的对齐要求时,b字段也一定能满足int型的4字节对齐规定。那么sizeof(MS1)显然就应该是8,而b字段相对于结构体首地址的偏移就是4。非常好理解,对吗?现在我们把MS1中的字段交换一下顺序:

  typedef struct ms2  
  {
     int a;
     char b;
  } MS2;

    或许你认为MS2比MS1的情况要简单,它的布局应该就是

       _______________________
       |             |       |
       |     a       |   b   |
       |             |       |
       +---------------------+
 Bytes:      4           1

    因为MS2对象同样要满足4字节对齐规定,而此时a的地址与结构体的首地址相等,所以它一定也是4字节对齐。嗯,分析得有道理,可是却不全面。让我们来考虑一下定义一个MS2类型的数组会出现什么问题。C标准保证,任何类型(包括自定义结构类型)的数组所占空间的大小一定等于一个单独的该类型数据的大小乘以数组元素的个数。换句话说,数组各元素之间不会有空隙。按照上面的方案,一个MS2数组array的布局就是:

|<-    array\[1\]     ->|<-    array\[2\]     ->|<- array\[3\] .....

\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_
|             |       |              |      |
|     a       |   b   |      a       |   b  |.............
|             |       |              |      |
+----------------------------------------------------------
Bytes:  4         1          4           1

    当数组首地址是4字节对齐时,array[1].a也是4字节对齐,可是array[2].a呢?array[3].a ....呢?可见这种方案在定义结构体数组时无法让数组中所有元素的字段都满足对齐规定,必须修改成如下形式:

       ___________________________________
       |             |       |\\\\\\\\\\\|
       |     a       |   b   |\\padding\\|
       |             |       |\\\\\\\\\\\|
       +---------------------------------+
 Bytes:      4           1         3

    现在无论是定义一个单独的MS2变量还是MS2数组,均能保证所有元素的所有字段都满足对齐规定。那么sizeof(MS2)仍然是8,而a的偏移为0,b的偏移是4。

    好的,现在你已经掌握了结构体内存布局的基本准则,尝试分析一个稍微复杂点的类型吧。

  typedef struct ms3  
  {
     char a;
     short b;
     double c;
  } MS3;

    我想你一定能得出如下正确的布局图:
        
        padding 
           |
      _____v_________________________________
      |   |\|     |\\\\\\\\\|               |
      | a |\|  b  |\padding\|       c       |
      |   |\|     |\\\\\\\\\|               |
      +-------------------------------------+
Bytes:  1  1   2       4            8
          
    sizeof(short)等于2,b字段应从偶数地址开始,所以a的后面填充一个字节,而sizeof(double)等于8,c字段要从8倍数地址开始,前面的a、b字段加上填充字节已经有4 bytes,所以b后面再填充4个字节就可以保证c字段的对齐要求了。sizeof(MS3)等于16,b的偏移是2,c的偏移是8。接着看看结构体中字段还是结构类型的情况:

  typedef struct ms4  
  {
     char a;
     MS3 b;
  } MS4;

    MS3中内存要求最严格的字段是c,那么MS3类型数据的对齐模数就与double的一致(为8),a字段后面应填充7个字节,因此MS4的布局应该是:
       _______________________________________
       |       |\\\\\\\\\\\|                 |
       |   a   |\\padding\\|       b         |
       |       |\\\\\\\\\\\|                 |
       +-------------------------------------+
 Bytes:    1         7             16

    显然,sizeof(MS4)等于24,b的偏移等于8。

    在实际开发中,我们可以通过指定/Zp编译选项来更改编译器的对齐规则。比如指定/Zpn(VC7.1中n可以是1、2、4、8、16)就是告诉编译器最大对齐模数是n。在这种情况下,所有小于等于n字节的基本数据类型的对齐规则与默认的一样,但是大于n个字节的数据类型的对齐模数被限制为n。事实上,VC7.1的默认对齐选项就相当于/Zp8。仔细看看MSDN对这个选项的描述,会发现它郑重告诫了程序员不要在MIPS和Alpha平台上用/Zp1和/Zp2选项,也不要在16位平台上指定/Zp4和/Zp8(想想为什么?)。改变编译器的对齐选项,对照程序运行结果重新分析上面4种结构体的内存布局将是一个很好的复习。

    到了这里,我们可以回答本文提出的最后一个问题了。结构体的内存布局依赖于CPU、操作系统、编译器及编译时的对齐选项,而你的程序可能需要运行在多种平台上,你的源代码可能要被不同的人用不同的编译器编译(试想你为别人提供一个开放源码的库),那么除非绝对必需,否则你的程序永远也不要依赖这些诡异的内存布局。顺便说一下,如果一个程序中的两个模块是用不同的对齐选项分别编译的,那么它很可能会产生一些非常微妙的错误。如果你的程序确实有很难理解的行为,不防仔细检查一下各个模块的编译选项。

    思考题:请分析下面几种结构体在你的平台上的内存布局,并试着寻找一种合理安排字段声明顺序的方法以尽量节省内存空间。

    A. struct P1 { int a; char b; int c; char d; };  
    B. struct P2 { int a; char b; char c; int d; };
    C. struct P3 { short a\[3\]; char b\[3\]; };
    D. struct P4 { short a\[3\]; char \*b\[3\]; };
    E. struct P5 { struct P2 \*a; char b; struct P1 a\[2\];  };

 
 
 1、 sizeof应用在结构上的情况

请看下面的结构:

struct MyStruct

{

double dda1;

char dda;

int type

};

对结构MyStruct采用sizeof会出现什么结果呢?sizeof(MyStruct)为多少呢?也许你会这样求:

sizeof(MyStruct)=sizeof(double)+sizeof(char)+sizeof(int)=13

但是当在VC中测试上面结构的大小时,你会发现sizeof(MyStruct)为16。你知道为什么在VC中会得出这样一个结果吗?

其实,这是VC对变量存储的一个特殊处理。为了提高CPU的存储速度,VC对一些变量的起始地址做了“对齐”处理。在默认情况下,VC规定各成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该变量的类型所占用的字节数的倍数。下面列出常用类型的对齐方式(vc6.0,32位系统)。

类型
对齐方式(变量存放的起始地址相对于结构的起始地址的偏移量)

Char:偏移量必须为sizeof(char)即1的倍数;
int:偏移量必须为sizeof(int)即4的倍数;
float:偏移量必须为sizeof(float)即4的倍数;
double:偏移量必须为sizeof(double)即8的倍数;
Short:偏移量必须为sizeof(short)即2的倍数。

各成员变量在存放的时候根据在结构中出现的顺序依次申请空间,同时按照上面的对齐方式调整位置,空缺的字节VC会自动填充。同时VC为了确保结构的大小为结构的字节边界数(即该结构中占用最大空间的类型所占用的字节数)的倍数,所以在为最后一个成员变量申请空间后,还会根据需要自动填充空缺的字节。

下面用前面的例子来说明VC到底怎么样来存放结构的。

struct MyStruct

{

double dda1;

char dda;

int type

};

为上面的结构分配空间的时候,VC根据成员变量出现的顺序和对齐方式,先为第一个成员dda1分配空间,其起始地址跟结构的起始地址相同(刚好偏移量0刚好为sizeof(double)的倍数),该成员变量占用sizeof(double)=8个字节;接下来为第二个成员dda分配空间,这时下一个可以分配的地址对于结构的起始地址的偏移量为8,是sizeof(char)的倍数,所以把dda存放在偏移量为8的地方满足对齐方式,该成员变量占用 sizeof(char)=1个字节;接下来为第三个成员type分配空间,这时下一个可以分配的地址对于结构的起始地址的偏移量为9,不是sizeof (int)=4的倍数,为了满足对齐方式对偏移量的约束问题,VC自动填充3个字节(这三个字节没有放什么东西),这时下一个可以分配的地址对于结构的起始地址的偏移量为12,刚好是sizeof(int)=4的倍数,所以把type存放在偏移量为12的地方,该成员变量占用sizeof(int)=4个字节;这时整个结构的成员变量已经都分配了空间,总的占用的空间大小为:8+1+3+4=16,刚好为结构的字节边界数(即结构中占用最大空间的类型所占用的字节数sizeof(double)=8)的倍数,所以没有空缺的字节需要填充。所以整个结构的大小为:sizeof(MyStruct)=8+1+ 3+4=16,其中有3个字节是VC自动填充的,没有放任何有意义的东西。

下面再举个例子,交换一下上面的MyStruct的成员变量的位置,使它变成下面的情况:

struct MyStruct

{

char dda;

double dda1;

int type

};

这个结构占用的空间为多大呢?在VC6.0环境下,可以得到sizeof(MyStruc)为24。结合上面提到的分配空间的一些原则,分析下VC怎么样为上面的结构分配空间的。

struct MyStruct

{

char dda;//偏移量为0,满足对齐方式,dda占用1个字节;

double dda1;//下一个可用的地址的偏移量为1,不是sizeof(double)=8的倍数,需要补足7个字节才能使偏移量变为8(满足对齐方式),因此VC自动填充7个字节,dda1存放在偏移量为8的地址上,它占用8个字节。

int type;//下一个可用的地址的偏移量为16,是sizeof(int)=4的倍数,满足int的对齐方式,所以不需要VC自动填充,type存放在偏移量为16的地址上,它占用4个字节。

};

所有成员变量都分配了空间,空间总的大小为1+7+8+4=20,不是结构的节边界数(即结构中占用最大空间的类型所占用的字节数sizeof (double)=8)的倍数,所以需要填充4个字节,以满足结构的sizeof(double)=8的倍数。所以该结构总的大小为:sizeof (MyStruc)为1+7+8+4+4=24。其中总的有7+4=11个字节是VC自动填充的,没有放任何有意义的东西。

VC对结构的存储的特殊处理确实提高CPU存储变量的速度,但是有时候也带来了一些麻烦,我们也屏蔽掉变量默认的对齐方式,自己可以设定变量的对齐方式。

VC 中提供了#pragma pack(n)来设定变量以n字节对齐方式。n字节对齐就是说变量存放的起始地址的偏移量有两种情况:第一、如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式,第二、如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。结构的总大小也有个约束条件,分下面两种情况:如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数;否则必须为n的倍数。

下面举例说明其用法。

#pragma pack(push) //保存对齐状态

#pragma pack(4)//设定为4字节对齐

struct test

{

char m1;

double m4;

int m3;

};

#pragma pack(pop)//恢复对齐状态

以上结构的大小为16,下面分析其存储情况,首先为m1分配空间,其偏移量为0,满足我们自己设定的对齐方式(4字节对齐),m1占用1个字节。接着开始为 m4分配空间,这时其偏移量为1,需要补足3个字节,这样使偏移量满足为n=4的倍数(因为sizeof(double)大于n),m4占用8个字节。接着为m3分配空间,这时其偏移量为12,满足为4的倍数,m3占用4个字节。这时已经为所有成员变量分配了空间,共分配了16个字节,满足为n的倍数。如果把上面的#pragma pack(4)改为#pragma pack(16),那么我们可以得到结构的大小为24。

2、 sizeof用法总结

在VC中,sizeof有着许多的用法,而且很容易引起一些错误。下面根据sizeof后面的参数对sizeof的用法做个总结。

A. 参数为数据类型或者为一般变量。例如sizeof(int),sizeof(long)等等。这种情况要注意的是不同系统系统或者不同编译器得到的结果可能是不同的。例如int类型在16位系统中占2个字节,在32位系统中占4个字节。

B. 参数为数组或指针。下面举例说明.

int a\[50\]; //sizeof(a)=4\*50=200; 求数组所占的空间大小

int \*a=new int\[50\];// sizeof(a)=4; a为一个指针,sizeof(a)是求指针的大小,在32位系统中,当然是占4个字节。

C.参数为结构或类。Sizeof应用在类和结构的处理情况是相同的。但有两点需要注意:第一、结构或者类中的静态成员不对结构或者类的大小产生影响,因为静态变量的存储位置与结构或者类的实例地址无关。第二、没有成员变量的结构或类的大小为1,因为必须保证结构或类的每一个实例在内存中都有唯一的地址。

下面举例说明,

Class Test{int a;static double c};//sizeof(Test)=4,静态成员不对结构或者类的大小产生影响

Test \*s;//sizeof(s)=4,s为一个指针。

Class test1{ };//sizeof(test1)=1,没有成员变量的结构或类的大小为1

D. 参数为其他。下面举例说明。

int func(char s\[5\]);

{

cout<
//数的参数在传递的时候系统处理为一个指针,所以sizeof(s)实际上为求指针的大小。

return 1;

}

//sizeof(func(“1234”))=4//因为func的返回类型为int,所以相当于求sizeof(int).

以上为sizeof的基本用法,在实际的使用中要注意分析VC的分配变量的分配策略,这样的话可以避免一些错误。

相关文章:

    技术文章

查看或添加留言

用WiX编写MSI安装包

· 阅读需 4 分钟

 

WiX(Windows Installer XML)是Rob Mensching(Windows组的一个SDE)写的一个用来编写MSI安装包的工具集,已经被微软内部的很多产品组采用,例如Office组,以及我所在的MSN组,等等。

下面是WiX的一个简单例子(相当于高级的Hello World!的级别),在这个例子中编写了一个SimpleCopy.msi。安装SimpleCopy.msi将能够:

  • 创建C:\Program Files\SimpleCopy目录并拷贝一系列文件;
  • 创建HKEY_LOCAL_MACHINE\SOFTWARE\MPG Lab注册表键,该键下的DestPath变量存储了一个文件路径,DestPath可以在msiexec命令行中用DESTPATH来指定;
  • 创建名为SimplyCopy的Scheduled Task,该任务每小时运行一次,执行C:\Program Files\SimpleCopy\SimpleCopy.exe,将该目录下data\source.xml拷贝到DestPath中指定的目录;
  • Uninstall时,安装时拷贝的文件、创建的注册表键和Scheduled Task都将被删除。

下面就是用WiX编写SimpleCopy.msi的步骤:

1. 从http://sourceforge.net/projects/wix下载wix的2.0.3220.0版本并解压到硬盘,将candle.exe所在的路径添加到Path环境变量中;
2. 用C#编写SimpleCopy.cs并用csc.exe在同一目录下编译成SimpleCopy.exe:

using System;
using System.IO;
using System.Reflection;
using Microsoft.Win32;

namespace SimpleCopy
...{
internal class Application
...{
[STAThread]
private static void Main(string[] args)
...{
string sourcexml = GetExecutableLocation() + @"\data\source.xml";
if (File.Exists(sourcexml))...{
DateTime now = DateTime.Now;
File.Copy(sourcexml, Application.QueryDestPath() +
@"\source_" + now.ToString("yyMMdd_HH.mm.ss") + ".xml", true);
}
else...{
StreamWriter writer = new StreamWriter(QueryDestPath() + @"\error.log", false);
writer.WriteLine(DateTime.Now.ToString());
writer.WriteLine("Cannot find source file: " + sourcexml);
writer.Close();
}
}

private static string QueryDestPath()
...{
RegistryKey regkey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\MPG Lab");
if (regkey != null)...{
object obj = regkey.GetValue("DestPath");
if (obj != null)...{
return obj.ToString();
}
}

if (false == Directory.Exists(GetExecutableLocation() + @"\regkey_not_found"))...{
Directory.CreateDirectory(GetExecutableLocation() + @"\regkey_not_found");
}
return (GetExecutableLocation() + @"\regkey_not_found");
}

private static string GetExecutableLocation()
...{
return Directory.GetParent(Assembly.GetEntryAssembly().Location).ToString();
}

}
}

3. 在SimpleCopy.cs同一目录下创建"data"子目录,在data\下创建一个任意内容的source.xml;
4. 在SimpleCopy.cs同一目录下编写SimpleCopy.wxs,其Schema文件wix.xsd可以在wix下载解压后的doc目录中找到。SimpleCopy.wxs内容如下:

<?xml version='1.0'?>
<Wix xmlns='http://schemas.microsoft.com/wix/2003/01/wi'>
<Product Id='c859431f-086d-4840-b2d7-a84b3bce50f2' Name='SimpleCopy'
Language='1033' Version='1.0.0.0' Manufacturer='MPG China'>
<Package Id='2738eb03-978b-4712-8e21-7e1868c74c2f' InstallerVersion='200' Compressed='yes' />
<Property Id='DESTPATH' Value ='C:\'/>
<Media Id='1' Cabinet='product.cab' EmbedCab='yes' />
<Directory Id='TARGETDIR' Name='SourceDir'>
<Directory Id='ProgramFilesFolder' Name='PFiles'>
<Directory Id='AppDirectory' Name='SimpleCp' LongName='SimpleCopy'>
<Component Id='ExecutableFile' DiskId='1' Guid='379ccaeb-a8c9-448e-a4b3-fa9884676492'>
<File Id='SimpleCopy_exe' Name='SimpleCp.exe'
LongName='SimpleCopy.exe' src='SimpleCopy.exe' />
</Component>
<Directory Id='DataFolder' Name='data'>
<Component Id='SrcXml' DiskId='1' Guid='746496f1-d2b1-4334-9b6f-00f71938c459'>
<File Id='Source_xml' Name='source.xml' src='data\source.xml' />
<Registry Id='4e10403a-1976-447f-b38c-59839cb7c5cb' Root='HKLM' Type='string'
Key='SOFTWARE\MPG Lab' Name='DestPath' Value ='[DESTPATH]'/>
</Component>
</Directory>
</Directory>
</Directory>
</Directory>
<Feature Id='AllFiles' Title='Simple Copy All Files' Level='1'>
<ComponentRef Id='ExecutableFile' />
<ComponentRef Id='SrcXml' />
</Feature>
<InstallExecuteSequence>
<Custom Action='CreateScheduleTask' After='InstallFinalize'>NOT Installed</Custom>
<Custom Action='DeleteScheduleTask' After='InstallFinalize'>Installed</Custom>
</InstallExecuteSequence>
<Property Id='SCHTASKS'>SCHTASKS.exe</Property>
<CustomAction Id='CreateScheduleTask' Property='SCHTASKS'
ExeCommand='/Create /RU "" /SC HOURLY /TN SimplyCopy /TR "%ProgramFiles%\SimpleCopy\SimpleCopy.exe"'/>
<CustomAction Id='DeleteScheduleTask' Property='SCHTASKS'
ExeCommand='/Delete /TN SimplyCopy /F'/>
</Product>
</Wix>

5. 打开命令行,进入SimpleCopy.cs所在的目录,先后运行candle SimpleCopy.wxs以及light SimpleCopy.wixobj,然后就得到SimpleCopy.msi了:

![](images/o_wixsample.gif)

6. 在命令行运行“msiexec /i SimpleCopy.msi /quiet /log log.txt DESTPATH=d:\”就可以安装了。

相比较其他MSI编写工具(例如Visual Studio 2003/2005,以及InstallShield等),WiX的优点是:

  1. 便于版本控制(wxs是纯文本的);
  2. 便于自动化(编辑工具candle.exe和light.exe都是命令行的)。
查看或添加留言

C# 中的接口与抽象类

· 阅读需 6 分钟

 

一.接口的定义

定义:定义一个协定。实现接口的类或结构必须遵守其协定。

简单的说就是接口或者类之间交互时遵守的一个显示定义。最初接触“类与类之间通过接口交互”这个概念时,误以为接口就是类公开的方法,类之间通过类的方法进行交互。其实接口是独立于类的一个定义。接口定义类之间交互的标准。

那么类与类之间直接交互就好了,为什么还要使用接口呢?

这主要是因为:接口是类之间交互内容的一个抽象,把类之间需要交互的内容抽象出来定义成接口,可以更好的控制类之间的逻辑交互。可见接口内容的抽象好坏关系到整个程序的逻辑质量;另外可以在任何时候通过开发附加接口和实现来添加新的功能;

关于接口一个很重要的概念:接口只包含成员定义,不包含成员的实现,成员的实现需要在继承的类或者结构中实现。

接口的成员包括:方法,特性,索引器,事件。

**注意:**接口不包含字段。

实现接口的类必须严格按其定义来实现接口的每个方面

      接口本身一旦被发布就不能再更改,对已发布的接口进行更改会破坏现有的代码。

一个典型的接口示例:         

using System;

using System.Colletion;

public delegate voic Chang(object  sender,object event)//定义一个委托

public interface Ibroker  //定义一个股票经济人接口

{

  string GetRating (string stock);//一个获得金额的方法(此处没有实现)

  decimal pricePerTrade    //定义一个设置每股价格的特性

  {

get;                //没有实现的

set;

}

decimal this(string StockName)    //定义索引器

{

  get;

  set;

}

event Change pricechange;   //定义接口的事件

}

二.接口与抽象类

   抽象类和接口在定义上和功能上有很多相似的地方,具体在程序中使用抽象类还是接口需要比较抽象类和接口的具体差别。

抽象类:一种不能实例化而必须从中继承的类,抽象类可以提供实现,也可以不提供实现

        子类只能从一个抽象类继承

       抽象类应主要用于关系密切的对象

       如果要设计大的功能单元,则使用抽象类。

       如果预计要创建组件的多个版本,则创建抽象类

接口:是完全抽象的成员集合,不提供认识实现。

类或者结构可以继承几个接口。

接口最适合为不相关的类提供通用功能

如果要设计小而简练的功能块,则使用接口

接口一旦创建就不能更改。如果需要接口的新版本,必须创建一个全新的接口

三.接口的的实现

   接口的实现分为:隐式实现和显式实现。如果类或者结构要实现的是单个接口,可以使用隐式实现,如果类或者结构继承了多个接口那么接口中相同名称成员就要显示实现。显示实现是通过使用接口的完全限定名来实现接口成员的。

针对上面的例子 我们可以这样实现接口:

public class testInterface: Ibroker  //定义一个继承ibroker接口的类

{

  hashtable hash=new hashtable();

  decimal pricepertrade;

  public testInterface(decimal price)   // 构造函数

  {

    pricepertrade=price ;             //初始化字符串

}

public string  Getrating (string stock)     //隐式实现接口的方法

{

   return “buy”;

}

public decimal Ibroker.pricepertrade //  显式实现接口的特性

{

  get

{

  return pricepertrade;

}

set

{

  pricepertrade=value;

pricechange(“Finacebroker”,value);

}

public decimal this(string stockName)

{

  get

{

  return (decimal)Hash\[stockName\];

}

set

 {

   hash.add(stockname,value);

}

}

}

public event changer pricechange;//接口中的所有成员都要实现

}

四.接口中的多态

 多个类继承了相同的接口就实现了接口的多态性,接口的多态性的访问和类的多态性访问一样。下面的例子说明怎么实现接口的多态性访问:

public class InterfaceTester

{

  public stratic int Main (string\[\] args)

  {

string recommendation;

Arraylist Brokers=new Arraylist;//定义一个列表

Brokers.add(new  firstBroker(7.21m));//添加第一个继承接口的类

Brokers.add(new  secondBroker(12.3m));// 添加第二个继承接口的类

InterfacetTester new Iftst=new InterfacetTester

Foreach (Ibroker broker in Brokers)

{

   broker.pricechange+=new change(iftst.pricepertradechange);

   broker\[“adc”\]=12.33m;

   broker\[“rty”\]=11.23ml

   broker.pricepertrade=12.55m;

}

}

}
查看或添加留言

生物进化与软件演化

· 阅读需 26 分钟

作为一名软件工程师,同时拥有生物医学的研究背景,我对计算机科学与生物学都有着浓厚的兴趣。平日里,这两方面的书籍常常是我的读物。虽然这两门学科看似风马牛不相及,但它们的某些思想却能够相互借鉴、启发。这篇文章将分享进化论的相关理论如何影响了我的程序设计思路,以及在学习编程思想后对进化论的新认识。

一、基因突变与物竞天择思想在编程算法中的应用

计算机科学中有一门学科叫做遗传算法,它系统地探讨了如何将遗传与进化的思想引入计算机科学,比我的尝试要深入得多。不过,在正式接触这门学科之前,我曾经有过一些朴素的探索。

最初接触这个问题的契机是这样的:一个朋友刚学会了黑白棋(Othello),邀请我对战。虽然他是个新手,但我的水平更糟糕,总是输给他。于是我萌生了一个想法:既然下棋不行,那不如发挥编程的专长,写个程序来击败他。然而,问题在于我对黑白棋的规则和策略了解有限,怎么确保程序能比我更强呢?

黑白棋规则相对简单,基本算法容易实现。我最初的想法是让计算机模仿我的思路下棋,借助它的计算速度优势,理论上应该能比我强一些。但这还不够,我希望程序能通过某种方式自我学习、不断提升棋艺。这个想法受到了进化论中基因突变和物竞天择思想的启发:让不同的程序进行对战,淘汰表现差的,保留和优化表现好的,从而推动棋力的逐代提高。

我的方法是提取可能影响棋局胜负的参数,比如棋子的落点、每颗棋子周围的空格数量、己方可落子的数量、对方可落子的数量等等。虽然我对这些参数的实际作用心中无数,但参数一定要“宁多勿漏”,程序会自动筛选出那些有用的参数。这些参数可以看作程序的“基因”,不同程序的区别就在于这些参数的具体值有所不同。

接下来,我组织了几十个程序进行循环赛,并将成绩居于后半的程序淘汰;成绩靠前的程序保留它们的“基因”,还可以通过“基因重组”和“随机突变”等方式,调整参数的值,生成新的后代。如此一代代模拟进化,程序的棋力也随之逐步提高。

最终,这个程序经过一段时间的自我训练,成功战胜了我的朋友。这让我感到相当自豪!

然而,这种“进化式”程序设计也暴露了一些明显的问题,最主要的是效率低下。程序棋力的提升非常缓慢。这点其实不难理解,毕竟生物界花了数十亿年的时间才进化到今天的水平。即便我的程序效率远高于自然进化,想取得显著的进步也可能需要数百年的时间。

另外,每代程序的“种群规模”太小,仅有几十个样本。相比之下,生物界每代往往有几百万甚至几亿的个体,这样的大样本量才能提供足够的多样性。我的程序等于在“近亲繁殖”,甚至可能导致棋力一代不如一代。这两个问题的根源在于计算机的运算与存储能力有限。

遗传算法中提到的解决方案远比我的方法成熟,但也存在类似的局限性。对这一领域感兴趣的读者不妨深入研究相关书籍。

二、对进化论的反思

假如我基于基因突变和物竞天择思想设计的黑白棋程序,一直按照既定规则运行,经过几万年的演化,是否可能进化成能够下五子棋或者其他棋类的程序呢?这一问题引发了我对进化论的重新思考。

达尔文在《物种起源》中提出了两大核心假设:同源说和进化论。同源说认为地球上的所有物种都源于同一个最初的生命体,这一观点得到了较为广泛的认可,特别是在基因学和分子生物学的支持下。比如,人类与单细胞细菌之间也存在高度同源的基因,这为同源说提供了强有力的佐证。

然而,进化论,即通过基因突变和物竞天择解释物种多样性的假说,却从一开始便饱受质疑。虽然 DNA 的发现为进化论提供了一定支持,但仅凭基因突变与自然选择能否完全解释生物的演化仍存在诸多争议。

进化论主要依赖三大证据:比较解剖学、古生物学(化石记录)以及胚胎发育的重演律。然而,这些证据并非无懈可击。

  1. 比较解剖学 人类和猴子的骨骼结构高度相似,这是比较解剖学的经典证据之一。有人认为这是一种循环论证:人和猴子的骨骼相似是因为它们有共同的祖先,而为何有共同祖先的原因又归结于骨骼的相似性。然而,我认为骨骼相似本身是一种客观现象,无需证明,也无需与进化论挂钩。这一现象可以很好地支持同源说,但却无法解释生物演变的具体过程。

  2. 古生物学(化石记录) 按照基因突变理论,生物的演化应是一个缓慢而连续的过程。然而,化石记录却提供了截然不同的图景。大多数化石展示的是物种突然出现并长期保持稳定的状态,过渡类型的化石寥寥无几。例如,始祖鸟是为数不多的“过渡类型”化石之一,但与成千上万的物种化石相比,显得过于稀少。

更为显著的是所谓的“寒武纪大爆发”现象:在30亿年前至5亿年前的漫长时间里,化石记录几乎全部是单细胞生物。然而在5亿年前,各种复杂的多细胞生物似乎在一夜之间出现在海洋中。这种突发性和缺乏过渡类型的化石记录,更多地揭示了基因突变理论的局限性。

  1. 胚胎发育的重演律 重演律认为胚胎发育过程会重现物种的演化历程。然而,如果进化的驱动力是基因突变,原有的信息理应被替代或遗忘,那么胚胎为何会保留并重现这些信息?除非生物的演化并非完全依赖基因突变,而可能涉及更为复杂的机制。

回到文章开头的问题:如果让我的黑白棋程序持续进行基因变化,它能否演化为下五子棋或其他棋类的程序?答案显然是否定的。因为黑白棋程序的基因设定是专注于黑白棋规则的,在没有外界干预或新信息输入的情况下,它的“进化”只会局限在原有的范围内。这表明,“基因突变”并不足以解释复杂系统如何从无到有地发展出新的功能。

综合来看,进化论的三大证据在解释物种多样性时都存在局限性。尽管基因突变和自然选择无疑在演化中起到了重要作用,但将其作为唯一机制,可能无法全面揭示生物演化的真实过程。或许,探索其他未被完全理解的生物机制,才是进一步解答这些谜题的关键。

三、程序是如何“进化”的

虽然我的黑白棋程序无法自行进化成五子棋程序,但在我的干预下,这种“进化”却完全可行。

我的程序使用 C++ 编写。熟悉 C++ 的人都知道,即使是一个简单的程序,也通常会运用面向对象和泛型编程等思想,我自然也不例外。那么,如果我打算开发一个五子棋程序,应该如何着手呢?

黑白棋和五子棋有许多相似之处,比如棋盘的类型、棋子的基本属性等。如果从头重新创建一个五子棋程序,显然既费时又低效。

最直接的办法是复制原来的黑白棋代码,然后在此基础上修改。然而,这种方法存在明显的问题:代码冗余。两份程序会包含大量重复代码,不仅浪费资源,还会给后续的维护带来麻烦。比如,如果我优化了黑白棋程序的某部分代码,还需要手动将改进同步到五子棋程序中,这显然不够灵活。

得益于面向对象的编程方法,这个问题可以迎刃而解。我可以将黑白棋程序中的核心功能抽象为一个基类,并在此基础上派生出五子棋的类。五子棋中与黑白棋不同的特性和方法,可以通过覆写(override)实现。在这种设计下,基类的代码仍然存在,但被覆写的部分在五子棋类中将被替代或补充。

更有趣的是,某些基类的功能并不会因为派生类的覆写而完全消失。例如,子类的构造函数通常会先调用基类的构造函数,然后再执行自己的逻辑。这意味着,父类的一些特征和行为仍然能够保留,并在子类实例化的过程中显现出来。

这种基类与派生类的关系,很容易让人联想到胚胎发育的重演过程。在生物学中,胚胎在发育早期会呈现出某些祖先的特征,而随着发育的进行,这些特征逐渐被新的结构和功能取代。这与程序设计中基类的部分代码被派生类覆盖,却依然能够影响派生类的实例化过程有异曲同工之妙。

这种程序设计方式,不仅减少了代码的重复性,还保留了灵活扩展的可能性。就像生物演化中的“保守与创新并存”,程序设计也在继承已有功能的基础上不断发展出新的特性。

四、对生物的进化的反思

参考程序自动“进化”的规律,我们或许可以提出一种新的假设:基因突变仅能在同一物种的小范围内引起改良,而真正导致质的变化的,除了量的积累外,更多的可能是类似程序中“继承”或“重组”这样的基因操作。

现代生物学研究发现,高等生物的染色体中存在大量所谓的“无用”基因片段。这些片段虽然在当前看似不起作用,但它们可能在生物的进化历史中曾经扮演过重要角色。随着生物体获得新的基因片段,这些新片段可能具有与旧片段类似的功能,但表现出更高的优先级,从而抑制了旧片段的活性。

当新基因片段主导生物体的功能后,旧片段逐渐失去作用,成为看似“无用”的遗留。然而,这些片段并未被完全淘汰,它们的存在暗示着一种“历史记忆”,记录了生物在进化过程中经历的不同阶段。这一机制与软件开发中的代码继承有异曲同工之妙:旧的功能被保留但不再被调用,而新的功能覆盖了它们的位置。

那么新基因片段是从何而来的呢?有很多种可能性,比如:

  • 有性繁殖的重组:有性繁殖是基因多样性的重要来源。在此过程中,父母的染色体通过交叉和重组,形成了包含新组合的染色体。而在某些特殊情况下,可能会引入完全新颖的基因片段,从而为后代带来前所未有的功能。
  • 基因交换:在低等生物中,比如细菌,基因交换是一种常见现象。通过质粒转移或其他方式,细菌可以将自身的基因传递给其他个体,这种机制为基因创新提供了直接途径。
  • 病毒的作用:病毒能够将自己的基因插入宿主细胞的染色体中。在某些情况下,这些外来基因可能被宿主整合并表达,从而引入新的遗传信息。
  • 外部环境的输入:新基因片段可能通过饮食或环境途径间接进入生物体。例如,食物中的 DNA 在极特殊的情况下可能被吸收并整合进自身的基因组中。

从以上分析可以看出,生物进化并非完全依赖基因突变的随机性,而是一个更为复杂的过程。突变提供了变异的基础,但快速的进化可能更依赖于基因重组、新基因的引入,以及生物间的遗传信息共享。

这种进化机制与软件设计中的继承和扩展有相似之处:旧有的功能可以被继承和改良,新功能的引入则通过扩展实现。这种“保守与创新”的平衡,或许正是生物进化与程序设计的共同规律。

五、未来软件的发展趋势

生物用了 30 亿年的进化才发展到今天的复杂程度,而计算机软件仅仅发展了不到50年。尽管两者当前的复杂性尚无法直接比较,但在人类智慧的推动下,软件进化的速度可能远超生物进化。或许在未来百年内,我们就能看到具备自我完善能力的智能软件,其发展速度可能是生物进化的千万倍。基于这一思路,我们可以大胆预测,未来软件的发展也许会在某些方面与生物进化展现出类似的规律。

在维护大型软件时,处理遗留代码(legacy code)是一个常见的挑战。例如,我最近需要修改一段存在于公司大型软件中的老旧代码。这些代码风格极差,如变量名使用单字符表示、大量硬编码等。如果几年前的我,可能会直接选择彻底重写这段代码。然而,实践经验表明,这种做法往往得不偿失。

一个大型软件经过几十年的发展,可能有数百甚至上千人参与过开发,其代码之间交织着复杂的引用关系。贸然重写某段代码,极可能在功能上无法完全覆盖原有逻辑,进而引入更多的隐性bug。而这正如生物学中所谓的“无用基因片段”:它们表面上似乎没有功能,但实际上可能隐藏着潜在的价值或作用。

现在生物学上,研究基因片段功能的方法主要是,先把一段基因敲掉,然后看看他对生物体带来了什么影响。然而大多数基因被敲掉以后,却看不出生物体受到了明显的影响,这就是那些“无用”基因片断。这一是因为上面提到的基因有备份,备用基因会及时发挥作用,弥补缺失的基因的功能。还有就是很多基因的功能是和其他很多基因共同作用才能显现出来的,或者是在某些特殊情况下才会被显现出来的。这些基因的功能不是那么容易被观察到的,他们缺失了,或者被添加到一个本来不具备这种基因的生物体上,它们的潜在的影响或许要过很多年才能被发现。现在大家对转基因作物的戒心,这也是原因之一。(对转基因食物更大的担心可能是在于,被植入的基因片段不够稳定,更容易整合到人体基因中去,导致人类的基因被转化。)

对遗留代码的最佳处理方式,通常不是直接改写,而是通过添加包装层的方式进行优化。例如:

  • 如果原代码是一个简单的除法运算,没有处理除数为零的情况,最佳做法不是直接修改源代码,而是在其外部包裹一层新函数。新函数负责检查除数是否为零,若是,则进行错误处理;否则调用原代码执行除法。这种方法避免了对原代码行为的直接改动,同时确保新的功能可控且安全。
  • 如果需要改动的是一个类,则可以通过继承原有类,创建一个派生类。在新类中覆盖或添加所需的功能,而原有类保持不变。这样,旧代码的稳定性得以保留,而新功能通过扩展实现。

这一策略的缺点在于:会导致程序体积迅速膨胀。然而,随着存储成本的下降和计算机硬件能力的提升,程序体积不再是首要问题。维护效率和功能安全性将成为更重要的考量。

随着软件系统的规模日益庞大,完全消除bug几乎是不可能的。尤其在关键场景中,系统的任何故障都会带来不可接受的损失。在这种情况下,备份机制成为保障系统稳定性的最佳手段。硬件系统已经普遍采用备份策略,未来的软件系统也会逐步引入这一思路。例如,对于某些核心功能,软件可以采用双实现策略:由不同的开发团队设计两套独立的实现代码。在正常情况下使用其中一套,当出现异常时自动切换到备份代码。这一方法与生物基因的双拷贝特性非常相似。生物体内的基因通常成对存在,不仅是为了繁衍后代,更是为了在突变或损伤时提供冗余保障。例如,当一条基因出现缺陷时,另一条可以迅速接替其功能,确保生物体正常运作。

随着技术的持续发展,软件的进化将越来越表现出类似生物系统的特性:

  • 冗余与容错:通过多版本实现和模块化设计,软件将具备更高的容错能力。
  • 继承与扩展:老旧代码不会被轻易抛弃,而是在其基础上进行扩展和优化,形成可持续的演进路径。
  • 自我完善:在人工智能的帮助下,未来的软件可能具备自我优化和自我修复能力,实现从“人类驱动”到“自我驱动”的飞跃。

生物进化依赖基因的突变与重组,而软件进化则依靠人类的智慧和创新。当我们结合生物与软件的进化规律进行思考,也许会发现,两者最终的目标并不冲突:创造出更智能、更高效的系统,推动未来社会的发展。

查看或添加留言

.NET Framework 的容器

· 阅读需 3 分钟

  自从我发现了STL,再编写C++程序,就离不开它了。STL提供的容器和算法极大的方便了C++编程。
  最近在对比研究C#中的泛型、标准容器和算法,看看它是如何实现类似STL的功能的。先总结一下C#中的标

准容器。我使用的是 Visual C# 2005。
 
  C# 中主要有两类容器:一个是 System.Array 类(参阅:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemarrayclasstopic.asp),类似 STL 中的 Vector;另一类是集合。集合还可以细分

成功能不同的几个容器。
  C# 1.0 的集合类的容器全部在 System.Collections 名字空间下。(参阅:http://msdn2.microsoft.com/en-us/library/k166wx47(en-us,VS.80).aspx)其中实现好的容器有:ArrayList

,BitArray,Hashtable,Queue,SortedList,Stack 。名字空间中的其它类,比如 Comparer 等,是用来帮助实现容器和接口的,就不把他们算作容器了。
  C# 2.0 由于增添了对泛型编程的支持,它又新增了一个名字空间 System.Collections.Generic,存放所有支持泛型的各种集合类的容器。(参阅:http://msdn2.microsoft.com/en-us/library/system.collections.generic)支持泛型的集合类容器有:Dictionary,LinkedList,List,Queue

,SortedDictionary,SortedList,Stack。
  与之对比对比,STL中实现的容器有:Vector,Deque,List,Set/Multiset,Map/Multimap,Stack,Queue,Bitset。

  下面主要介绍一下,C# 支持范型的容器。
  Array 顾名思义,提供了数组容器和操作,如查找、排序等。类似于STL中的 Vector。其声明如下:
  public abstract class Array : ICloneable, IList, ICollection, IEnumerable
  Array 与STL中的 Vector 和 C# 中的其它容器不同之处,它不需要特别的把变量用 Array 类来声明,直接在元素类型后加中括号就可以把变量指定为是 Array 容器。例如声明一个元素数据类型为int的数组容器用一下语句:int[] myIntArray = new int[5] 5;  语句中的 myIntArray 即成为一个数组容器。

  Dictionary 与 STL中的 Map/Multimap 相类似。Ditctionary 是两个类型参数(健和值)的集合。

  List, SortedList,与 STL中的 List 类似,提供链表容器和操作。

  Queue与 STL中的 Queue 相类似。是元素先进先出的集合。
  Stack与 STL中的 Stack 相类似。是元素先进后出的集合。

查看或添加留言

DLL的导入库中是什么信息

· 阅读需 2 分钟

当DLL被链接时,链接程序要查找关于输出变量,函数,或C++类的信息,并自动生成一个lib文件。该lib文件包含一个DLL输出的符号列表。如果要链接引用该DLL的输出符号的任何可执行模块,该lib文件是必不可少的(使用GetProcAddress除外)。

其实导入库中并不含RVA(每个符号的相对虚拟地址),只是一些符号而已,还有关于这个lib所对应的DLL的名字等。 (这只是我现在的理解)

那当应用程序调用一个DLL的函数时,是怎么进行的呢?(使用lib的情况下)

答案是在进程的主线程开始运行之前,由加载器完成。

加载器根据输入节中DLL的名字按照windows的搜索路径搜索DLL,找到后DLL映射到进程的地址空间,这是DLL中对应于输入节中的各个符号的地址就可以确定了,加载器在这个时候将地址重新填入可执行模块的输入节中,动态连接完成。

这就是使用lib命令就能将def文件生成lib文件的原因。

1)做一个def文件
XX.def :

LIBRARY  
 xujian.dll
EXPORTS
 xujian

////////////////////////////////////////////
2)生成lib文件:
lib /def: XX.def /machine: i386 /out: XX.lib

/////////////////////////////////////////////

3)做一个试验程序:(在工程文件中加入XX.lib)

extern "C" void xujian();

void main()
{
 xujian();
}

编译连接都通过!

查看或添加留言