C++编程之内存和指针

来源:互联网 发布:电视棒软件安卓版 编辑:程序博客网 时间:2024/04/30 19:31
一、内存分配方式有三种: 
(1)  从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static 变量。 
(2)  在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 
(3)  从堆上分配,亦称动态内存分配。程序在运行的时候用 malloc 或 new 申请任意多少的内存,程序员自己负责在何时用 free 或 delete 释放内存。动态内存的生存
期由我们决定,使用非常灵活,但问题也最多。

二、常见的内存错误及其对策如下:
(1)内存分配未成功,却使用了它。

(2)内存分配虽然成功,但是尚未初始化就引用它。 
犯这种错误主要有两个起因:一是没有初始化的观念;二是误以为内存的缺省初值
全为零,导致引用初值错误(例如数组)。 

 (3)内存分配成功并且已经初始化,但操作越过了内存的边界。 
例如在使用数组时经常发生下标“多 1”或者“少 1”的操作。特别是在 for 循环语
句中,循环次数很容易搞错,导致数组操作越界。

 (4)忘记了释放内存,造成内存泄露。 

三、释放了内存却继续使用它的三种情况: 
(1)程序中的对象调用关系过于复杂,实在难以搞清楚某个对象究竟是否已经释放了
内存,此时应该重新设计数据结构,从根本上解决对象管理的混乱局面。 
(2)函数的 return 语句写错了,注意不要返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。 
(3)使用 free 或 delete 释放了内存后,没有将指针设置为 NULL。导致产生“野指针”。

四、规则
【规则 7-2-1】用 malloc 或 new 申请内存之后,应该立即检查指针值是否为 NULL。防止使用指针值为 NULL 的内存。 
【规则 7-2-2】不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。 
【规则 7-2-3】避免数组或指针的下标越界,特别要当心发生“多 1”或者“少 1”操作。 
【规则 7-2-4】动态内存的申请与释放必须配对,防止内存泄漏。 
【规则 7-2-5】用 free 或 delete 释放了内存之后,立即将指针设置为 NULL,防止产生“野指针”。

类似下面的很多函数都是返回栈内存的,只要栈内存开辟的空间足够大,就可以得到正确的返回值。这是为什么?
 
char *CGenericServer::CharToCString(UCHAR* str, int nLength)
{
//CString strShow=_T("");
LPTSTR p;
char szText[1024];
ZeroMemory(szText, 1024);
p = szText;
for (int i = 0; i< nLength; i++)
{
p+= wsprintf(p, "%.2X", str[i]);  //这部分为关键部分
}
return szText;
}
答:首先这是一种非常危险的方式,强烈要求能不这样用。这种情况还能读取到的原因可能是,函数退出时,指针指向的内存空间还暂时没有回收。
 
2、关于数组或指针的下标越界
(1)char a[] = “hello”;      a 的容量是 6 个字符,其内容为 hello\0

(2)推想:自己在一个函数里面定义了数组  char Number[16];
如果自己给他16个char 型字符会不会造成数组的越界呢?
答:这个如果给他16个字符是肯定不会越界的,只要下标对应正确。

下面这段函数,如果Length的值为16时,就会造成溢出错误。如果c[8]变为10就没有问题,想想为什么呢?
U8* CGenericServer::GPRSTranslationID(U8* buffer, int Length)
{
U8 c[8];
ZeroMemory(c,8);
int nCount = 0;
CString temp;
int i,j;
for (i=0;i<Length/2;i++)
{
CString temp="";
for (j=nCount*2;j<nCount*2+2;j++)
{
temp+=buffer[j];
}
sscanf(temp,"%2x",&c[nCount]);
nCount++;
}
return c;
}

