随笔-145  评论-173  文章-70  trackbacks-0
         最近突然发现自己曾经建立的C++体系出现了很多漏洞,对很多问题都产生了疑问,不知道自己是钻了牛角尖还是探究C++的底层或者细节,不过既然碰到了,就解决之吧,或许在某一天之后,会惊奇的发现,原来自己的这些问题会出现某天的笔试或者工作中呢。
          言归正传,这个问题是关于new操作符的。今天在完成C++作业的时候,需要自己实现一个strlen来,其实就是调用C语言函数就可以,不过我突发奇想,写下了下面的这个代码:
code 1:
1#include <iostream>
2using namespace std;
3
4int main()
5{
6    char *str = new char[4];
7    strcpy(str,"fjifejfief");
8    cout << str << endl;
9}
code 2:
 1#include <iostream>
 2using namespace std;
 3
 4int main()
 5{
 6    char *str = new char[5];
 7    strcpy(str,"Good");
 8    strcat(str,"I Love C++");
 9    cout << str << endl;
10}
上面的两个代码都是用到了new操作符,动态分配了一个固定的存储空间,OK。
然后通过strcpy来实现赋值,从而将原来的那个分配的区域填充。可是在1和2中都有一个问题,那就是,填充的字符串超过了动态分配的大小,在code1中,实际分配的只有4个字节,而自己拷贝过去的确有10个字节(不含null),而code2中,首先初始化的时候没有问题,后面的连接函数也是。为何我会产生这个问题呢?
因为曾经在《C++ Primer》一书中讲到,当进行字符串操作的时候,需要特别注意到那个null要占用一个空间,而对于strcat和其他函数,特别要注意是否超过表示的范围,而我自己曾经碰到过这个问题,所以比较疑惑,上面的那个没有溢出吗?为何可以输出正确的结果呢?

