C语言中的表达式求值问题

在细读《C programming Language 2nd》(K&R)到53页的时候,看到作者举了这样一个例子:

a[i] = i++;

如果你知道这个表达式有什么问题,就不需要继续看下去了,下面内容对你而言有些浅显。

如果你也像我一样,觉得这个很容易理解啊,i++这个表达式就是先取i的值返回,然后对i自加。a[i]就是i的值,然后i自加1。

这其实也是C语言陷阱之一,在K&R中反复强调(page52以及page202),函数参数也好、某个操作符中的操作数也罢,表达式求值次序是不一定的,每个特定机器、操作系统、编译器都不一样。(特例是&&,||,?:以及逗号操作符,它们会确定表达式求值顺序的)

还有类似的例子如下:

f() + (g() * h())

或者 int i = 7;  printf("%d\n", i++ * i++);

在我们一开始提出的问题中,a[i]取下标操作与i++自增的运算顺序是不一定的。这就是一种不确定性。

在第一个表达式中,可以确定的是这些内容:g和h函数的结果会先做乘法运算,然后与f函数的结果做加法运算,但是f,g,h谁先被调用,谁后被调用,这是不一定的,C语言标准没有对此作规定。

第二个表达式中,第一个自增操作和第二个自增操作以及乘法操作的顺序是不一定的,所以结果根本无法确定,即使我们给i++都包裹上括号 (i++) * (i++)也是一样的。括号并不会得到确定的操作数计算顺序,括号只能保证操作数的值(就是表达式或者函数求值的结果)相互计算的顺序。

K&R提供了几个建议,首先是函数调用嵌套赋值语句(或者可能改变参数值的操作)或者自增操作,都会有"side effect”,我们应该确保这种边际效应不会影响程序运行结果,如果某个表达式对同一个变量同时修改两次,那么一定要非常注意这是不是你想得到的结果。

如果不知道特定机器上实现如何,就不要依赖表达式计算顺序;即使知道了实现方式,这种依赖也不是一种好的编程方式。比如f、g、h函数计算,可以用赋值给临时变量来决定需要的顺序,对于print(i++)这个表达式也是如此,在printf之外先计算i的值。用K&R第一版的话来说就是if you don’t know how they are done on various machines, that innocence may help to protect you.

参考资料:

http://www.eskimo.com/~scs/cclass/notes/sx7c.html

《C Programming Language second edition》

Tags :

Visual C++ Project uses asm files

As you know, C++ project could embed asm in function. But in some cases, we want to use asm file in our project. How to do?

1, open the project, and insert existing xxx.asm file into project. Or you could generate new file and name it as "xxx.asm".

image

2, in C/C++ source code, if you want to use the function, just add declaration before using it. Please note: in C++ code, extern "C" should be added (like above image).

3, edit and save the asm function.

image

4, In asm1.asm custom project setting, add following in command line:

ml /c /Cx /coff -Zi "-Fl$(InputDir)\$(InputName).lst" "$(InputPath)"

Add following in outputs:

$(InputName).obj

image

Try to compile the asm1.asm, the asm1.lst and asm1.obj files should appear under project folder.

5, Build the solution. All done.

If there are some errors, you could try to copy the asm1.obj to "Debug" or "release" folder, if it could fix the issue, you could modify the path of command line and outputs setting of asm1.asm.

Tags :

Lua Unicode (Wiki翻译)

http://lua-users.org/wiki/LuaUnicode

这里尝试着回答一下LuaFaq问题:

我可以使用Unicode字符串么?或者,Lua支持Unicode么?

简言之,可以,不支持。Lua只有极为精简的支持和足够的辅助功能,就没有其它的了。Unicode是一个庞大而复杂的标准,像是“Lua是否支持Unicode”是比较模糊地问题。

一些问题如下:

我可以存取Unicode字符串么?

我的Lua程序能用Unicode写么?

我可以比较Unicode字符串是否相同么?

字符串排序。

模式匹配。

我可以取得一个Unicode字符串的长度么?

支持括号型匹配,双向打印,随意的字符串组合以及各种高品质排版技术中产生的问题。