3、修改常量字符串
(1)错误的程序
char *p = “world”;     // 注意 p 指向常量字符串 
p[0] = ‘X’;            // 编译器不能发现该错误 
cout << p << endl; 
指针 p 指向常量字符串“world”(位于静态存储区,内容为 world\0),
常量字符串的内容是不可以被修改的。从语法上看,编译器并不觉得语句 p[0]= ‘X’有什
么不妥,但是该语句企图修改常量字符串的内容而导致运行错误。
(2)与正确的程序对比
char a[] = “hello”; 
a[0] = ‘X’; 
cout << a << endl; 
字符数组 a 的容量是 6 个字符,其内容为 hello\0。a 的内容可以改变,如 a[0]= ‘X’。

4、 数组和指针的内容复制与比较 
(1)数组的复制与比较
    char a[] = "hello"; 
    char b[10]; 
    strcpy(b, a);          // 不能用  b = a; 
    if(strcmp(b, a) == 0);  // 不能用  if (b == a) 
不能对数组名进行直接复制与比较。
若想把数组 a 的内容复制给数组 b,不能用语句 b = a ,否则将产生编译错误。应该用标准库函数 strcpy 进行复制。
同理,比较 b 和 a 的内容是否相同,不能用 if(b==a) 来判断,应该用标准库函数 strcmp
进行比较。

(2)指针的复制与比较

int Length = strlen(a);
char *p = (char*)malloc(sizeof(char)*(Length+1));
strcpy(p, a);                  //不能用p = a;
if(strcmp(p, a) == 0);  //不能用if(p==a);

语句 p = a 并不能把 a 的内容复制指针 p,而是把 a 的地址赋给了 p。要想复制 a
的内容,可以先用库函数 malloc 为 p 申请一块容量为 strlen(a)+1 个字符的内存,再用 strcpy 进行字符串复制。同理,语句 if(p==a) 比较的不是内容而是地址,应该用库函数 strcmp 来比较。

在这里为什么是length+1呢,难道指针指向的字符串后面而需要结束符?
答:我们这个p指针只指向了某个字符串的起始地址(首地址),它并不知道这个串有多长。假如没有结束符,我们取这个字符串的时候怎么知道取到哪里为止呢?因此'/0'就专门用来结束,这个也是个字符,在内存里面存放的值就是数字0.意思就是说'/0'字符的ASCII码就是0。

5、指针在内存中的存放

char *p = “world”; 

他一共占6字节,内存中不可能在同一地址下存放6个字节。一个内存地址只能存放一个字节。所以这个"world"就占用了6个内存地址,首地址就是w字符的地址。大家可以将p选中拖放到内存窗口的地址栏观察。形如:

0x0012fed4     首地址,p的值就等于0x0012fed4这个地址值。

0x0012fed5    o

0x0012fed6    r

0x0012fed7    l

0x0012fed8    d

0x0012fed9    0

上面就是数据在内存里面存放的位置关系,逐字节存储。这里注意,在内存里面通常我们看到的是16进制。这里只是为了更形象直接写成字符了。(参考masefee的深入指针部分)

注:如果是指向short ,int等其他类型的指针,则其后自然也就没有结束符'\0'了,因为他们的长度是定的,不需要这个。


6、空指针

空指针既然叫空指针,他的意思就是这个指针所存放的内存地址为0,不如:

int* a = 0; 指针a所存的地址就是0x00000000,这个地址专门用于指针初始化或者清零,内存的这个地址是被保护起来的,既不能往里面写数据也不能读里面的数据。如果试图如:

int* pNULL = 0;

int var = *pNULL;

这样将出错,出错信息是:

什么什么.exe的什么什么地方 未处理的异常: 0xC0000005: 读取位置 0x00000000 时发生访问冲突 

如果试着写:

*pNULL = 100;

将出错:

什么什么.exe的什么什么地方 未处理的异常: 0xC0000005: 写入位置 0x00000000 时发生访问冲突 。


7、野指针

