分类 技术志 下的文章

C++咬文嚼字-'Hijack const'

晚上无意翻看Bjarne Stroustrup的'The C++ Programming Language Special Edition'(英文版)第94页,章节5.4 Constants一节,看到这么一句原文'C++ offers the concept of a user-defined constant, a const, to express the notion that a value doesn't change directly.'字眼就在directly上,既然不能directly change,那我试试indirectly change。

问题就发现于这个indirectly change,代码如下:

#include <iostream>

int main() {
 const int a = 2007;   // 这是一个常量,我们'不能directly change'^_^
 int *p = const_cast<int*>(&a);   //我们换一种方法hijack
 *p = 2008;    //篡改

 std::cout << "a = " << a << std::endl;  //期待输出2008
 std::cout << "*p = " << *p << std::endl;
 std::cout << "&a = " << &a << std::endl;
 std::cout << "p = " << p << std::endl;
 
 return 0;
}

我首先在Windows上使用Mingw的g++编译,输出结果让我大惊失色:
a = 2007
*p = 2008
&a = 0x23ff74
p = 0x23ff74

原以为a应该被hijack了,结果a仍然原封未动;关键是后两行打印的a的地址和p的指向都是一个地方,难道C++对常量的保护如此之好,如此智能。不行,换一个平台试试,我又把源码搬到了Solaris上同样是g++编译器,输出结果一致。

百思不得其解后继续'咬文嚼字'的往下看该小节。突然发现这么一句话:'If the compiler knows every use of the const, it need not allocate space to hold it.'…'The common simple and common case is the one in which the value of the constant is known at compile time and no storage needs to be allocated.',左思又想,这么一来在某些时候a被当作类似宏的方式处理的,就如:std::cout << "a = " << a << std::endl;这里cout输出一个常量表达式,编译器估计直接将a替换成2007了,实际上就相当于std::cout << "a = " << 2007 << std::endl;而后的int *p = const_cast<int*>(&a);操作,这时就需要为a分配地址了。有人说a的输出操作是在分配地址之后,那为什么还输出2007呢,我们从编译器的角度看看,编译器在解析到const int a = 2007的时候发现这是一个常量,便将之首先记录到常量符号表中,而后在解析const_cast<int*>(&a)时为a在栈上分配内存,但是在走到输出a那块时首先引用到的还是常量符号表,而输出&a时,由于是取地址操作,所以就把前面分配的栈地址赋到这里了。

我们继续再看一个例子:

#include <iostream>

int main() {
 int i = 2006;
 const int a = i + 1;   
 int *p = const_cast<int*>(&a);   
 *p = 2008;    //篡改

 std::cout << "a = " << a << std::endl;  //期待输出2008
 std::cout << "*p = " << *p << std::endl;
 std::cout << "&a = " << &a << std::endl;
 std::cout << "p = " << p << std::endl;
 
 return 0;
}

在这个例子中const int a = i + 1;用一个非常量表达式给常量a赋初值,按照Bjarne Stroustrup的说法,是需要给a分配内存了。这样我想编译器也许不会在常量符号表中给a留位置,在下面的a的打印输出时,a真的被hijack了。

输出结果:
a = 2008
*p = 2008
&a = 0x23ff70
p = 0x23ff70

再看一个例子:
#include <iostream>

int main() {
 const int i = 2006;
 const int a = i + 1;   
 int *p = const_cast<int*>(&a);   
 *p = 2008;    //篡改

 std::cout << "a = " << a << std::endl;  //期待输出2008
 std::cout << "*p = " << *p << std::endl;
 std::cout << "&a = " << &a << std::endl;
 std::cout << "p = " << p << std::endl;
 
 return 0;
}

编译器在解析到const int i = 2006时首先将i作为常量保存到常量符号表中,在const int a = i + 1时实际上相当于const int a = 2006 + 1,编译器作优化,编译器直接得到a = 2007而且是一个常量,也被保存到常量表中,下面的流程就和第一个例子一样了。