Lua字符串使用8-bit,所以简单的应用是支持的(比如存取操作)。但对于其他复杂操作没有内建支持。想知道更多故事,往下看。

Unicode字符串和Lua字符串

一个Lua字符串是一个至少8bit值的任意组合;可以直接映射到C编译器中的char类型(可能会比8bits更宽,但是一定会保证有8bits)。Lua没有保留任意字符,包括NUL。这意味着你可以成功地在Lua中存储UTF-8字符串。

注意UTF-8只是存储Unicode字符串的一种可能。还有其他编码模式,包括UTF-16和UTF-32以及它们的大端、小端变体。无论如何,所有这些都仅仅是8位字节的组合,可以毫无问题的存储到Lua字符串中。

在Lua中字符串的输入输出(使用IO库)使用C的stdio库,ANSI C不需要stdio库来处理任意8位字节顺序,除非是二进制模式。更进一步说,在非二进制模式,一些8位字节顺序可以转换到其他字符(为了处理不同平台上的换行符)。

这可能影响到处理非二进制的而且不是UTF-8格式的Unicode字符串文件输入输出。UTF-8字符串可能还是安全的,因为UTF-8不使用控制字符如\r \n作为多字节解析的一部分。而且这也不是一定的。如果你需要个确定答案,你应该使用二进制模式输入输出(binary-mode)。(如果你使用二进制模式,行结尾符将不会被转换)。

Unix文件输入输出很长时间里都是8字节整的。如果你不关心可移植性,仅仅在Unix或者类Unix系统上工作,可以完全不用担心前面提到的。

如果你使用Unicode是限制在传送字符串到外部支持Unicode的程序员,应该是不用担心的。例如,你应该可以从数据库取得一个Unicode字符串传递到一个识别Unicode的图形库中。但是也看看下面关于模式匹配和字符串等同比较的章节。

Unicode Lua 程序

字面型Unicode字符串可以出现在你的Lua程序组,UTF-8编码的字符串可以直接用8位字符形式或者使用\ddd语法(注意ddd是一个十进制数)。无论如何没有编码多字节顺序的功能(比如\U+20B4);你可能不得不手动把它们编码成UTF-8格式或者以正确的大端或小端顺序塞进单独的8位字节数值中(针对UTF-16或者UTF-32)。

如果你不是使用某个char比8位更长字节的操作系统,就不能用任意的Unicode字符作为Lua标示符(变量名或者类似)。你可能使用ANSI以外的8位字符,Lua使用C函数isalpha和isalnum来辨别正确的可作为标示符的字符,所以这与当前locale配置有关。实话讲,使用ANSI以外的的字符做标示符不是一个好主意,因为你的程序可能没法在标准C locale下编译。

比较和排序