野指针就是指向了不该指向的内存地址,假如这个内存地址不可写或者不可读,我们的程序将会崩溃。假如这个内存没有被保护,读写都可以的话,这样的错误将很难找到。数据将会出错。会出现很多莫名其妙的现象;很多时候会因为越界刚好修改了某个指针的值,这样指向的内存地址就有可能不是我们想要的地址就造成了野指针。

int* p = &a;  // 正常

p = ( int* )0x12345678; // 这句不要奇怪,既然指针能够转换为整数,整数同样可以转换为指针,这里转换过后,p的值就等于0x12345678;这个内存地址并不是我们想要的,也很可能是非法的。这里的p就野了。也就是野指针。

8、数据拼接

short a[ 2 ] = { 0xffff, 0xeeee };

short* pA = a;

这样一来,pA[ 0 ] 就是0xffff,pA[ 1 ]就是0xeeee。再有一个:

int* pInt = ( int* )pA; 将pA转换为int*(指针之间可以随便转换,只要确保不出错,前面已经说过),short*变成了int*。我们知道2个short刚好等于一个int所占的内存。然后我们操作这个pInt:

int var = *pInt;  这里也可以直接使用:int var = *( int* )pA; 那么此时var取出来的就是4个字节的内存数据,我们知道short数组a有2个元素刚好占用4个字节,而且数组是连续存放的。那么var将是这两个元素的组合值。

程序测试:
short a[ 2 ] = { 0xffff, 0x0001 };
short* pA = a;
int *lpa = (int *)pA;
int var = *lpa;
printf("var is %d", var);
return 0;

c++编程学习之内存和指针
131071 = 0x0001ffff ;

9、数组的内存容量
用运算符 sizeof 可以计算出数组的容量(字节数)。sizeof(a)的值是 12(注意别忘了’\0’)。指针 p 指向 a,但是 sizeof(p)的值却是 4。这是因为sizeof(p)得到的是一个指针变量的字节数,相当于 sizeof(char*),而不是 p 所指的内存容量。
C++/C 语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。 
注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。

测试程序:
using namespace std;
int  fun(char table[100]);
int _tmain(int argc, _TCHAR* argv[])
{
int len1, len2, len3;
char a[] = "hello world";
len1 = sizeof(a);
char *p = a;
len2 = sizeof(p);
len3 = fun(a);
cout<<"数组大小为 "<<len1<<endl;
cout<<"指向数组的指针大小为 "<<len2<<endl;
cout<<"传入函数的数组大小为 "<<len3<<endl;
return 0;
}
int  fun(char table[100])
{
int length = sizeof(table);
return length;
}
c++编程学习之内存和指针



10、 指针参数是如何传递内存的
 如果函数的参数是一个指针,不要指望用该指针去申请动态内存。
例如:
 void GetMemory(char *p, int num) 
    p = (char *)malloc(sizeof(char) * num); 
void Test(void) 
    char *str = NULL; 
    GetMemory(str, 100);  // str 仍然为 NULL  
    strcpy(str, "hello");  // 运行错误 
毛病出在函数 GetMemory 中。编译器总是要为函数的每个参数制作临时副本,指针参数 p 的副本是 _p,编译器使 _p = p。如果函数体内的程序修改了_p 的内容,就导致参数 p 的内容作相应的修改。这就是指针可以用作输出参数的原因。在本例中,_p 申请了新的内存,只是把_p 所指的内存地址改变了,但是 p 丝毫未变。所以函数 GetMemory并不能输出任何东西。事实上,每执行一次 GetMemory 就会泄露一块内,因为没有用free 释放内存。 

如果非得要用指针参数去申请内存,那么应该改用“指向指针的指针”
void GetMemory2(char **p, int num) 
    *p = (char *)malloc(sizeof(char) * num); 
void Test2(void) 
    char *str = NULL; 
    GetMemory2(&str, 100); // 注意参数是 &str,而不是 str 
    strcpy(str, "hello");   
    cout<< str << endl; 
    free(str);  

原创粉丝点击