通常,new负责在堆(heap)中找到一个能够满足要求的内存块。new运算符还有一种变体,被称为定位(placement)new运算符,他能让你能够指定要使用的位置。程序员可以使用这种特性来设置其内存管理规程、处理需要通过特定地址进行访问的硬件或在特定位置创建对象。

要使用定位new特性,

  • 需要包含头文件new,它提供了这种版本的new运算符的原型;
  • 将new运算符用于提供所需地址的参数。

下面的代码段演示了new运算符的4种用法:

#include <new>
struct chaff {
char dross[20];
int slag;
};
char buffer1[50];
char buffer2[500];
int main() {
chaff* p1, * p2;
int* p3, * p4;
//常规new运算符
p1 = new chaff;//结构放在堆中
p3 = new int[20];//int数组放在堆中
//定位new运算符
p2 = new (buffer1) chaff;//结构放在buffer1中
p4 = new (buffer2) int[20];//int数组放在buffer2中
return 0;
}

上面这个示例使用两个静态数组来为定位new运算符提供内存空间。接下来使用常规new运算符和定位new运算符创建动态分配的数组。

#include <iostream>
#include <new>
const int BUF = 512;
const int N = 5;
char buffer[BUF];
int main() {
using namespace std;
double* pd1, * pd2;
int i;
cout << "Calling new and placement new:\n";
pd1 = new double[N];
pd2 = new (buffer) double[N];
for (i = 0; i < N; i++) {
pd2[i] = pd1[i] = 1000 + 20.0 * i;
}
cout << "Memory adress:\n" << "heap:" << pd1 << " static:" << pd2 << endl;
cout << "Memory contents:\n";
for (i = 0; i < N; i++) {
cout << pd1[i] << " at " << pd1 + i << ";";
cout << pd2[i] << " at " << pd2 + i << endl;
}
//----------------again---------------------------
cout << "Calling new and placement new a second time:\n";
double* pd3, * pd4;
pd3 = new double[N];
pd4 = new (buffer) double[N];
for (i = 0; i < N; i++) {
pd4[i] = pd3[i] = 1000 + 40.0 * i;
}
cout << "Memory adress:\n" << "heap:" << pd3 << " static:" << pd4 << endl;
cout << "Memory contents:\n";
for (i = 0; i < N; i++) {
cout << pd3[i] << " at " << pd3 + i << ";";
cout << pd4[i] << " at " << pd4 + i << endl;
}
//释放p1指向的内存块,在buffer中分配新的内存
cout << "Calling new and placement new a third time:\n";
delete[] pd1;
pd1 = new double[N];
pd2 = new (buffer + N * sizeof(double)) double[N];
for (i = 0; i < N; i++) {
pd2[i] = pd1[i] = 1000 + 60.0 * i;
}
cout << "Memory adress:\n" << "heap:" << pd1 << " static:" << pd2 << endl;
cout << "Memory contents:\n";
for (i = 0; i < N; i++) {
cout << pd1[i] << " at " << pd1 + i << ";";
cout << pd2[i] << " at " << pd2 + i << endl;
}
delete[] pd1;
delete[] pd3;
}

运行结果:

注意:

1. p2和buffer的地址相同,说明确实将数组p2放在数组buffer中了。p1位于动态管理的堆中。

2. 由于buffer是char指针,将cout运用于char指针时,会从第一个字符开始打印,直到遇到空字符为止。因此使用(void *)对 buffer进项强制转化。

3. 定位new运算符使用传递给它的地址,不跟踪那些内存单元被使用,也不查找未使用的内存块。因此,第二次定位new运算符分配了与第一次相同的内存块。这将一些内存管理的负担交给了程序员

//偏移量为40bytes
pd2 = new (buffer + N * sizeof(double)) double[N];

4. 用定位new运算符来创建新的类对象后,当该对象消亡时,程序并不会自动地调用其析构函数,所以必须显式地调用析构函数。这个少数的需要显示调用析构函数的情况之一。