工作中的故事-0是'TRUE'还是'FALSE'?

这个故事源于今天测试组测出的一个BUG,BUG被测试人员转给了我,故事便从这里开始了。

我们的系统是一个后台服务器程序,用C写的,运行在Solaris上,数据存储在数据库中,每次系统启动都要从数据库中读取配置数据。系统根据配置数据对输入的消息数据进行处理。今天的这个BUG现象就是对于一定的输入消息,系统根据配置数据的指导进行处理,结果得到的结果本应该是A,但是却得到了B。

首先咱抱着谨慎负责的态度,先从头到尾,再从尾到头检查自己的程序是否有漏洞或者疏忽大意之处,许久后,未发现问题,疑惑中,怎么经过我的程序这么一番处理,结果就是这样呢?

由于测试数据较简单,所以我对照着数据库中的数据,然后用输入消息数据在我脑子中根据程序的处理步骤人工处理了一次,终于发现了一处’不和谐的音符’。我发现数据库中一业务层配置表中的一字段的数据值有出入,赶忙打开数据库设计报告查看,一找一个准儿,问题就在这儿。

这个表中的这个字段的含义是’是否为默认项’,数据库设计报告中其值的定义是这样的:0 – 默认项;1 – 非默认项。首先我不去评论数据值设计是否合理,我们先来看看程序是如何处理的。
int is_default_item;
…..

if (is_default_item == 1) {
 /* 按照默认项处理 */
} else {
 /* 按照非默认项处理 */
}

看到这所有人都能看出问题所在了,没错,程序里想当然的以为’1′就是默认项,其他就是’非默认项’了。虽然问题找到了,但是我的心里却有了嘀咕,到底是谁错了,这个问题很显然有两个改法,一个是程序修改’1′->’0′;另一个是数据库修改,让1代表默认项。首先这里我要说我不是数据库设计的高手,可以说我自己没做过相关的数据库设计,数据库表中各字段取值设计有无经验可循我也不是很清楚。写到这可以把故事升华一下,升华成一个问题,也就是本篇的题目-0到底是’TRUE’还是’FALSE’?,这里的’TRUE’和’FALSE’并不仅仅代表真与假,而是代表更广义的含义,比如’TRUE’我们可以理解为’成功’、’正确’、’与预期目标一致’等;’FALSE’则可理解为’失败’、’错误’、’与预期目标不一致’等。

在UNIX上用C写过系统程序的人可能清楚Unix提供的API多以返回0代表调用成功,这就是一个典型的0表示’TRUE’的例子,这种返回值方式也被很多人用于程序设计中;在我们自己实现的底层库中,我们同样遵循了这样一种方式。还就我们上述的问题而言,数据库设计中’0′代表’默认项’是否就一定合理呢,相信也是见仁见智的问题;但是从程序角度,你认为:
if (is_default_item == 1) {
 /* 按照默认项处理 */
} else {
 /* 按照非默认项处理 */
}
更合逻辑还是
if (is_default_item == 0) {
 /* 按照默认项处理 */
} else {
 /* 按照非默认项处理 */
}
更合逻辑一些呢?起码我觉得第一种比较符合逻辑一些,代码可读性好些。当然如果按照下面的使用manifest constant的方式处理会比直接用literal constant更好些^_^,这样无论用0还是用1代表’默认项’起码从代码里都是逻辑通顺的,可读性好的。
#define DEFAULT_ITEM 1

if (is_default_item == DEFAULT_ITEM) {
 /* 按照默认项处理 */
} else {
 /* 按照非默认项处理 */
}

这个故事叙述到这就结束了,故事没有完,因为它在我们日常生活工作中还会时常发生,0是’TRUE’还是’FALSE’,把决定权留给大家^_^。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言精进之路1 Go语言精进之路2 Go语言编程指南
商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats