看侯捷大佬的内存管理时的一点记录。
operator new/delete
全局的重载
::operator new/new[]/delete/delete[]可以重载,但是不能在命名空间内重载。重载之后,使用new的时候会调用::operator new。如果在类里重载了operator new,则new 类的对象时会调用类里的函数。
#include<iostream>
using namespace std;
void* operator new(size_t size)
{
cout<<"my operator new(), size is "<<size<<endl;
return malloc(size);
}
void* operator new[](size_t size)
{
cout<<"my operator new[](), size is "<<size<<endl;
return malloc(size);
}
void operator delete(void* ptr)
{
cout<<"my operator delete()\n";free(ptr);
}
void operator delete[](void* ptr)
{
cout<<"my operator delete[]()\n";free(ptr);
}
void test1()
{
int *a = new int;
int *b = new int[10];
delete a;
delete b;
}
int main(int argc, char const *argv[])
{
test1();
return 0;
}
这里delete没有调用重载的delete,不知道为啥。将指针转为void*时由会调用重载的delete,但编译时有warning。
类里的重载:
在类里重载为静态函数,因为非静态函数需要类的对象才可以调用。
例子如下:
class A{
public:
A(){}
~A(){}
static void* operator new(size_t size);
static void operator delete(void* ptr);
static void* operator new[](size_t size);
static void operator delete[](void* ptr);
};
void* A::operator new(size_t size)
{
cout<<"operator new in class A, size is "<<size<<endl;
void* ret = malloc(size);
cout<<"address is "<<ret<<endl;
return ret;
}
void A::operator delete(void* ptr)
{
cout<<"operator delete in class A"<<endl;
free(ptr);
}
void* A::operator new[](size_t size)
{
cout<<"operator new[] in class A, size is "<<size<<endl;
void* ret = malloc(size);
cout<<"address is "<<ret<<endl;
return ret;
}
void A::operator delete[](void* ptr)
{
cout<<"operator delete[] in class A"<<endl;
free(ptr);
}
void test_size()
{
cout<<sizeof(A)<<endl;
A* a = new A[10];
cout<<"address a "<<a<<endl;
uint64_t val = *((uint64_t *)a - 1);
cout<<"8 Bytes as uint64: "<<val<<endl;
delete[] a;
}
运行test_size时输出如下:
1
operator new[] in class A, size is 18
address is 0xea6d10
address a 0xea6d18
8 Bytes as uint64: 10
operator delete[] in class A
可以看到,没有成员时,类的对象占一个字节,并且new A[10]会多申请8个字节,返回的首地址是申请的内存首地址0xe36d10+0x8=a0xe36d18。这8个字节应该是64位无符号整数,等于申请的类的对象的数量。
类里元素的分布
这个感觉和结构体很像,默认的4字节对齐,从上到下,不区分公有和私有。在上边的基础上,比如:
private:
int a;
char b;
int c;
size是12.char虽然占1字节,但是为了4字节对齐,后边三个字节空着。
private:
int a;
char b;
int c;
public:
char d;
size是16
public:
int e;
private:
int a;
char b;
int c;
public:
char d;
size是20
然后打印他们的地址:
#define print_address(x) cout<< #x <<" : "<<(void*)x<<endl;
#define print_value(x) cout<< #x <<" : "<<x<<endl;
void print(){
print_address(this);
print_address(&a);
print_address(&b);
print_address(&c);
print_address(&d);
print_address(&e);
}
void test_adress()
{
A a;
a.print();
A* b= new A();
b->print();
delete b;
}
输出如下:
this : 0x61fdc0
&a : 0x61fdc4
&b : 0x61fdc8
&c : 0x61fdcc
&d : 0x61fdd0
&e : 0x61fdc0
operator new in class A, size is 20
address is 0xdc6d10
this : 0xdc6d10
&a : 0xdc6d14
&b : 0xdc6d18
&c : 0xdc6d1c
&d : 0xdc6d20
&e : 0xdc6d10
operator delete in class A
可以看到地址顺序应该是:e,a,b,c,d
,每次加4字节。
然后可以通过这个地址来读写类的成员变量,不管是不是私有,有点离谱。
//打印值
void print_val()
{
print_value(a);
print_value(b);
print_value(c);
print_value(d);
print_value(e);
}
void test_change_private()
{
A* a= new A();
a->print_val();
int* tmp = (int*)a;
tmp[0]=10;//int e
tmp[1]=20;//int a
*(char*)(tmp+2) = 'b'; //char b
tmp[3] = 40;//int c
*(char*)(tmp+4) = 'd'; //char d
cout<<"change val:\n";
a->print_val();
delete a;
}
输出结果如下(我没有初始化,第一次输出可能是随机的)
operator new in class A, size is 20
address is 0x1016d10
a : 0
b : P
c : 0
d :
e : 16847440
change val:
a : 20
b : b
c : 40
d : d
e : 10
operator delete in class A
注:类和结构体一样可以用这个#define PACKED __attribute__((packed, aligned(1)))
,用来使成员1字节对齐,也就是强制压缩,将其加在类后,再看看sizeof(A)
class A{
...
} PACKED;
输出:
17
operator new[] in class A, size is 178
address is 0x1f6d10
address a 0x1f6d18
8 Bytes as uint64: 10
operator delete[] in class A
输出的A的大小变为了17,最后一个char只占1字节,如果在最后一个char后边定义一个4字节的int,那么类的对象占字节数就是24。感觉就是类里元素还是4字节对齐,到了最后一个元素就是1字节(不考虑对齐)。
注:编译器为mingw64/bin/g++.exe