分类: tech

  • 选书之美与购书之道

    什么什么之美与什么什么之道现在简直是有些烂大街了,
    就好像“寂寞”之于春晚,美和道这两个词能让计算机书商用上三四年没问题,
    比如《代码之美》与《代码之道》我都买了,可是都没看完,如果用豆瓣打分也就是三星,可以读读。我承认,这两本书买了有些后悔。

    好了,想说什么问题呢?怎么也要表达一些中心思想吧,
    没错,“选书跟买电脑一样,不要在刚出来的时候就买,至少等一两个月“
    这就是我的选书之美,美不美?

    比如《代码之美》这本书的内容简介(from china-pub):
    本书介绍了人类在一个奋斗领域中的创造性和灵活性:计算机系统的开发领域。在每章中的漂亮代码都是来自独特解决方案的发现,而这种发现是来源于作者超越既定边界的远见卓识,并且识别出被多数人忽视的需求以及找出令人叹为观止的问题解决方案。
    本书33章,有38位作者,每位作者贡献一章。每位作者都将自己心目中对于“美丽的代码”的认识浓缩在一章当中,张力十足。38位大牛,每个人对代码之美都有自己独特的认识,现在一览无余的放在一起,对于热爱程序的每个人都不啻一场盛宴。虽然本书的涉猎范围很广,但也只能代表一小部分在这个软件开发这个最令人兴奋领域所发生的事情。

    太牛逼了,简直是无以伦比的文案,可实质上,这本书就是一部散文集罢了。

    另外《代码之道》这本书,不知是不是我境界太低,读起来真是费劲啊,看了几张就丢一边了。如果你在书刚刚发布的几天购买,会发现星星的评分真是高,编辑、作者一个劲推荐,也有一些名牛人使劲推(我不认识这些名牛人,无法评价他们是不是真的看了),让你感觉不买就买不到了(新楼盘?)。过了几周,你会发现一些”不和谐“的声音慢慢出现了,这其实才是真正的读者开始慢慢浮现。我们排除敌对出版社的打击,里面还是有不少值得借鉴的意见和观点。

    OK,还有另外一半,就是购书之道。前面有同学在TL提问买什么书好,我的建议很简单,
    不要买带“美”和“道”的书,太玄了,编程就是编程,扯什么大道至简、负负得正的有什么意思?是不是联系上道家思想,编程就会变得清新空灵?(请去豆瓣搜“小清新”小组)

    好了,我瞎掰完了。

  • 一个有趣的代码问题

    OldNewThing发布了一篇很有意思的文章http://blogs.msdn.com/oldnewthing/archive/2010/01/20/9950638.aspx

    The wrong way to determine the size of a buffer

    A colleague of mine showed me some code from a back-end program on a web server. Fortunately, the company that wrote this is out of business. Or at least I hope they’re out of business!

    size = 16384;

    while (size && IsBadReadPtr(buffer, size))

    { size--; }

    高人就是高人,这个代码其实还是需要好好想一想才能明白为何有问题。

    IsBadReadPtr的意思是尝试去读一个不属于自己进程的内存地址,因为指针越界在大多数情况下都是代表错误发生。但是这个函数实际上并不是那么好用,具体原因可以看OldNewThing的这篇文章http://blogs.msdn.com/oldnewthing/archive/2006/09/27/773741.aspx 介绍的非常详细。

    回到这个问题上,在OldNewThing的文章中介绍的非常清楚“But guard page exceptions are raised only once.”,也就是说,IsBadReadPtr将只会有效一次(?need to double check),这个代码逻辑就是有问题的。

    再把其中包含的问题列一下,主要参考了文章后的留言。

    1)16384这个16K大小来的莫名其妙,当然不排除在某些特定软件设计中,最大就是这个大小。

    2)第二个问题,IsBadReadPtr并不是那么好用。

    3)这个计算得到的值未必正确。

    那么我们该如何获取buffer的大小呢?

    在c-faq中告诉我们,通常情况下是没有解决办法的,只能自己保存这个buffer大小。办法也很简单,我们需要申请的时候就把buffer大小记住,需要知道的时候,查找一下就知道了,或者是使用某种数据结构,把buffer的大小以及其他信息放在开头固定的一块大小就行了。

    如果是windows,那么有一些特定的办法。可以用_msize来获得malloc申请的buffer。

    allocated with LocalAlloc, use LocalSize. For HeapAlloc, use HeapSize. For GlobalAlloc, use GlobalSize,如果使用CoTaskMemalloc,可以先用CoGetMalloc拿到一个IMalloc接口,然后调用GetSize()

    这些函数到底好用不好用,还是得靠你自己多测试。最简单牢靠的办法还是自己保存这个size信息。

  • Google的Go编程语言使用初探

    Go编程语言是Google中一些大牛(尤其是有着plan9前科的大牛们)如Rob Pike,Ken Thomason这两位赫赫有名的程序高手、技术作家。

    很多人认为Go编程语言有点像是C语言与Python的混血,在Golang主页上也清楚写着Go的祖先有C,有Pascal/Modula(也是C++的祖先)/Oberon,还有CSP这个语言,另外很多基础代码也来自Plan 9操作系统。

    我是在一个Ubuntu9.10的虚拟机上试用的Go,大家可以跟着我的脚步一探Go的究竟。

    1,准备

    安装Go之前需要安装mercurial,这是Go的版本控制工具,可以直接通过ubuntu的安装程序搜索添加。

    然后为当前用户定义下面几个bash变量。在ubuntu下敲入cd $HOME,一般是/home/username,然后敲vim .bashrc,修改.bashrc

    在这个文件中添加如下(我的cpu是intel,所以是GOARCH是386,具体可参考golang.org说明):

    GOROOT="$HOME/Go"

    export GOROOT

    GOOS=linux

    export GOOS

    GOARCH=386

    export GOARCH

    GOBIN="$HOME/bin"

    export GOBIN

    在帮助里写GOBIN是可选的,但我试了,必须有。

    在.bashrc最后还要加上这一行,保证能正确编译安装。

    PATH=$PATH:$GOBIN

    2,下载源代码编译

    在你的用户目录下运行这个命令

    hg clone -r release https://go.googlecode.com/hg/ $GOROOT

    这样在你的用户目录下应该有个Go目录了。

    确保你的ubuntu安装了gcc(可以在软件包管理中添加build-essential)

    进入Go\src

    然后敲./all.bash

    如果前面没有问题,编译应该可以正确完成。编译后可以进入$HOME/bin查看是不是有6g 6l 8g 8l这样的可执行文件。

    image

    3,试用Go

    一般x86机器是使用8开头的命令,如8g进行编译,8l进行链接,6开头的是给AMD cpu使用,如果编译安装没错,那么$HOME/username/bin这个目录应该已经在PATH中(可以通过echo $PATH确认)。

    这时候应该hello world了,编辑一个hello.go如下:

    package main
    import "fmt"

    func main() { 
        fmt.Printf("Hello, world; or Καλημέρα κόσμε; or こんにちは 世界\n");
    }

    运行8g hello.go编译,正常情况下什么都不提示就执行完了,但是ls可以发现多了个hello.8文件

    运行8l hello.8链接,会产生一个8.out.

    运行./8.out应该打出如下消息:

    image

    然后可以进入Go的目录中(因为我设置的有问题,下载Go的目录名是hg,但是不影响什么),有个test目录,进去test目录,运行./run命令。

    image

    image

    也可以单独编译某个代码运行,如果正确的话,不会有任何提示信息输出。

    OK,基本上Go编程语言的大致试用情况就是这样,真正要学Go编程语言的朋友,还是需要花时间来读文档,写写代码不断练习的。

  • 勿用屠龙来杀猪-论如何正确整合Lua与C++

    经常有人问到关于Lua的问题是“Lua如何能使用C++的成员变量?”“Lua如何调用C++类的成员函数?”“C++的复杂数据结构(如数组)如何传递进入Lua让Lua可以使用?”

    这些问题之频繁,几乎每天都可以看到。问题的起源也很简单,这些提问者大多是C++程序员,公司需要使用Lua,他们就想如何能够“完美无缝”整合C++和Lua。

    如果在谷歌中文(google.cn)搜索Lua关键字,第一页大概前五六个网址中会有云风的这篇文字《Lua 不是 C++》,里面的结论部分十分精彩:

    对于那些新接触 lua 的 C/C++ 程序员来说,我的第一条建议通常是:看看 lisp/scheme 吧,可能 lua 的血统里,scheme 的成分比 C 更多一些。要不玩一下 Haskell ,增进对函数式编程的了解。C++ 借助 template 是可以玩玩函数式编程,但很少有人真的去用。进入 lua 的领域后,你得正正经经的理解一下了。

    Lua与C++不是一种语言,Lua的产生也不是为了让C++这个大奶高兴,它的地位是独立的。

    我在twitter上发了这样一些感慨:

    感觉这里面有百分之五十的问题都是c++如何导出所有类型给lua,lua如何调用c++成员变量、类成员、内部函数。这些人太纠结了!思路不对,就跟把c++当做高级c来使用一样。展开一点说,这种用法的来源,首先是设计人员脑子不清楚了,lua与c++的边界完全混在一起,没有把接口最小化,以及适当简化,就是c++完全贴到lua上。清晰地api(通用意义上的),就是定义适当的操作,可以扩展,但是不过分耦合。lua和c++应该是哑铃状连接,而不是水桶。

    为什么Lua与C++无缝整合有问题,原因很简单:任何一个有经验的程序员都知道,如果两个系统之间是紧密相连到成员函数成员变量都可以随便调用,那么这两个系统的耦合度一定是相当高,容易出现的问题就是难扩展、难修改、难维护。这两个系统不如直接整成一个系统算了。

    如何正确使用Lua与C++?

    首先第一个问题是,你的系统谁是主导?Lua还是C++?首先搞清楚这个问题。因为主导的部分代码将作为框架出现,负责调用或者响应事件、逻辑。如果Lua与C++的代码都做同样的工作,那么这个系统的设计是有问题的。

    第二个问题是,你的系统里面,作为主导部分想开放那些接口?(我们姑且认为Lua与C++的中间连接部分为接口interface)。两个系统之间,不需要完全整合,只要开放必要的接口或者说是API即可。在著名的《Designing Qt-Style C++ APIs》中,作者Matt总结了下面6个优秀API的特点:

    总而言之,Lua本身有它自身的特点,如函数式编程,尾调用,变量无类型等等,这些特点与C++是截然不同的,如果你非要把C++与Lua弄得无缝整合,将来一定会因为这个高耦合产生各种各样的问题。

    就如我在twitter中说的那样:编程弄得这么纠结,何必呢?!

  • 一个VARIANT引发的bug

    昨天一位同事跟领导讨论过程中提到,他认为微软Visual c++6.0编译器的最大优化有bug。因为以前没有使用最大优化的时候代码没有问题,把project setting改成最大优化以后就出现bug了。

    因为这个max speed optimization设置修改是我提出来的,所以感觉有必要看看原因到底是什么。因为我是不相信这种“微软编译器优化能导致接口调用出错”的说法,往往此类问题就出在我们自己的代码上面。

    在这个同事的电脑上重现了一下这个问题,然后回到我自己电脑上,果然也可以重现,这就有点意思了。

    在代码中加入断点,发现是某个接口函数调用失败,这也是那个同事所谓的“最大速度优化导致接口调用失败”说法的起源,不过我相信,这之前的代码一定已经有问题了。

    查看运行时的调用栈,在几个相关函数加入log信息,然后跑了一遍错误的情况(setting为最大优化),再跑一次结果正确的情况(setting为不优化)。然后使用winmerge比较两次log的文本,发现它们俩有不同的输入参数。

    为何同样的代码,只是修改了project setting就有不同的结果呢?难道真的是VC++出了问题被我这个同事抓到bug?

    我又跑了几次错误的情况,发现输入参数字符串的最后一部分是随机变化的(正常情况应该是不变的)。我记起一种可能性,当我们使用最大优化的时候,某些没有初始化的变量会有这种可能。

    从调用栈上溯,发现这个参数是来自另外一个接口,它的调用方式类似这样:

    CComVariant var;
    p->GetAddr(&var);
    CString str.Format("aaaa %d", var.uiVal);

    uiVal的定义是unsigned short,也就是一个2Bytes数据,可是查看GetAddr()这个函数,里面代码类似这样:

    HRESULT CAaaa::GetAddr(VARIANT* pVar)
    {      
         CComVariant var(m_addr); // 这里的m_addr的声明是BYTE m_addr;
         return var.Detach(pVar);
    }

    好像能抓到一些问题的原因了。我们查看VARIANT的定义可以发现它内部包含了一个union,里面有BYTE bVal;有unsigned short uiVal;有unsigned long ulVal;

    OK,原因很清楚了。我们在GetAddr中赋值的时候,只(使用)设置了VARIANT的联合中1个BYTE的数据,而我们在后面的代码里使用uiVal形式,实际上读取了2个BYTE,高位部分的数据是无效的。通过观察随机数的16进制格式也可以发现,正确数据应该为0x00,我们得到的错误结果都是0x5800,0x6300,0xCC00这样的。

    知道了问题的原因该如何修改呢?有两种办法,一个是在结果中也是使用bVal,这是最好的办法。另外,如果要使用比赋值时数据长度更宽的类型,一定要通过var.ChangeType(VT_UI2);这样的类型转换,保证高位部分数据有效。

    改完了这部分代码,程序在最大优化的设置下一样运行正确,根本就不是什么设置导致的问题,就是代码有问题。

    另外非常需要注意的是,这种bug跟优化不优化没有任何联系,而是跟CComVariant以及VARIANT这种复杂数据结构里面的union有关系,CComVariant好像没有对VARIANT的数值成员做清零操作,而是调用了VariantInit()。使用union的时候都需要注意类似的情况。

    我构造了一个test case可以在不设置最大优化的情况下一样出现类似问题。如果认为这是“最大速度优化导致接口调用失败”,只是看到了一种表象,真正的原因离着远呢。