OnTheWay2012
埋葬昨天的我,迎来重生的我!
posts - 15,  comments - 89,  trackbacks - 0
今天在写代码的时候发现了一个很有意思的错误,出错的代码如下。
加入你的电脑里定义了JAVA_HOME这个环境变量,你能看出程序的输出结果是什么吗?
 1#include <iostream>
 2#include <assert.h>
 3#include <map>
 4#include <string>
 5
 6using namespace std;
 7
 8bool GetEnvByKey(string const &strKey, string &strValue)
 9{
10    bool bRet = false;
11
12    assert(!strKey.empty());
13
14    size_t sizeValue = 0;
15    if(0 == getenv_s(&sizeValue, NULL, 0, strKey.c_str()))
16    {
17        strValue.reserve(sizeValue);
18        bRet = (0 == getenv_s(&sizeValue, const_cast<char*>(strValue.c_str()), sizeValue, strKey.c_str()));
19    }

20
21    return bRet;
22}

23
24int main( )
25{
26    string strKey("JAVA_HOME");
27    string strValue;
28    
29    if(GetEnvByKey(strKey, strValue))
30    {
31        cout<<strKey<<endl;
32        cout<<strValue<<endl;
33
34        map<stringstring> mapEnvInfo;
35        mapEnvInfo.insert(mapEnvInfo.end(), make_pair(strKey, strValue));
36
37        cout<<mapEnvInfo.size()<<endl;
38        cout<<mapEnvInfo.begin()->first<<endl;
39        cout<<mapEnvInfo.begin()->second<<endl;
40    }

41
42    return 0;
43}


先把你猜想的结果写出来,然后再把代码复制到VC里你试一下,看看到底输出什么。你猜对了吗?是不是输出结果有点不太对?呵呵,经过调试代码你发现错误了吗?
错误就在
strValue.reserve(sizeValue);
bRet = (0 == getenv_s(&sizeValue, const_cast<char*>(strValue.c_str()), sizeValue, strKey.c_str()));
这两行。
错在这种使用strValue的方式,详述如下:
strValue.reserve(sizeValue)这句代码给strValue分配了sizeValue字节的内存,然后getenv_s又把从操作系统里取得的值赋给了strValue所代表的内存中,请注意strValue除了有指向字符串的内存数组,还有一个记录该字符串中保存的字符数的一个变量_Mysize。上面的getenv_s执行后只是把字符串给赋值了,但是这个记录字符串中字符个数的变量却没有赋值。下面的代码需要使用这个变量才能进行输出,而此时这个变量是0,所以strValue就没有输出。
经过上面的说明之后你肯定明白了上述代码出错的原因。为什么会出错?是因为我们使用strValue的时候把它认为成了数组,所以就出错了。本来strValue是一个对象,它具有各种方法,但是我们的使用方式强迫strValue脱下外衣,强迫它变成赤裸裸的字符串数组,这就是问题的发生根源。因此我们使用类对象的时候一定不能把类的外衣脱掉,让类带来的好处消失于无形。
posted on 2010-04-02 22:30 OnTheWay 阅读(2764) 评论(9)  编辑 收藏 引用 所属分类: C、C++

FeedBack:
# re: 不要把类的外衣脱下来,让类的美丽消失于无形
2010-04-02 22:54 | 杨帆
恩,明白,有收获。3Q。  回复  更多评论
  
# re: 不要把类的外衣脱下来,让类的美丽消失于无形
2010-04-02 23:10 | 杨帆
不对,我又看了看,我觉得楼主的理解似乎有问题,也请指教指教。

strValue.reserve(sizeValue);
bRet = (0 == getenv_s(&sizeValue, const_cast<char*>(strValue.c_str()), sizeValue, strKey.c_str()));

的确是这里有问题,问题的确出在const_cast<char*>(strValue.c_str())这个表达式上。

但是楼主想表达的意思是不是getenv_s()这个函数把strValue这个string类型“强行”当做的了一个字符串来进行处理,这个函数修改strValue的时候,仅仅修改了“一部分”?

我想应该是这样,const_cast<char*>(strValue.c_str())是这么执行的:
1、strValue.c_str() 这里strValue返回了一个”临时的“字符串,注意,是临时的而且是const的,它应该是新开辟了一小段内存用以存储这个C-Style字符串,而不是把strValue本身当做字符串给返回回去了。

2、使用const_cast<char *>将这个const 并且”临时的“字符串进行了转换,转换成了 非const ,但仍然是临时的字符串。

