计算机程序在存储数据时必须跟踪3个基本属性:
- 信息存储在何处
- 存储的值是多少
- 存储的信息是什么类型
我们可以从C++ 语法的角度简单分析一下声明语句;
int a;
该声明语句指出了值的类型和符号名,还让程序为值分配内存,并在内部跟踪该内存单元,这是一种基本类型的策略。
我们来看复合类型的策略。即指针的策略,指针是一个变量,其存储的是值的地址,而不是值本身。对于两种不同变量的指针,虽然这两个指针指向不同的数据类型,但是其本身的值(即地址)的大小是相同的,通常在32位的机器上,一个指针占4字节,因此求sizeof得到4;在64位的机器上,一个指针占8字节,因此求sizeof得到8.
指针大小准确滴说跟你用的编译器直接相关,那个应该看编译器手册,什么平台环境只要编译器支持都可以,比如你用64位系统,而你用的编译器的设定的指针是32位的,那你用那个编译器写程序的时候指针也是32位的。一般指针在处理器处理器上面就一个间接寻址或者跳转,而有些处理器间接寻址空间是有设定的,跳转也分长短跳转,而且设置还有指令长度模式设定等等,而指针那个概念只会在高级语言里面的设定,具体是长跳转还是短跳转,寻址模式,使用指令长度模式等等设置都会在编译器里面搞,写程序的完全不关心而已。而原理上编译器在一个16位处理器弄个32位的指针是完全木有问题的,但木有实质的意义。
使用new分配内存
变量是在编译时分配的有名称的内存,而指针指示为可以通过名称直接访问的内存提供了一个别名。指针真正的用武之地在于,在运行阶段分配未命名的内存以存储值。在这种情况下,只能通过指针来访问内存。C语言中可以使用 mallco 来分配内存, C++ 中可以使用 new。
int * pn = new int
上述语句中,new int 告诉程序,需要适合存储int 的内存,new 运算符根据类型来确定需要多少字节的内存,然后,它找到对应的内存,并返回该内存块的地址。接下来,将地址赋给pn。
我们在这里放上一段CPP primer plus 的示例代码:
#include <iostream>
int main() {
using namespace std;
int nights = 1001;
// allocate space for an int
int* pt = new int;
// store a value there
*pt = 1001;
cout << "nights value = ";
cout << nights << ": location " << &nights << endl;
cout << "int ";
cout << "value = " << *pt << ": location = " << pt << endl;
double * pd = new double; // allocate space for a double
*pd = 10000001.0; // store a double there
cout << "double ";
cout << "value = " << *pd << "; location = " << pd << endl;
cout << "location of pointer pd: " << &pd << endl;
cout << "size of pt = " << sizeof(pt);
cout << "; size of *pt = " << sizeof(*pt) << endl;
cout << "size of pd = " << sizeof pd;
cout << "; size of *pd = " << sizeof(*pd) << endl;
return 0;
}
// output
nights value = 1001: location 0x7ffee3fac938
int value = 1001: location = 0x7f9b83405930
double value = 1e+07; location = 0x7f9b83405940
location of pointer pd: 0x7ffee3fac928
size of pt = 8; size of *pt = 4
size of pd = 8; size of *pd = 8
上述代码中需要注明几点,
- 声明指针时必须指出指针所指向的类型,因为指针存储了数据对象的地址信息,而地址本身只指明了对象存储地址的开始,而没有指出其类型(使用的字节数)。由于上述代码中知名了指针的类型,因此打印 *pt 的值时 cout 知道要读取多少字节以及如何解释他们。
- new 分配的内存块通常与常规变量声明分配的内存块不同。变量nights和pt的值都被存储在栈中,而 new 从被称为堆(heap)或自由存储区(free store)都内存区域分配内存。
- 在内存被耗尽的情况下,new 或引发异常,在较老的C++实现中,new会返回0(null pointer),即空指针,C++确保空指针不会指向有效的数据。
Delete
#include <iostream>
int main() {
using namespace std;
int* pt;
*pt = 10;
cout << "==========before delete ============" << endl;
// int value = 10; location = 0x7ffee341d948
cout << "int ";
cout << "value = " << *pt << "; location = " << pt << endl;
// location of pointer pt: 0x7ffee341d920
cout << "location of pointer pt: " << &pt << endl;
delete pt;
// ERROR
// delete pt;
cout << "==========after delete ============" << endl;
cout << "int ";
// ERROR malloc: *** error for object 0x7ffee64d2948: pointer being freed was not allocated
cout << "value = " << *pt << "; location = " << pt << endl;
// ERROR malloc: *** error for object 0x7ffee341d948: pointer being freed was not allocated
cout << "location of pointer pt: " << &pt << endl;
}
这将释放ps指向的内存,但不会删除ps指针本身。例如,可以将ps重新指向另一个新分配的内存块。一定要配对地使用 new 和 delete。否则将出现内存泄漏(memory leak),直至内存溢出。需要注意的是,这里要注意的是:
- 不建议使用 delete 删除栈数据,有些编译器可能会报错,如果没报错,那么下次使用该数据时就会报错
- delete 删除 new 生成的对象时只会将该段内存标为可用,原有的数据不会进行清空,当下一次对该内存块写入新数据时原有的数据才会被抹去。
- 使用 delete 的关键在于,将它用于 new 分配的内存。这并不意味着要使用用于 new 的指针,而是用于new 的地址,看下面代码可以帮助理解
int* a = new int;
int* b = a;
delete b;
cout << "Delete use second pointer OK" << endl;
// ERROR malloc: *** error for object 0x7fef8f405930: pointer being freed was not allocated
// delete a;
上述代码a, b都指向同一个内存块,因此使用 delete b 也可以将该内存块进行释放,即使用 delete 的关键在于使用内存块的地址,但是不要创建两个指向同一个内存块的指针,因为这将增加错误地删除同一个内存块两次的可能性。
new 和 delete 总结
当我们使用 new 和 delete 时需要遵守以下规则:
- 不要使用 delete 来释放不是 new 分配的内存
- 不要使用 delete 释放同一个内存块两次
- 如果使用 new[] 为数组分配内存,则应使用 delete[] 来释放
- 如果使用 new 为一个实体分配内存,则应使用 delete 来释放
- 对空指针应用 delete 是安全的