本人写C/C++的,虽非科班出生,但是总是特别有兴趣,于是时常捣鼓。
新写的代码出了个很是奇怪的bug,基础不牢,犯了一个想当然的错误,于是乎查了好几天才找到原因。因为想要把自己写过的一些代码凑起来,弄一个自己的代码库。
因为想要使代码更具有可移植性,所以当然对一些基本的变量类型要做typedef,而typedef的类型来自于Makefile中给编译器的-D选项。基本代码如下:
#if ElemType == float
typedef float Doub;
#elif ElemType == double
typedef double Doub;
#endif
于是乎,无论我把ElemType宏定义为float还是double,Doub的类型总为float。也就是说条件ElemType == float
总成立。
这就奇怪了,我一直以为是我在这个文件中引用了其他文件,而那个文件里面宏定义了ElemType为float从而覆盖了我的宏定义。于是乎我把所有的源代码翻出来找还有哪些地方做了ElemType的宏定义,肯定是找不到的撒。然后我想,是不是ElemType没有定义过,编译器会认为此条件永远成立,于是用#ifdef
检查,它已经被定义了啊!我开始有点抓狂了!
偶然间,我发现把它改成Elemtype
还是有Doub总为float型,然后顺藤摸瓜发现随便改成啥Doub都为float型,接着把两条typedef换一下,Doub就永远变成double型了。原来,问题的原因在于#if ElemType == float
总是满足条件!为什么会这么奇葩,赶紧翻翻条件编译,原来#if
后面只能跟常量,编译器根本不会判断条件表达式!只要不为0就永远为真!
我真是太天真了,想当然地就写出了这样的bug。我想,要是有个稍微懂行点的人一提点马上就明白了。唉,旁边师兄弟都不知道条件编译是啥玩意儿,写的都是大一的C水平的代码。做科研的有一颗码农蠢蠢欲动的心,真心伤不起啊!
其实把代码改成这样就成了
#ifndef ElemType
typedef double Doub; // default floating type
#else
typedef ElemType Doub;
#endif
=============================================================
好吧,谈到宏定义的问题,在这里多说一句。虽然宏定义是一个很好的简化代码的方式,但是绝对是杀敌一千,自损八百,因为它把编译器拒之门外,所有的工作都是在编译器进入之前就完成了。于是,一旦出现问题就会有一些非常奇怪的现象或错误。
“Numerical Recipes” 是一本数值计算的经典教材,里面提供了大量实用的算法。它提供一个C++的版本,但是,代码质量确实不敢恭维。它并不适合当做一个库来实用,仅适合拷贝一些片段到自己的代码中去。
于是,我希望把它改造为一个库,可以让我随意的实用。我给它添加了防止重复包含的宏,添加了无名名字空间来防止出现函数重定义,有空了再把文件包含关系梳理一下。这当中我遇到一个很奇怪的问题,我发现当先包含头文件nr3.h
(包含NR代码的类型定义)再包含omp.h
(openmp要用的)或cassert
(断言)的时候会出现问题,说是表达式后差一个分号或什么的。后面两个是标准库包含的,我不敢怀疑它有问题。问题只能是出在nr3.h
上,于是我分段注释掉它的代码来观察到底是哪儿出了问题。原来问题出在一个宏定义上,它定义了一个宏叫throw(message)
用来实现错误输出,然而并没有在文件结束后去掉这个宏。于是,当omp.h
中出现需要标准的throw
时就出现问题了。解决办法可以是在文件尾把它undef
掉,或者换一个不常用的宏名。
此外,介绍一个小trick,用我改造的宏定义做例子,用来将一个含有多个语句的宏包装为一个函数
#define throw(message) \
do{\
printf("ERROR: %s\n in file %s at line %d\n", message,__FILE__,__LINE__);\
throw(1);\
}while(0)
宏是不能递归展开的,这里用while(0)
实在太巧妙了。