Tchaikov’s Journal

September 12, 2006

C++表达式中的副作用

Filed under: C++

昨天,有同学给我看这样的程序:

int fun(int n)
{
    if(++n==5)
	return n++;
    else {
	return n*fun(n++);
    }
}

问,

fun(1)
得多少?咋一看,这不是求 5! 么?再看,
++n==5
已经让 n+=1 了,那就应该是这样的:

f(1)
 2  *  f(2)
        3  *  f(3)
               4  *  f(4)
                       5

答案应该是 120。作为一个悲观的 code warrior,我没有就此停止,我习惯性地把段代码贴进 Emacs,编译了一把,运行结果竟然是……300。我瘫坐在椅子里,听着外面淅淅沥沥的雨声。不是说“*”号是从左到右么?怎么会这样。再看了一眼 K&R 的 TCPL,第53页里表格说的是 associativity,不是 evaluate order。而且就在这一页,作者提到了

C, like most languages, does not specify the order in which the operands of an operator are evaluated.

函数参数的情形也一样。

C++标准 ISO/IEC 14882-2003 chap5.4 也提到

Except where noted, the order of evaluation of operands of individual operators and subexpressions of individual expressions, and the order in which side effects take place, is unspecified.

Unspecified behaviour 和 undefined behaviour 真是 C/C++ 程序员的噩梦。故事如果发生在不使用副作用的函数语言(比如 Scheme)那里,应该就没有这种问题了。

July 3, 2006

SWIG 和指针

Filed under: Python, C++

演示平台需要使用C语言实现的算法。算下来比较成熟的绑定工具就数 SWIGBoost.Python 了。SWIG 是一个通吃型的杀手程序,从 Python 到 Mzscheme 都照顾到了。wxWidgetsPython 绑定就是用的 SWIG。 Boost.Python 则专注于 C++ 和 Python 的互通。考虑到安装的便捷程度和易用性,我还是用了 SWIG。以前在 Windows 上花半个小时,用 jam 编译 Boost,太可怕了。而且这种高阶的 meta programming 不是所有人都能够接受的。嗯,还是 SWIG。SWIG 有直接可以在 Windows 安装的预编译版本,十分方便。用起来也简单。照着这里写一个 interface file:

module example
%include \"typemaps.i\"
int foo(int n, float* OUTPUT, float* OUTPUT);

swig 的文档说的非常明白了。在这里,n是输入参数,两个OUTPUT是用来作为输出的指针。example是 Python 看到的模块名。要编译生成一个 example 模块:

swig -python example.i # 生成 example_wrap.c 和 example.py
gcc -c -fPIC example.c # 编译 foo() 实现所在的源文件
gcc -c -fPIC example_wrap.c -I /usr/include/python2.3
ld -shared example.o example_wrap.o -o _example.so

这时候,example模块只包含了两个文件:example.py 和 _example.so。在 Python 里照常用就可以了。

In [1]: import example
In [2]: example.foo(1)
Out[3]: [4, 1.0, 2.0]

可以注意到,SWIG 把两个输出参数和返回值并成一个 list 返回了。不知道调用的代价会不会很高,先试试看吧。

June 3, 2006

gcov 和 gprof

Filed under: GNU/Linux, Programming, C++

gcov 和 gprof 都是 GNU 开发的程序。它们为程序员和测试工程师提供了很好的工具。
前者能够做 coverage 评测,这对 QA 来说会有很大的帮助。后者则能提供一个程序运行的 profile,帮助程序员快速找到程序的 hotspot。以前在看 CS APP 的时候曾用过 gprof。但是 gcov 是今天才知道的。原来像英特尔这样的大公司使用 GNU 工具的频度还是很高的。

March 15, 2006

calling convention 和 printf

Filed under: C++