Lua字符串比较(使用==操作符)是通过逐个字节比较完成。这意味着==仅仅可以用来比较特殊的等同性,就是Unicode字符已经被正常化为正常可能之一(http://www.unicode.org/faq/normalization.html)。标准Lua库不提供任何正常化Unicode字符串的能力。相应的,非正常化的Unicode字符串也不能被可靠的用作table的key。

如果你想用Unicode类型的字符串等同比较,或者使用Unicode作为table的key,你不能保证你的字符串一定被正常化,然后你不得不自己写或者寻找一个正常化函数,这是一个很重要的练习!

Lua字符串比较操作符(<和<=)使用C语言函数strcoll,这个函数依赖于locale配置。这意味着两个字符串随着locale不同会得到不同的结果。例如,字符串使用西班牙语传统排序与威尔士语排序结果是不一样的。

也许你的操作系统实现了你想要的排序算法,这时候你可以使用它们,否则你可能要自己写函数来对Unicode字符串排序。这是一个更重要的练习。

UTF-8被设计成一个8位字节序的简单逐位比较可以得到同样的结果。UTF-32也是正确的,但是我不知道什么系统使用这个编码方式。不幸的是,简单的逐位比较没有被用作任何语言的排列顺序。

模式匹配

Lua的模式匹配的工作方式也是逐个字节的。通常情况下,这对于Unicode的模式匹配没有用处。即使有时候程序以你预想的方式工作了。例如,"%u"将不能匹配所有Unicode大写字符。你能匹配正常化后的Unicode字符串中的单个Unicode字符,但是你可能也担心字符串顺序。如果没有后续合并字符串,"a"将匹配一个UTF-8字符中的字符a。在UTF-16LE中你能匹配"a%z"。(记住你不能使用\0在Lua模式中)

长度和字符串索引

如果你想知道一个Unicode字符的长度,根据情况不同你会得到不同答案。

如果你想知道一个字符串占据多少个字节,比如你想拷贝到某个buffer中,现有的string.len函数可以工作。

你可能想知道Unicode字符串有多少字符。根据被使用的编码,一个单一的Unicode字符占用4个字节。只有UTF-32LE和UTF-32BE是常数长度编码(每个字符四字节);UTF-32是一个常数长度编码但是第一个元素应该是”字节序标记”。这不应该被看做字符(UTF-32和变体是最新版本Unicode4.0的一部分)。

一些UTF-16的实现确保所有字符都是两字节长,但是从Unicode3.0版本以后就不是这样。

很高兴的是UTF-8被设计成很容易计算Unicode字符串中符号个数;仅仅计算0×00到0x7f或者0xC2到0xF4范围内的八位字节数字个数。这些是UTF-8字符码的开始。0xC0, 0xC1, 0xF5到0xFF不能用于构成UTF-8字节序;八位字节在0×80到0xBF可以仅仅出现在第二个或者多位编码后面部分。记住你不能使用\0在Lua模式中。

例如,你想使用接下来的代码计算UTF-8字符个数,(对于计算某些非法字符,这是不正确的)

local _, count = string.gsub(unicode_string, "[^\128-\193]", "")

如果你想知道一个Unicode字符串占据多少打印列(当你使用固定宽度字体),又有不同的回答了。这是因为某些Unicode字符没有一个打印宽度,而另外一些有两倍的打印宽度。合并字符被用于给其他字符加重音,通常打印时不占据额外空间。

所以不同情况下至少有三种不同的长度可能。Lua提供了一种string.len,而其它你需要自己写函数。

有一个类似的问题就是通过位置索引字符串某个字符。string.sub(s, -3)会返回最后三个字节,但未必会返回最后三个字符。

你可能使用接下来代码段枚举UTF-8字节序:


 for uchar in string.gfind(ustring, "([%z\1-\127\194-\244][\128-\191]*)") 

  do -- something 

end

More sophisticated issues

As you might have guessed by now, Lua provides no support for things like bidirectional printing or the proper formatting of Thai accents. Normally such things will be taken care of by a graphics or typography library. It would of course be possible to interface to such a library that did these things if you had access to one.

There is a little string-like package [slnunicode] with upper/lower, len/sub and pattern matching for UTF-8.

See ValidateUnicodeString for a smaller library.

[ICU4Lua] is a Lua binding to ICU (International Components for Unicode [1]), an open-source library originally developed by IBM.

See UnicodeIdentifers for platform independent Unicode Lua programs.

Tags : ,

static link luasocket into lua with VC2010 under windows

1, download lua source code from luabinary project (it includes VC2008 project file). Unzip it to one folder.

2, download luasocket latest code. Unzip it to the folder we create in step1.

3, open lua solution file with vc2010 (I think VC2008 should be OK too). try to build it. Should be successful.

4, add all *.h *.c file of luasocket into the project. Remove following from project: usocket.h usocket.c unix.h unix.c. They are used by unix OS.

5, Modify the project setting.

preprocessor definitions look like: WIN32;_WIN32LUASOCKET_EXPORTS;LUASOCKET_DEBUG;LUASOCKET_API=__declspec(dllexport);NDEBUG;_CONSOLE;%(PreprocessorDefinitions)

and add “Ws2_32.lib;”into library.

6, insert

  {"mime", luaopen_mime_core},  {"socket", luaopen_socket_core},

into luaL_Reg lualibs[] of linit.c file.

add two extern function declaration.

int luaopen_mime_core (lua_State *L);int luaopen_socket_core (lua_State *L);

7, build.

8, change socket.lua under luasocket from require("socket.core") to require("socket") , change mime.lua, from require("mime.core") to require("mime")

9, in your test script, for example the testclnt.lua in luasocket/example, add this line

loadfile("socket.lua")()

before socket = require("socket");

10, run the test. Everything should be OK now.

http://comments.gmane.org/gmane.comp.lang.lua.general/67646

 


 

软件开发中单元测试unit testing

尽管现在已经有了大量的软件开发方法论及辅助工具帮助开发团队提高软件质量,防止、检测bug,但是一些很简单实用的手段依然是提升软件质量必须的手段,比如单元测试,比如Code review。

单元测试是一种很基本的软件质量保证方法,随着敏捷开发、持续集成的流行,一个高质量软件,如果没有单元测试是无法想象的。比如Java,C#都有非常成熟的单元测试框架,Google也开源了他们的C++单元测试框架以及mock框架。

这篇文章将探讨几个方面的问题,什么是单元测试?单元测试为何如此重要?什么时候进行单元测试?如何进行单元测试?单元测试要注意避免什么问题?

什么是单元测试?其实单元测试的本质就是assert,比如c语言就有内置的assert函数,在不满足某个条件的时候返回错误码,而MFC内置的ASSERT和VERIFY就更强大。如果你已经使用assert函数,其实你已经进入了单元测试的初级阶段,稍微学习一下就可以掌握单元测试方法了。对于C++编程,可以使用google的googletest框架做单元测试,这种unittest框架的好处是集成了单元测试常见的需求,避免了重复开发。

常见的开发任务大致为两种:维护legacy code或者偶尔加入一些小的feature,另外就是搭建一个新软件。这两种任务都不可避免的要反复修改某个feature代码,或者根据测试人员或用户的反馈修改bug。我们如何保证修改以后的代码没有引入新的问题?如果你说你用人品担保,那我服了。对于一个正规流程来说,应该有一种正式的手段来确保修改一个bug没有引入两个三个新的bug,或没有导致以前正确的功能出错,这就是单元测试的重要性,有了足够的单元测试,你就可以理直气壮的说新代码没有问题。

单元测试另一个重要性是帮助你理清设计。对于反对单元测试的一个常见借口是,我们的应用太复杂了,没法写单元测试。不是应用复杂,其实是软件设计有问题,导致没法测试,可测试性也是软件很重要的一个本质特性。如果设计中保证了某个函数某个接口只完成单一责任,没有过多的耦合依赖,那么测试其实是很简单的事情。

单元测试另一个优点是可以集成到持续集成过程中,或者通过脚本简单快速反复运行,不需要手动干预,这对于提高开发效率而言非常重要。

什么时候我们应该写单元测试?是软件代码都写完了,实际运行的时候再写么?单元测试其实应该在设计阶段就开始写,单元测试完成以后再写实际功能部分代码。设计应该是基于接口设计,单元测试也应该基于接口测试,另外针对某些复杂的内部逻辑,也应该有比较多的单元测试保证覆盖率。对于某些核心部分,单元测试的代码甚至应该超过实际代码。而且要注意的是,应该将单元测试部分的代码与工作代码等同看待,一样要做版本管理放入ClearCase或者SVN,而且单元测试部分的代码也要review,保证测试代码也是正确的。个人感觉单元测试(包括部分集成测试代码)在整个代码实现部分要占30%到40%的任务量,这样才比较正常。引入单元测试会在前期导致一些延迟,这是无法避免的,相应的会大大减少后期的维护工作,这是我自己的亲身体会。

那么该如何写单元测试呢?单元测试能不能测GUI点击输入?首先要明确的一点是,单元测试不会替代其它测试手段,单元测试只是白盒测试的一种。单元测试是由开发者编写测试,保证正确完成某个逻辑功能的一种测试方法。而且单元测试不应该涉及到其它外部依赖或者其它的模块,比如GUI点击、网络通讯、数据库通讯或者需要安装某个第三方软件等等,这就需要开发者做好设计,尽量把可能有耦合依赖的部分提取隔离,在集成测试或者其它测试的时候再检查。

我们用一个简单的例子解释一下。某个GUI界面,当按下一个按钮,它要变成另一种颜色,功能完成以后恢复原状,或者是一个控件允许用户输入,输入完成以后校验,根据校验的结果保存或者提醒用户出现问题。这些都是比较常见的流程。这些流程显然不是原子的(atom),涉及到model、View、control各个方面,某些程序员往往在CXXXDialog这样的类里面实现所有这些功能,还感觉封装的非常好,“这不是面向对象封装了么?我把它们都封装到类里面了啊?!”

我们就拿输入校验来说明一下如何分解这个MVC过程。第一步,用户输入,点击OK。这部分显然是View和Control方面的,这部分可由tester方面做检查测试,开发人员需要保证功能实现完整,简单运行正确即可。第二步,检查输入数据,进行逻辑运算。这部分显然是比较复杂的逻辑,涉及到Model和Control,一般不涉及到界面显示,输入部分就是数据,输出部分就是检查的结果。显然,这部分应该做单元测试。第三步,如果出错,反馈给用户出错的结果。这一部分基本上也是以界面显示为主,是需要tester测试的部分。第四步,数据正确,保存用户的输入。这部分涉及到数据运算,也是可以进行单元测试的,涉及到数据库的部分,可能需要做集成测试。

从前面的分析可以看出,涉及到GUI界面的测试一样可以有单元测试,需要开发人员做更多的工作,抽象逻辑计算部分代码。这不容易实现,但是值得去做。

当进入后期开发阶段,当用户或者测试人员发现问题,开发者就应该把这些问题转化为测试用例,这样既保证了修改后的代码没有导致其他bug重新出现,也是对代码逻辑的一种很好的覆盖。针对某些需要依赖其他模块的功能,我们可以mock接口,也可以编写实现模块间的集成测试。另外,开发者还可以进一步定制自己需要的单元测试框架功能,比如我就针对现在工作的项目,设计了一个灵活的添加测试用例的方案,测试用例用类似ini或者xml格式编写,单元测试程序读取测试用例进行测试。这样引入一个新的测试用例就非常容易方便,不需要修改编译代码。当然这种设计也是针对我们项目的输出主要为COM接口而定制的,更像是一种集成测试。

前面就是一些泛泛之谈,没有涉及到实际技术方法,只是鼓吹了单元测试的优点。希望各位程序员或技术领导能更加重视单元测试,在工作中使用单元测试,让它真正成为日常工作的工具来保证软件的高质量开发。

Tags :

appengine建立自己的web service抓取新浪图片

twitter一位朋友提到想抓取新浪娱乐http://slide.ent.sina.com.cn的高清图片,省得一张张去翻看。

首先分析这个需求可不可以完成,打开网页源代码,哇,原来所有图片地址在第一页就能看到,而且图片地址出现的很有规律,这就很容易搞定了。

image

前一阵看过一个Python的类库叫做beautifulsoap,它可以完成解析html的工作,加上appengine的支持,简单到爆。

1,先下载python以及google appengine,如果没有appengine账号申请一个即可,如果不清楚google一下都能找到。

2,下载beautifulsoap,解压BeautifulSoup.py到你项目目录里面就可以了。

3,在app.yaml里面加入,这样就把soap这个网址定向到img.ly了:

- url: /soap/.*
        script: img.py
        secure: optional

4,建立img.py,main函数部分使用google的webapp框架,关联到SoapLink类:

image

5,建立SoapLinks类,可以参考appengine的文档:

image

其中要注意的是beautifulsoap解析出来的是一个Tag对象(而不是String),所以要用ccontents[0]来获得string,这个花了我一些时间。

然后就可以部署服务了,具体可参考appengine或者留言问我。

demo:http://ucfcuk.appspot.com/soaplinks/ 加上你要访问的新浪娱乐地址

如:http://slide.ent.sina.com.cn/slide_4_703_11949.html

image

代码:http://gist.github.com/487246

Tags :