标签: c++

  • C语言表达式计算顺序的一个小问题

    浏览Q.yuhen的博客这篇文章 http://www.rainsts.com/article.asp?id=959 发现一个小问题,估计有类似想法的同学也有,所以记录一下。

    问题在于这句话:

    “很显然,依据 cdecl 规则,"printf(…, test(2), test(1))" 中的 printf 函数参数依次从右向左 "入栈"(暂且用这个说法)。因此 test(1) 被先调用,然后才是 test(2),上面的汇编代码也说明了这点。”

    尽管事实是这样的,但这是一个有问题的说法。

    cdecl的入栈顺序是没错的,这个入栈顺序是针对每个逗号分隔的表达式结果而言。也就是说对于每个结果一定是这样的顺序。但是表达式计算顺序(或者说每个逗号分割的函数调用)其实是没有规定的。这在K&R影印版第二版52页最末一段说的非常清楚,下面的f和g不一定谁先调用:

    C, like most languages, does not specify the order in which the operands of an operator are evaluated. (exceptions are && || ?: and ",") for example x = f() + g();

    另外在63页开始:

    The commas that separate function arguments, variables in declarations, etc., are NOT comma operators, and do NOT guarantee left to right evaluation.

    这也就是说逗号分隔的函数参数与逗号操作符是不一样的,不保证从左至右的计算顺序(当然也没有保证从右至左)。

    注意这个evaluation,其实就是对test(2), test(1)的调用。如果这些调用有边界效应,在不同编译器、操作系统上可能会得到不同的结果。另外类似的问题有对在参数列表中,同一个变量调用++两次以上。

    如果要保证求值顺序(注意不是求值结果的入栈顺序),只能用临时变量保存调用结果,或使用逗号操作,&& || :?这样可以保证求值顺序的操作才行。

    其实我在前面这篇博客已经提到了这个问题,只是看到这个提法再重申一次。“C语言中的表达式求值问题http://sunxiunan.com/?p=1684

    参考资料:

    http://www.andromeda.com/people/ddyer/topten.html 参考第七条

    C陷阱与缺陷 3.7求值顺序

  • 能打印出自己的代码

    任务:写一段C语言程序,打印出自己整个代码,不能差一分一毫。

    这个任务在计算机编程中有个术语叫做:Quine,维基百科上有专门的条目介绍。

    http://en.wikipedia.org/wiki/Quine_%28computing%29

    quine代码好像没有太大作用,但是我们可以将其想象成一种可以自我繁殖的生物,每次运行就产生一个同样的实体,然后一个个这样繁殖下去,哇塞,这就是恐怖片了!

    搜索了一下,基于C语言(使用VC2010编译必须设置language为C才可以)主要有以下几种比较简洁的实现:

    main(a){a="main(a){a=%c%s%c;printf(a,34,a,34);}";printf(a,34,a,34);}

    这算是第一种模式,关键在于%c%s%c这个打印格式,然后输入参数中多半有34或者0x22(也就是双引号)或者引用到字符串数组中双引号的位置。

    另外一种是通过宏定义实现的:

    #define T(a) main(){printf(a,#a);}
    T("#define T(a) main(){printf(a,#a);}\nT(%s)")

    宏定义的实现消除了对双引号的使用,格式更为灵活一些,不需要是abab这种模式了。

    关于quine一个非常详细的论文,介绍了深层理论、如何写quine代码等等,可以看看。

    http://www.madore.org/~david/computers/quine.html

    另外可以参考:

    http://www.c4swimmers.esmartguy.com/selfcodeprint.htm

    http://www.c2.com/cgi/wiki?QuineProgram

  • 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》

  • 学习C语言的免费电子书

    学习C语言,其实只需要一本书就是《K&R》。但是,经典有时候写的不是那么容易理解,多看看其他书籍互相参考一下也非常有必要。

    在这里介绍几本免费的C语言学习电子书,不免费的那就很多,至于如何找我就不说了,哈哈。

    http://publications.gbdirect.co.uk/c_book/

    这一本写的非常详细,你可以把它看成是类似谭浩强版的教科书。

    http://www.knosof.co.uk/cbook/cbook.html

    这一本云风曾经推荐过,相当深入的介绍了C99标准,深入细节时候需要读读。

    http://wangcong.org/blog/?page_id=196

    作者王聪,也是相当hardcore,从放出来的两章看,包含了相当多的信息量。

    《C语言深度解剖》

    这本可以在百度文库或google搜到,可以读读,有些参考性。

    《C标准和实现》

    作者姚新颜,他的《深度探索C、C++》算是当年比较有深度的书籍,可惜已经绝版了。这本书也可以在百度文库搜到。这本书也比较值得读。

  • 勿用屠龙来杀猪-论如何正确整合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中说的那样:编程弄得这么纠结,何必呢?!