下面,列出个错误的例子来看看:
code3
 1#include <iostream>
 2using namespace std;
 3
 4int main()
 5{
 6    char str[4= "goo";
 7    cout << str << endl;
 8    strcat(str,"sfsfefe");
 9    cout << str << endl;
10    return 0;
11}
明显code3会出错(刚开始的时候居然没有出错,后来程序才崩溃的,以为灵异事件呢。),固定分配的数组的话,需要特别注意像strcat函数,因为这种连接函数就可能超过范围,发生错误。

ok:
汇总如下:固定分配的时候,不能够超过数组的范围,而且字符串的话需要注意总是有一个null在其中的,所以大小要自己把握好。
但是,对于new动态分配的话,可以超过范围,不管是strcat还是直接在分配的时候超过范围都可以,Why?
============================================================================================================

经过网友留言汇总,同时自己测试,发现了一些新的问题,也有了新的体会!   Version 1

============================================================================================================

留言汇总(解答):
经过和网友的讨论,我初步了解了错误可能的原因,现罗列如下,如有遗漏和错误,还望各位指教。
1.两者的分配方式不同。使用关键字new操作符分配的话,分配的空间是在堆当中(heap),而直接使用数组的话,分配的空间在堆栈中(stack)。
2.操作。对于堆栈的溢出,由于变量的填充时按照从高地址到低地址的方式,所以,溢出的话会修改函数的返回值,造成运行的错误。而对于堆分配的话,这里即使溢出了,由于没有对它进行进一步操作,所以没有出现问题。
3.我的一点理解。对于堆栈溢出的错误,我想大家都知道了,可是上面的这个堆溢出的话,好像即使有错误也没有什么问题。我尝试了使用
delete []str,或者是输出str[8]等单元,都是显示正确的结果,也没有出现错误。所以怀疑的是,难道堆上的这种分配这么安全,那我delete的话,到底是删除的分配的4个字节,还是拷贝过去的溢出的那个超过4个字节的那么多单元内容呢?我的理解是,堆上分配的单元由于太随意,灵活性太强,所以对于错误的发生就可能性很小。
4.我写的调试代码的一个新的问题:
 1#include <iostream>
 2using namespace std;
 3
 4int main()
 5{
 6    char* str1 = new char[4];
 7    str1 = "sfe"  //如果去掉这一行,那么程序就可以正常运行了……可是这个仅仅是初始化指针的啊。
 8    char str2[4= "adf";
 9    cout<<"赋值前:"<<endl;
10    cout<<"str1指向:"<<&str1<<endl;
11    cout<<"str1内容:"<<str1<<endl;
12    cout<<"str2指向:"<<&str2<<endl;
13    cout<<"str2内容:"<<str2<<endl;
14    strcpy(str2,"ooo");
15    cout<<"str2先赋值后:"<<endl;
16    cout<<"str1指向:"<<&str1<<endl;
17    cout<<"str1内容:"<<str1<<endl;
18    cout<<"str2指向:"<<&str2<<endl;
19    cout<<"str2内容:"<<str2<<endl;
20    strcpy(str1,"iiiiii");
21    cout<<"str1后赋值后:"<<endl;
22    cout<<"str1指向:"<<&str1<<endl;
23    cout<<"str1内容:"<<str1<<endl;
24    cout<<"str2指向:"<<&str2<<endl;
25    cout<<"str2内容:"<<str2<<endl;
26        
27}

28
程序在运行一半后崩溃,典型的溢出了。但是,此代码如果去掉上面初始化的那一行,程序就没有错误,而且我这里还计算发现对于堆栈的话没有任何错误,而堆的话故意写了个溢出的赋值,就是下面strcpy(str1,"iiiiii");
此时加上初始化的代码后,问题就来了,上个截图。




从错误的信息来看,就是指向到 strcpy(str1,"iiiiii")的时候出错了的。

这个说明:在我没有对堆上的变量初始化的时候,如果越界了,没有发现出错(它会自动扩展那个大小吗?)。
而如果我对堆上的变量初始化的话,那么,再次复制的时候,如果越界了,就会发生错误!

各位,知道为什么吗?(问题好像更明朗了些,而且好像更深入了些。当然,钻这个牛角尖或许没有必要!)
posted on 2010-01-13 23:14 deercoder 阅读(2165) 评论(23)  编辑 收藏 引用

评论:
# re: 【欢迎各位留言讨论】C++中运算符New的一个疑问[未登录] 2010-01-14 00:54 | Steven
你需要去了解下 堆(stack) 和 栈(heap) 的区别.  回复  更多评论
  
# re: 【欢迎各位留言讨论】C++中运算符New的一个疑问 2010-01-14 02:06 | 陈梓瀚(vczh)
因为new的时候,系统可以选择多给你几个字节,而且这并不是固定的数字。  回复  更多评论
  
# re: 【欢迎各位留言讨论】C++中运算符New的一个疑问 2010-01-14 02:20 | OwnWaterloo
1. 在C/C++代码正确且C/C++实现(编译器,运行库)正确的情况下, C/C++语言保证得到正确的结果。

如果出现了错误的结果, 可以问why。
是"C/C++代码正确"这个假设有误? 还是"C/C++实现正确"这个假设有误?


2. 如果C/C++代码本身有某种错误, C/C++实现不保证得到正确的结果, 也不保证得到错误的结果, 更不保证会报告错误的结果。
代码的错误有可能会被隐藏, 到其他时候发作。

这时候, 询问"为什么没有出现错误", 是不明智的。
C/C++实现没有义务保证产生一个错误。
  回复  更多评论
  
# re: 【欢迎各位留言讨论】C++中运算符New的一个疑问 2010-01-14 09:22 | bluegene
我是这样理解的,你用 new 分配的空间是放在堆(heap)里的,超出了的空间如果不被其它数据覆盖暂时是没有问题的。固定分配的时候是在栈(stack)里分配的,其空间肯定是不能益处的。  回复  更多评论
  
# re: 【欢迎各位留言讨论】C++中运算符New的一个疑问 2010-01-14 10:26 | 饭中淹
new分配的在堆里,你溢出了,只会导致堆出问题。而你下面又没有再使用堆,所以不会出错。
直接写的局部变量,分配在堆栈里,堆栈里有函数的返回地址,所以溢出了,覆盖了返回地址,函数执行完就出错了。
另外,有些 编译器会生成对固定长度数组的保护性检测代码,一旦溢出,就会弹出提示。早先在vs2003还是vs2002的时候见过。
  回复  更多评论
  
# re: 【欢迎各位留言讨论】C++中运算符New的一个疑问 2010-01-14 10:27 | zuhd
对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向;对于栈来讲,它的生长方向是向下的,是向着内存地址减小的方向增长。看下反汇编代码一切都明白了
  回复  更多评论
  
# re: 【欢迎各位留言讨论】C++中运算符New的一个疑问 2010-01-14 10:31 | zuhd
栈里有函数的返回地址,所以溢出了,覆盖了返回地址,函数执行完就出错了。

==========================================
这句话是重点,当ret的时候,call下条指令时异常了,如果你多定义了几个变量,让栈溢出不到函数的返回地址,错误依然不会出现的  回复  更多评论
  
# re: 【欢迎各位留言讨论】C++中运算符New的一个疑问[未登录] 2010-01-14 12:43 | vane
出错是必然的,没问题的时候你比较幸运。

#include <iostream>
using namespace std;

#pragma pack(push)
#pragma pack(1)
int main()
{
char *str = new char[5];
strcpy(str,"Good");
strcat(str,"I Love C++");
cout << str << endl;
}
#pragma pack(pop)

你可以这么试一下  回复  更多评论
  
# re: 【欢迎各位留言讨论】C++中运算符New的一个疑问 2010-01-14 15:53 | 坏人
越界,后果自负。  回复  更多评论
  
# re: 【欢迎各位留言讨论】C++中运算符New的一个疑问 2010-01-14 15:57 | turygo
数组越界都是这样的,错误不是立刻出现,而是不知道什么时候就出问题了,而且到那个时候代码量一大,你根本无从找起错误。  回复  更多评论
  
# re: 【欢迎各位留言讨论】C++中运算符New的一个疑问 2010-01-14 17:30 | 零宇
建议楼主补充一下基础知识  回复  更多评论
  
# re: 【欢迎各位留言讨论】C++中运算符New的一个疑问 2010-01-14 19:22 | besterChen
LZ遇到的问题是堆和栈溢出的问题,您说的可能是灵异事件是因为程序对栈和堆检查导致的。

不知道LZ用的是什么开发环境。我学习C语言也刚好学到这里,自己在VC6得开发环境下仔细调试过其过程并做了笔记,地址:http://www.cppblog.com/besterChen/archive/2010/01/13/105538.html

希望能对楼主有帮助,由于自己也是初学,希望LZ能多多指正日志中的错误……  回复  更多评论
  
# re: 【欢迎各位留言讨论】C++中运算符New的一个疑问 2010-01-14 22:30 | wildpointer
这种越界的事,你知道会错还用。  回复  更多评论
  
# re: 【欢迎各位留言讨论】C++中运算符New的一个疑问 2010-01-14 23:21 | 刘畅
@Steven
好的,谢谢。我觉得也应该是这方面的问题。  回复  更多评论
  
# re: 【欢迎各位留言讨论】C++中运算符New的一个疑问 2010-01-14 23:22 | 刘畅
@陈梓瀚(vczh)
可是多出来的字节是无法预知的啊。而且如果太多的话还是会溢出的吧。  回复  更多评论
  
# re: 【欢迎各位留言讨论】C++中运算符New的一个疑问 2010-01-14 23:25 | 刘畅
@bluegene
谢谢,之前一直觉得和内存的分配有关,堆栈和堆确实不大一样。  回复  更多评论
  
# re: 【欢迎各位留言讨论】C++中运算符New的一个疑问 2010-01-14 23:27 | 刘畅
@饭中淹
@zuhd
谢谢你们的精彩解释,受益匪浅。  回复  更多评论
  
# re: 【欢迎各位留言讨论】C++中运算符New的一个疑问 2010-01-14 23:29 | 刘畅
@besterChen
呵呵,一起学习!  回复  更多评论
  
# re: 【欢迎各位留言讨论】C++中运算符New的一个疑问 2010-01-14 23:29 | 刘畅
@零宇
好的,谢谢!  回复  更多评论
  
# re: 【欢迎各位留言讨论】C++中运算符New的一个疑问 2010-01-15 00:05 | Benjamin
strcpy等在一些公司的编码规范中是禁用的。  回复  更多评论
  
# re: 【欢迎各位留言讨论】C++中运算符New的一个疑问 2010-01-15 00:55 | 空明流转
bushuoshale...  回复  更多评论
  
# re: 【欢迎各位留言讨论】C++中运算符New的一个疑问[未登录] 2010-01-15 09:47 | Steven
@刘畅
呵呵,我把堆和栈的英文写反了。。。
以前写C 程序的时候经常用到大量的 strcopy, strcat之类的函数,偶尔出错是在所难免的,只要是人总是有算错的时候,尤其有些时候混合处理unicode和ansi串的时候,算字符串长度是容易出错的。

所以后来写 C++ 的时候我尽量避免去使用这类函数了,而是倾向于用 stl的string wstring, 尽量用 stl的容器类去代替自己分配管理内存。

代码规模大到一定程度,除了缓冲溢出实在是很难找。
顺便说下,http://www.coverity.com/products/static-analysis.html
这个东西很强大,可以检测到你代码里潜在的溢出问题。  回复  更多评论
  
# re: 【欢迎各位留言讨论】C++中运算符New的一个疑问 2010-01-15 12:43 | 刘畅
@Steven
谢谢,去看看,学习了……  回复  更多评论
  

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