上面的程序中没有用delete来释放定位new运算符分配地内存。buffer指定的内存是静态内存,而delete只用用于这样的指针:指向常规new分配的堆内存。也就是说,数组buffer位于delete的管辖区域之外,下面的语句将引发运行阶段错误:

delete [] pd2;//won't work

如果buffer是使用常规new运算符创建的,便可使用常规delete运算符来释放整个内存块。

char * buffer = new char[BUF];
delete [] buffer;

delete [] buffer;释放使用常规new运算符分配的整个内存块,但没有为定位new运算符在该内存块中创建的对象调用析构函数。
这种问题的解决方法是,显式地为使用定位new运算符创建的对象调用析构函数。显式地调用析构函数时,必须指定要销毁的对象。由于有指向对象的指针,因此可以使用这些指针:

String *pd = new (buffer) String("hello",5);
pd->~String();

需要注意的是,对于使用定位new运算符创建的对象,应以与创建顺序相反的顺序进行删除。原因在于,晚创建的对象可能依赖于早创建的对象。另外,仅当所有对象都被销毁后,才能释放用于储存这些对象的缓冲区。

5.  new运算符只是返回传递给它的地址,并将其强制转换为void *,以便能够赋给任何指针类型。

C++笔记(8)常规new运算符和定位new运算符的更多相关文章

  1. C++ 定位new运算符

    这里说的定位new运算符,是一种相对于普通的new运算符,可以指定内存地址的运算符,程序直接使用我们提供的地址,不管它是否已经被使用,而且可以看到新值直接覆盖在旧值上面. 定位new运算符直接使用传递 ...

  2. PYTHON 100days学习笔记002:语言元素-数字变量与运算符

    参考文章: Python 变量类型 Python 运算符 Day02 - 语言元素 1. 指令和程序 计算机的硬件系统通常由五大部件构成,包括:运算器.控制器.存储器.输入设备和输出设备.其中,运算器 ...

  3. 【读书笔记】C#高级编程 第七章 运算符和类型强制转换

    (一)运算符 类别 运算符 算术运算符 + - * / % 逻辑运算符 & | ^ ~ && || ! 字符串连接运算符 + 增量和减量运算符 ++ -- 移位运算符 < ...

  4. PHP中的运算符---位运算符、递增递减运算符、三元运算符、字符串运算符、数组运算符、类型运算符、错误控制运算符

    1.位运算符 位运算符用来对整型数的指定位进行置位,如果被操作数是字符串,则对该字符串的ASCII码值进行操作. 运算类型 运算符 举例 结果 按位与 & $a & $b 将$a 与 ...

  5. 【&】位与运算符【|】位或运算符之权限控制算法

    [&]位与运算符: 按位与运算符"&"是双目运算符. 其功能是参与运算的两数各对应的二进位相与.只有对应的两个二进位均为1时,结果位才为1 ,否则为0.参与运算的数 ...

  6. JS代码放置位置、变量与数据类型、运算符与逻辑表达运算符

    内容简要: 1.JS代码放置位置的问题: 2.变量与数据类型: 3.运算符与逻辑表达式的运算符   我的位置 全局问题:为何在网页推荐位置(一般在<head></head>内部 ...

  7. C语言杂谈(二)自增运算符++与间接访问运算符*的结合关系和应用模式

    自增运算符++有前缀和后缀两种,在搭配间接访问运算符*时,因为顺序.括号和结合关系的影响,很容易让人产生误解,产生错误的结果,这篇文章来详细分析一下这几种运算符的不同搭配情况. ++.--和*的优先级 ...

  8. C++运算符重载——重载二元运算符

    1.重载二元操作符的方法 二元运算符又称为双目运算符,即需要2个操作数的运算符,例如 + - * / 等. 运算符重载可以分为3种方式:类的非静态成员函数.类的友元函数.普通函数. 例如有 2 个操作 ...

  9. 【Shell脚本学习10】Shell运算符:Shell算数运算符、关系运算符、布尔运算符、字符串运算符等

    Bash 支持很多运算符,包括算数运算符.关系运算符.布尔运算符.字符串运算符和文件测试运算符. 原生bash不支持简单的数学运算,但是可以通过其他命令来实现,例如 awk 和 expr,expr 最 ...

  10. C++重载流插入运算符和流提取运算符【转】

    C++的流插入运算符“<<”和流提取运算符“>>”是C++在类库中提供的,所有C++编译系统都在类库中提供输入流类istream和输出流类ostream.cin和cout分别是 ...

随机推荐

  1. css 中的BFC

    一.是什么 我们在页面布局的时候,经常出现以下情况: 这个元素高度怎么没了? 这两栏布局怎么没法自适应? 这两个元素的间距怎么有点奇怪的样子? ...... 归根究底是元素之间相互的影响,导致了意料之 ...

  2. oracle表名、字段名等对象的命名长度限制(报错:ORA-00972: 标识符过长)

    oracle表名.字段名等对象的命名长度限制(报错:ORA-00972: 标识符过长) 简单来说,出现了ORA-00972: 标识符过长的错误 找来找去发现是自己的中间表名太长导致的 Oracle数据 ...

  3. 力扣534(MySQL)-游戏玩法分析Ⅲ(中等)

    题目: 需求:请编写一个 SQL 查询,同时报告每组玩家和日期,以及玩家到目前为止玩了多少游戏.也就是说,在此日期之前玩家所玩的游戏总数.详细情况请查看示例. 查询结果格式在以下示例中: 对于 ID ...

  4. DTCC 2020 | 阿里云赵殿奎:PolarDB的Oracle平滑迁移之路

    简介: Oracle兼容性是业务客户从Oracle生态迁移到PolarDB生态的第一步也是至关重要的一步,PolarDB通过不断沉淀支持大量实际业务的真实Oracle兼容性功能,确保客户业务可以真正做 ...

  5. KubeNode:阿里巴巴云原生 容器基础设施运维实践

    简介: 目前 KubeNode 已经覆盖了阿里巴巴集团的所有的 ASI 集群,接下来,将随着阿里巴巴集团"统一资源池"的项目,推进 KubeNode 覆盖更大的范围.更多的场景,让 ...

  6. 最佳实践丨云上虚拟IDC(私有池)如何为客户业务的确定性、连续性保驾护航

    ​简介: 企业业务上云后,还面临特定可用区购买云上特定计算产品实例失败的困境?云上私有池pick一下 Why 云上业务为什么需要资源确定性.服务连续性 云计算正朝着像水电煤一样的基础设施演进,支持用户 ...

  7. [Go] Golang Context 包的注意点

    Golang context 包,定义了context 类型,携带截止日期,取消信号,和其它跨API请求作用域及进程间的值. 到服务器的请求应该创建一个 Context,服务器对外的调用应该接受一个C ...

  8. [FAQ] Error: com.mysql.jdbc.Driver not loaded. :jdbc_driver_library

    以上问题出现在 logstash.conf 未配置好 MySQL 的 JDBC 驱动时导致的错误提示. 首先,下载好 MySQL JDBC 驱动库,可以放到 logstash.conf 所在当前目录或 ...

  9. 修改python别名

    修改Python别名 发现课程提供的启动python程序的命令均为python3,而我们的环境中只能用python来启动 Python 解释器,可以通过修改python的别名来实现不同名称调用同一个程 ...

  10. dotnet 5 的 bin 文件夹下的 ref 文件夹是做什么用的

    本文来和大家聊聊在 dotnet 5 和 dotnet 6 或更高版本的 dotnet 构建完成,在 bin 文件夹下,输出的 ref 文件夹.在此文件夹里面,将会包含项目程序集同名的 dll 文件, ...