因多线程引起的new关键字说明

本文详细介绍了new关键字在多线程环境中的工作原理,包括分配内存、调用构造函数和返回地址的过程。指出new与直接调用全局函数operator new的区别,强调new会自动调用构造函数,而直接使用operator new则需要手动调用。同时,提到了placement new的使用场景及其与析构函数的关系,并提醒在重载placement new时必须匹配地重载operator delete。此外,文章还讨论了new[]在32位系统中额外分配内存用于记录长度的特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在 线程安全单例以及不要使用双检测

说了new产了3个步骤;

  1.  分配内存 
  2.  调用构造函数
  3.  把地址返回

2 和 3 由编译器来完成;

有的小伙伴不太懂,说明一下;

先给结论:

使用关键字(new,delete)和直接调用全局函数(operator new / delete ) 不一样的 , 编译器会做额外的事

new关键字调用 先::operator new 分配空间 - > 调用构造/返回

delete关键字调用::operator delete ,先调用析构 -> 释放空间

如果直接调用全局函数::operator new / delete ,则需要自己调用构造/析构;

如同placement new(定位new ,名字有点怪,不分配空间) 需要自己调用构造/析构;

 

 

如何证明 new 关键字 由编译器来完成?

首先 new 关键字将调用 , 以下3个版本的其中一个 ;

   

// 一般的 new SomeClass() , 将调用此版本;
void* operator new (std::size_t size) throw (std::bad_alloc);


//没有异常的版本 , new (nothrow) SomeClass();
void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) throw();



/*
定位new , 一般用于内存池 , stl中很多这样的
比如:
char * p = (char*)malloc(100); 申请内存
new (p) SomeClass(); //调用构造函数
*/
void* operator new (std::size_t size, void* ptr) throw();

 

下面例子中重写了成员的delete,为方便起见,先说明一下:

    //全局作用域的delete 
	void operator delete(void * p) throw()
	{
        .....
	}


	//一般的class专属的delete , 有额外一个size参数
	//为什么会有size ,  因为继承的关系,每个class 大小不一样
	void operator delete(void * p, size_t size) throw()
    {
            ....
    }   

 

一个例子说明问题:

class A
{
public:
	A(){
		printf("A() \n");
	}
	//也可用于全局作用域 (意思是 可以把这个函数剪切到全局)
	static void * operator new (size_t size)
	{
	
		void * p = malloc(size);
		printf("new addr:%p , size:%ld\n", p,size);
		return p;
	}
	//也可用于全局作用域
	static void operator delete(void * p)
	{
		puts("operator delete(void * p)");
		free(p);
	}
	//class专属的delete , 如果没有上面的delete就调用这个,
	//为什么会有size ,  因为继承的关系,每个class 大小不一样
	static void operator delete(void * p, size_t size)
	{
		printf("operator delete(void * p, size_t size) . %d\n", size);
		free(p);
	}
	~A(){ printf("~A()\n"); }
};
int _tmain(int argc, _TCHAR* argv[])
{
	
	A * 直接使用 = (A*)::operator new(sizeof(A)); // 只会分配一个地址,不会调用构造函数
//	A * 使用new关键字 = new A(); 由编译器先调用operator new, 再调用构造,以及返回

	return 0;
}

 

因此new关键字由编译器完成额外的工作;

创建对象时(调用构造函数),operator new通过new关键字隐式调用 ,前后加了一些额外的代码;

如果想又::operator new , 还能正常调用构造怎么办?

下面贴出main中的代码,其他不变:

	A * a = (A*)::operator new(sizeof(A)); //用全局的



/*
        用的placement new 直接返回地址;
        void* operator new (std::size_t size, void* ptr) throw()
        { return ptr;}
        下面2种方式来调用构造

*/
	::new (a) A();  //通过定位new, 来构造;
	a->A::A(); //也可以这样,再此调用构造

	a->~A(); //调用析构, 如果不调用,则不会析构
	::operator delete(a); //用全局的,没有调用类的delete

 

从上面代码可以看出 , new 关键子由编译器帮忙,增加了额外操作;

同理 ,不仅仅是 new 关键字, 还有delete 关键字 ,如果直接使用::operator delete 仅仅是释放,不会调用析构.

 

对于placement new 一般用于内存池 或者 需要频繁的new obj, delete obj;那么可以先malloc一块内存,然后在上面

构造析构;

同时如果重载了placement new , 则必须有一个与之参数匹配的 operator delete, 比如:


//重载了 placement new 
void * operator new (size_t size, int a, int b)
{
	printf(" operator new ( size_t size , int %d, int %d) \n  ", a, b);
	return malloc(size);
}

//必须也重载一个与上面参数类型一致的 operator delete
// 这个函数仅仅在 new 失败的时候会被自动调用
//如果不写这个函数,在vs下有警告
void operator delete(void * p, int, int) throw()
{
	printf("operator new (size_t size, int a, int b) failed\n");
	free(p);
}

int _tmain(int argc, _TCHAR* argv[])
{
	
//A 还是之前那个
	A * a = new(1,2) A(); //使用重载的

// error C2541: 'delete' : cannot delete objects that are not pointers
// delete(1,2) a; 只有当new(1,2) A() 抛出异常时被调用,你不能直接使用


//此delete 并非上面重载的那个,重载那个仅在 new (1,2) A()失败时被自动调用
//此delete 还是与往常一样, 先析构 后释放
	delete a;
	return 0;
}

 

至于new[] 在32位情况下会额外多分配4个字节给你,用于记录长度:

class A
{
public:
	A(){printf("A() \n");}
	void * operator new[](size_t size){
		void * p = malloc(size);
		printf("operator new[](size_t %ld) , %p\n", size , p);
	
		return p;
	}
	void operator delete[](void * p, size_t size)
	{
		printf("operator delete[](void * %p, size_t %ld)\n", p, size);
		free(p);
	}
	~A(){ printf("~A()\n"); }
};
int _tmain(int argc, _TCHAR* argv[])
{
	
	A * a = new A[3]; //编译器将调用A::operator new[] , 同时执行构造
	printf("a = %p\n", a);
	int * ptr = (int*)a;
	printf("数组长度 : %d\n", *(--ptr));//往前偏移4个字节
	delete[] a; //编译器将调用A::operator delete[] 前先析构
	return 0;
}
输出:
operator new[](size_t 7) , 004E90C8
A()
A()
A()
a = 004E90CC
长度 : 3
~A()
~A()
~A()
operator delete[](void * 004E90C8, size_t 1)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值