Android with root Git for version control Lircd with Raspberry Pi for IR receiver and sender Tips for Windows Depolying your own password management tool -- KeeWeb Depoly your flask app into Heroku Fix shit IE code manually ISBN to Book Category by Scraping DangDang A Generic Makefile for C/C++ Program Configure Raspberry pi Remove watermark with PyPDF2 tips for docker Anaconda+TensorFlow+CUDA Snippets Configure Remote Mathematica Kernel Build your own ngrok server Access Array SSL VPN 使用Rstudio制作html5幻灯片 tips for Mac OS X system Tips for ipython notebook 配置Ubuntu server + Openbox (Obuntu) tips for Vimperator tips for Vim 安装CUDA My First Jekyll Blog rsync常见选项 在Linux中读取Ipod touch的文件 tip for texmacs 在VPS上建站的一些tip Gnuplot绘图札记 Samba系统和autofs自动挂载 Linux中alsamixer声卡无法录音 搭建自己的RSS订阅器——Tiny Tiny RSS Grub2引导安装Ubuntu awk tips 将Ubuntu系统装入U盘 The Great Rtorrent 编译GCC 再这样剁手!!!该死的libgd 使用ulimit进行资源限制 使用SSH代理上IPV6 使用RCurl抓取网页数据 修复Ubuntu Grub记 openbox中的文件关联 在Ubuntu 12.04下编译qtiplot 处理BCM4312网卡驱动纪实 配置我的Ubuntu Server记 Cygwin杂记 Linux 使普通用户具有以超级权限执行脚本 让firefox自定义地处理文件类型 WordPress优秀主题及插件 在phpcloud上搭建wordpress UBUNTU下用pptpd做VPN server ubuntu升级内核过后的一些问题 安装telnet服务 kubuntu札记 64位kubuntu札记 统计软件R Virtualbox stardict星际译王 Ubuntu重装windows系统后的grub引导修复 SSH服务及花生壳域名解析 采用cbp2make工具由code::blocks工程创建makefile文件 UBUNTU 札记

一个天真的bug

2013年06月27日

本人写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)实在太巧妙了。