前两天和同学谈到这个问题。同学说,Pascal convention 是无法实现类似printf 的变长参数的。虽然我也知道 C convention 和 Pascal convention 的意思和它们的区别,但是为什么后者无法实现 printf,这让我没有一点思路。
不管三七二十一,先补一下基础知识。Call Convention 是一套约定。它规定了调用方(caller)调用被调用的函数(callee)时,函数的参数应该以何种顺序传递给 callee,以及调用完成后,谁负责清理 stack frame。对了,为了简单起见,我们不讨论 C++ 的 inline 函数和成员函数。
通常使用的有三种 Calling Convention:C convention、Pascal convention 和stdcall convention。在 GCC 中,它们分别被记为:__cdecl、__pascal 和__stdcall。顾名思义,C convention 就是通常 C 语言所采用的约定。它要求参数传递的顺序为从右向左(即先把最右侧的参数压栈),同时由caller 负责清栈。Pascal convention 恰恰和 C convention 相反,参数从左至右,由 callee 负责清栈。
两个 Calling Convention 互有利弊。如果遵照 C convention,编译器每次看到有函数调用都会在调用处加入清理代码,每一处调用都会生成相应的二进制目标码,有100次调用的话就会有100份这样的清理代码(尽管有时调用的只是同一个函数)。这种效果如果累积的话,很可能会增加可执行文件的大小。倘若按照Pascal convention 行事,每个函数只在自己返回的地方加入清理代码,就算主程序调用了一百次子程序,可执行文件中的清理代码也只会有子程序的个数那么多份。如果大多调用的是相同的子程序,那么就会节省很多可执行文件的大小。在内存有限的情形,二进制文件的大小对程序的性能来说会是一个相当重要的因素。所以,Pascal convention 在很多情况能给程序稍稍减肥,从空间效率的角度来说,也提升了程序的性能。
但是,金无足赤,人无完人。Pascal convention 无法处理变长参数(variant length argument)的函数调用。在 C 里,printf 就是一个例子。若使用Pascal convention 的话,则要求 callee 在用毕输入参数后清栈(即调整%esp)。但是问题在于编译器在编译 printf 的时候根本就不知道它的参数到底有多少个!噢,你可能会说,“编译的时候源代码明明白白写在那里,编译器怎么会不知道呢?”。诚然,编译器在编译 caller 的代码时,它是清楚我们塞给printf 多少个参数的。但是 printf 作为 libc 的一员,一般是预编译好的库(precompiled library),在链接时才成为可执行文件的一部分。它,编译器,在那时(预编译时)是没有办法知道如何设置 %esp 的。这些信息只有在编译 caller 的源程序时,编译器才能知晓。
还有一个不是很充足的理由选择 C convention。假设我们使用汇编撰写子程序,call convention 使用 C。如果子程序的参数列表要添加一个参数(绝大多数情况是在后面添加)。为了访问这个参数,callee 只要用类似 (12)%ebp 的引用就可以了,对前面其它参数的引用不用更动。如果选了 Pascal convention 就麻烦了。对所有参数的引用都要往高地址移动 sizeof(last_param) 字节。另外,即使使用 C convention。虽然约定要求 caller 清栈,编译器也有自由不做这项工作,它可以重复使用参数在栈上占用的空间。
stdcall 是 win32 API 使用的 calling convention,它糅合了 C 和 Pascal两者的长处,参数从右到左传递,callee 负责清栈。不过因此,在 stdcall 下同样无法使用变长参数列表。由于访问寄存器比访问内存要快达数十倍,因此各家编译器也提供了(部分)使用寄存器传递参数的机制。在 MSVC 中,称之为 fastcall。GCC 则通过
“-mregparm=NNN”的参数来使用这个机制。用这种办法传递参数,对寄存器比较多的体系架构会有较大的性能改善。


http://gcc.gnu.org/onlinedocs/gnat_ugn_unw/Calling-Conventions.html
http://my.execpc.com/~geezer/osd/libc/index.htm#call

Get free blog up and running in minutes with Blogsome
Theme designed by Jay of onefinejay.com