char buf[4]=""; // {0, 0, 0, 0}, no need for memset(buf, 0, 4)
char buf2[4]="pzy"; // {'p', 'z', 'y', 0}
char buf3[3]="pzy"; // compile error: array bounds overflow
char buf4[4]; // unpredictable value, for example, {-52, -52, -52, -52}

2008-01-21

DEBUG小记

昨天被一个常见的C++指针错误折腾了近一个小时,衰得很,现在记一下情况,希望对以后有些许提示作用。

那个C++函数是以前写的,有个指针参数,我不知怎么的记成它是可选的了,在C#里调用时就传了null过去。C#显示这样的运行时错误:尝试读取或写入受保护的内存,这通常指示其他内存已损坏。如果整个都是在C++里,我会马上想到非法指针,可昨天我花半个多小时检查测试C#向C++传递数组的过程是否正确,一会尝试传递指针,一会尝试传递元素引用,一会尝试传递数组本身。

最后才怀疑那个C++函数。其实在昨天之前那一直是个MFC EXE,我把项目属性改成输出dll,再定义个全局函数封装原先的核心函数,把前者用DEF导出。虽然文件臃肿了些,但还是堪用的。那个指针参数确实应该是可选的,当初好像是觉得先在EXE里测试通过再说,不曾想事情一搁个把月,昨天我又以为当初已经把这些细节处理好了。

DEBUG近一个小时,最后发现轻轻地加个if (NULL != ...)就解决了,也怨不得别人,责任全在自己,真的连狠都没处发-_-!!!

后记:容易的是修改错误,难的是找到错误。

我在读DXF文件时,声明了一个double [6]数组,未初始化。debug下工作得很好,release时数组的值有异常。在代码中插入弹出消息框的语句又正常了,继尔发现暂时正常是因为在数组后面定义了一个CString变量。注释掉CString变量,代之以int型变量却不行。不要任何冗余的局部变量,给数组一个初始化表达式,结果也正常了。不使用初始化表达式,而是在定义数组后访问(读或写皆可)其中任何一个元素,也能保证最终的运行结果是正确的。可此中原因还是未解,我的编程环境是VC 2005。

今天碰到的一个问题是:如何判断一个动态数组是否已经分配(ReDim)。

对于声明后尚未ReDim的数组,使用ubound函数会引发错误。我想知道除了On Error语句外还有没有更好的办法来检测,在CSDN论坛上找到yinweihong给出的解答:使用oleaut32.dll库的SafeArrayGetDim函数,它返回安全数组的维数。

当前问题是勉强解决了,但因这个问题发现了一大片未知的领域:VB与COM的关系。之前大致了解VB的对象(包括内置对象和类模块)实际上都是COM对象,现在认识到,它们的关系比我想象得要密切得多。

这就是VB,我不把它当回事,只是随便用用时,觉得它连简单都算不上,说它“简易”更合适些。比如经常发现它的语言设施缺胳膊少腿,再比如它的文档看起来远没有C#的周密严谨。而当我稍微留意一些问题时,发现它“隐藏得极深”。对于C#,我敢说其语言本身我是精通了,对.Net Framework也掌握得很好;对C++,我只能算熟悉(持续学习中,基本没有实践,所以熟悉但不熟练),但至少了解它的难度;而对于VB,我则完全没有估计出深浅。就这个意义来说,我其实是个VB门外汉。

刚才说问题解决得勉强,是因为我还没有完全理解SafeArrayGetDim函数的结果。现在可以肯定的是,对于未定义的数组,它返回0,但对于ReDim后的数组,它的返回值匪夷所思,是类似12048,35232这样的数字。我一开始怀疑这个现象会不会源自VB与C对整型数据的解释不同,因为VB中没有无符号整型。可当我看到同一条件不同次运行会得到不同的值,傻眼了。

时间有限,暂时不打算纠缠这个问题,有机会的话会看看AdamBear写的《vb真是想不到》系列。

此篇文章为《C++多维数组与指针》的继续讨论,原文章地址为:http://pzy84.blogbus.com/logs/10702889.html

一般都是这么认为的,我也是这样理解的。

我曾经有过的疑惑是:既然数组只是指针(而不存在数组这个对象),那么

1. 为什么sizeof 运算符作用于数组和指针上会得到不同的结果(显然它能区分数组和指针)?
2. delete[] 运算是怎么知道一个指针所指向的数组的长度的?

现在的理解是这样的:C++在编译时才有数组这个概念,运行时没有。编译时,编译器理解数组对象,因而能获取数组有、而指针没有的信息(比如用 sizeof 运算符计算数组的长度);运行时,数组退化为指针。

这个理解能够成立的必要条件是,运行时所有针对数组的操作,都可以施加到指针上,而且效果应该是完全相同的。那么事实上呢?

MSDN提到:

When an identifier of an array type appears in an expression other than sizeof, address-of (&), or initialization of a reference, it is converted to a pointer to the first array element.

如果数组在运行时真的退化成了指针,那么以上提到的所有需要数组对象的表达式都必须在编译时计算——的确如此

至于 delete[] 运算符,则是利用了系统在分配内存时保存的长度信息,这个信息通常是数组数据的前面(指针向前偏移)。

总之,运行时根本没有数组长度信息可供使用(因而也不存在下标边界检查)。对于在栈上分配的数组,系统在运行不再需要知道它的长度信息,所以把这个信息给扔了。如果它是你定义的,那么你已经知道它的长度,如果它是别人作为参数传给你的,你得要求他把长度也告诉你。对于在堆上分配的数组,系统会在某个地方记录它的长度,以便于delete[]。