3、然后getenv_s()函数会对这个临时非const的字符串进行一些操作。

4、随着函数调用的结束,这个临时的字符串被释放掉了。

在上边这个过程中,并没有对strValue进行任何改变,也正因此在以后才什么都没有输出来。

和楼主不一样的是,getenv_s()根本没有对strValue进行任何操作。  回复  更多评论
  
# re: 不要把类的外衣脱下来,让类的美丽消失于无形
2010-04-03 08:59 | OnTheWay
@杨帆
谢谢你的关注。
不过strValue.c_str()没有返回什么临时的指针。
因为c_str()函数返回的是一个char const *类型,这说明返回的指针是不可写的。但是我为了让返回的指针可写,所以加了const_cast。这同时说明有const_cast的地方都存在潜在的错误。你可以在VC2005的debug版下看看程序具体的执行过程,在此过程中没有创建临时的字符串数组。  回复  更多评论
  
# re: 不要把类的外衣脱下来,让类的美丽消失于无形
2010-04-03 09:24 | CBKID
@杨帆
我也感觉有点像你的看法,但是如果这样的话可以先给strValue赋值,如果真的没有进行任何操作的话结果就很显眼了。  回复  更多评论
  
# re: 不要把类的外衣脱下来,让类的美丽消失于无形
2010-04-03 13:06 | 杨帆
@OnTheWay
感谢楼主,恩,我验证了一下,我的理解不正确,不好意思哈,也感谢从你这学到了东西,3Q。

我用下列代码进行了验证,在gcc下编译通过。

#include <iostream>
#include <string>
using namespace std;

int main(){
string CppString("我是一个string啊,咿呀咿呀哟!");

//在这里用c_str()返回了一个CStyle字符串,保存在CStyleString上
char * CStyleString = const_cast< char *>( CppString.c_str() );

//首先验证一下这个CStyleString的内容是不是正确
cout << CStyleString <<endl;

//然后对CStyleString进行一些改变。
cin >> CStyleString ;

//输出改变后的CStyleString看看。
cout << CStyleString << endl;

//这时再输出CppString的内容,它改变了!。
cout << CppString <<endl;
return 0;
}

程序运行如下:
我是一个string啊,咿呀咿呀哟!
我是一个CStyle String啊,咿呀咿呀哟! //这是我的输入。
我是一个CStyle
我是一个CStyle ……&%¥*&…… //后边一一堆乱码,原因请见楼主的帖子。  回复  更多评论
  
# re: 不要把类的外衣脱下来,让类的美丽消失于无形
2010-04-03 15:06 | 杨帆
进一步琢磨,我在http://www.cplusplus.com/上查了查c_str的描述,连接为:http: //www.cplusplus.com/reference/string/string/c_str/
描述如下:
const char* c_str ( ) const;

Get C string equivalent
Generates a null-terminated sequence of characters (c-string) with the same content as the string object and returns it as a pointer to an array of characters.

A terminating null character is automatically appended.

The returned array points to an internal location with the required storage space for this sequence of characters plus its terminating null-character, but the values in this array should not be modified in the program and are only granted to remain unchanged until the next call to a non-constant member function of the string object.


关键位最后一段:(凑乎着翻译下,大家海涵哈,不要觉得它惨不忍睹哈,^_^)

该函数所返回的指针(数组)指向该字符串的内部(internal)位置,并且该位 置具有足够存储空间来存储该字符序列以及表示结尾的空字符,但是返回数组中的值在程序不应该有任何修改并只应被传值使用(这里的grant实在译不好,望 大牛们指教),直到下一次调用该字符串对象的非常(non-const)成员函数。

这一段话再次验证了楼主的说法。


  回复  更多评论
  
# re: 不要把类的外衣脱下来,让类的美丽消失于无形
2010-04-03 19:44 | OnTheWay
@杨帆
感谢你的持续关注。
你的钻研精神值得敬佩。  回复  更多评论
  
# re: 不要把类的外衣脱下来,让类的美丽消失于无形
2010-04-05 08:18 | 99书城
福建省地方你们是地方  回复  更多评论
  
# re: 不要把类的外衣脱下来,让类的美丽消失于无形
2010-04-13 02:29 | 欲三更
代码没看,但是要如lz所说的话,好无厘头的错误啊。。。  回复  更多评论
  

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理



<2010年4月>
28293031123
45678910
11121314151617
18192021222324
2526272829301
2345678

常用链接

留言簿(4)

随笔分类

随笔档案

友情连接

搜索

  •  

最新评论

阅读排行榜

评论排行榜