在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
引用的工作方式是什么呢 不纠缠于语法的解释,看代码和汇编结果最直接。举下面这个小例子程序:(gcc -masm=hello -S main.cpp可以得到汇编代码) #include<stdio.h> int x=3; int f1(){return x;} int& f2(){return x;} int main(){ int a=f1(); int y=f2(); y=4;//仍然有x=3 int&z=f2(); z=5; printf("x=%d,y=%d",x,y);//z改变了x return 0; } 输出是什么呢? x=5,y=4 分析: f2是个返回引用的函数,当且仅当int&z =f2()的时候才是真的返回引用,int y=f2()返回的仍然是一个值的拷贝。汇编代码如下(部分) ----------------------------------------------------------------------------------- f1和f2的定义: .globl __Z2f1v .def __Z2f1v; .scl 2; .type 32; .endef __Z2f1v: push ebp mov ebp, esp mov eax, DWORD PTR _x f1()返回一个值的拷贝 pop ebp ret .align 2 .globl __Z2f2v .def __Z2f2v; .scl 2; .type 32; .endef __Z2f2v: push ebp mov ebp, esp mov eax, OFFSET FLAT:_x f2()返回的就是一个地址,不是值 pop ebp ret .def ___main; .scl 2; .type 32; .endef .section .rdata,"dr" 我们看一下main函数 _main: push ebp mov ebp, esp sub esp, 40 and esp, -16 mov eax, 0 add eax, 15 add eax, 15 shr eax, 4 sal eax, 4 mov DWORD PTR [ebp-16], eax mov eax, DWORD PTR [ebp-16] call __alloca call ___main call __Z2f1v -> 调用f1(), 返回值放在eax mov DWORD PTR [ebp-4], eax -> eax赋值给a call __Z2f2v mov eax, DWORD PTR [eax] -> 调用f2(), 返回x的值拷贝放在eax mov DWORD PTR [ebp-8], eax -> eax赋值给y mov DWORD PTR [ebp-8], 4 -> 立即数"4"赋值给y. y的改变不会改变x!!!!!! call __Z2f2v mov DWORD PTR [ebp-12], eax -> 调用f2(), 返回x的地址给z mov eax, DWORD PTR [ebp-12] -> x的地址放入eax mov DWORD PTR [eax], 5 -> 赋值5给eax指向的地址x mov eax, DWORD PTR [ebp-8] //以下是printf的调用 mov DWORD PTR [esp+8], eax mov eax, DWORD PTR _x mov DWORD PTR [esp+4], eax mov DWORD PTR [esp], OFFSET FLAT:LC0 call _printf mov eax, 0 leave ret .def _printf; .scl 2; .type 32; .endef
(九)虚拟继承有什么样子的内存模型 研究了一下虚拟继承时,对象的内存分布模型,写了下面这个小程序 #include<stdio.h> struct A {int x;int y; }; struct B : virtual public A { int a; B(){x=1;y=2;a=55;} }; struct C : virtual public A { int b; C(){x=3;y=4;b=66;} }; struct D : public B, public C { }; int main(void) { A a; B b; C c; D d; D *pd = &d; C *pd_c =(C*)(&d); B *pd_b =(B*)(&d); A *pd_a =(A*)(&d); printf("%d,%d,%d,%d\n",sizeof(a),sizeof(b),sizeof(c),sizeof(d)); printf("%p,%p,%p,%p\n",pd,pd_c,pd_b,pd_a); int *pd2=(int*)pd; printf("%p,%d,%p,%d,%d,%d\n",**((int**)(pd2)),*(pd2+1),**((int**)(pd2+2)),*(pd2+3),*(pd2+4),*(pd2+5)); return 0; } 输出 8,16,16,24 0022FF20,0022FF28,0022FF20,0022FF30 00000008,55,00000000,66,3,4 结论:D的内存分布像是这样(堆栈从高到低),vbptr表示虚基类量偏移指针 |A.y| |A.x| |C.b| |C.vbptr| |B.a| |B.vbptr| 其中bvptr是virtual public类型的对象中,虚基类的偏移量。这里C.vbptr=0,B.vbptr=8.对于d来说,C::C()在B::B()之后调用,所以(x,y)=(3,4) 因此按顺序输出D的内存内容就得到(8,55,0,66,3,4)
(十)混合编程时的初始化顺序 (1)ctor,dtor和atexit的调用顺序 #include<stdio.h> #include<stdlib.h> class a{ int ii; public: explicit a(int i){ ++count; ii=i; printf("ctor i=%d\n",ii); atexit(f); } ~a(){printf("dtor i=%d\n",ii);} static void f(){printf("f() count=%d\n",count);} static int count; }; int a::count=0; void g(){ a a2(2);//注意,如果a对象声明在一个循环中,那么循环执行N次a的构造函数就会调用N次!! printf("after g() a ctor\n"); } a a3(3);//最外层的对象 int main(void){ a a1(1);//次外层的对象 atexit(g); return 0; } 运行输出 ./a.out ctor i=3 ctor i=1 dtor i=1 ctor i=2 after g() a ctor dtor i=2 f() count=3 f() count=3 dtor i=3 f() count=3 (2)一个程序本质上都是由 bss段、data段、text段三个组成的。这样的概念,不知道最初来源于哪里的规定,但在当前的计算机程序设计中是很重要的一个基本概念。而且在嵌入式系统的设计中也非常重要,牵涉到嵌入式系统运行时的内存大小分配,存储单元占用空间大小的问题。 在采用段式内存管理的架构中(比如intel的80x86系统),bss段(Block Started by Symbol segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域,一般在初始化时bss 段部分将会清零。bss段属于静态内存分配,即程序一开始就将其清零了。 比如,在C语言之类的程序编译完成之后,已初始化的全局变量保存在.data 段中,未初始化的全局变量保存在.bss 段中。 在《Programming ground up》里对.bss的解释为:There is another section called the .bss. This section is like the data section, except that it doesn’t take up space in the executable. text和data段都在可执行文件中(在嵌入式系统里一般是固化在镜像文件中),由系统从可执行文件中加载;而bss段不在可执行文件中,由系统初始化。 例子: (windows+cl) 程序1: int ar[30000]; void main() ...... 程序2: int ar[300000] = {1, 2, 3, 4, 5, 6 }; void main() ...... 发现程序2编译之后所得的.exe文件比程序1的要大得多。发现在程序1.asm中ar的定义如下: _BSS SEGMENT ?ar@@3PAHA DD 0493e0H DUP (?) ; ar _BSS ENDS 而在程序2.asm中,ar被定义为: _DATA SEGMENT ?ar@@3PAHA DD 01H ; ar DD 02H DD 03H ORG $+1199988 _DATA ENDS 区别很明显,一个位于.bss段,而另一个位于.data段,两者的区别在于:全局的未初始化变量存在于.bss段中,具体体现为一个占位符;全局的已初始化变量存于.data段中;而函数内的自动变量(每个编译器都不同,cl是0xCCCCCCCC)都在栈上分配空间。.bss是不占用.exe文件空间的,其内容由操作系统初始化(清零);而.data却需要占用,其内容由程序初始化,因此造成了上述情况。 (3)例子:一个很特殊的strcpy例子,可以让程序崩溃的: #include<stdio.h> #include<string.h> #include<ctype.h> void f(char* s){ int len=strlen(s); char buf[len+1]; strcpy(buf,s); printf("s=%s,buf=%s\n",s,buf); strcpy(s,buf); printf("after strncpy\n"); } int main(void){ f("abc"); return 0; } ./a.out s=abc,buf=abc 段错误 如果我把main函数的内容改为 char b[]="abc";//堆栈分配 f(b); 运行就没有问题。 原因: “abc”是存在只读属性数据区,不能做strcpy的目的地 数组内存分配在栈上,可作修改,所以数组名可以做strcpy的第一个参数。 (4)如果循环里面要用到某个类对象(默认构造函数),最好把对象的声明移动到循环外面,否则这个对象被初始化的次数就是循环的次数 #include<stdio.h> class c{ public: c(){printf("ctor\n");} }; int main(void){ int i=10; while(i--){ c c1; } return 0; } 运行结果就是"ctor"被打印10次
(十一)数组和指针的异同 这个是C/C++中最容易混淆,最容易头晕的一个话题。 我们先从一个简单的例子看起(一维数组) void f(char* buf); | void f(char* buf); int main(...){ | int main(...){ char buf="abc"; | char* pbuf="abc"; f(buf); | f(pbuf); ->相同的生成代码 buf[2]='x'; | pbuf[2]='x' ->不同的生成代码 上面这两个程序有区别吗? 答案是: (1)对于一维数组的处理,传递参数的地时候统统作为指针来看待,也就是f(buf)的调用被编译器等效成了 char* pbuf="abc",f(pbuf)这样的调用。 (2)对于寻址和赋值: buf[2] 是编译器计算(buf的地址+2),放入x pbuf[2]是编译器计算pbuf的地址,得到pbuf中的值,再以这个值为基地址,+2,放入x 也就是说,pbuf的赋值语句是2次跳转的,效率比不上buf[2]这样的语句。 -------------------------------------------------------------- 考虑复杂一点的情况,多维数组怎么办? int main(...){ int buf[2][3];//这个buf数组在内存中仍然是1维连续内存! 那么buf[10][10]=6;这样的语句是如何计算的呢? buf的结构被看成一个矩阵被拉直为行向量的表示,10行10列,buf[1][2]的地址就是: 第二行的起始(1*10)+第3个元素的偏移(2),等效于((int*)(buf))[12]。 这样说很清楚了吧,如果我们要把buf传递给一个函数作为参数,怎么办呢? 只需要保证编译器能看出,这个被拉直的,2维数组,每一行多少个元素: void f(int buf[][10]){ buf[1][2]=6;//编译器能够通过f的形式参数声明来决定buf[1][2]是从buf偏移多少。 ... 上面这个声明和void f(int buf[10][10])甚至void f(int buf[20][10])是等效的。因为我们只需要知道每行包括多少个元素,至于有多少行,(数组多大),不是编译器控制的,是程序元的责任。 -------------------------------------------------------------- 如果f的声明是f(int buf[][])呢? 它等效于f(int *buf[])或者f(int ** ppbuf)这样的声明,传入参数必须是一个真正的2维数组。像下面这样 int** buf=new int*[10]; for(int i=0;i<10;++i)buf[i]=new int[10]; f(buf); buf数组本身必须是一个指针数组,buf[1][2]这样的计算是: (a)计算buf[1]的值 (b)这个值是一个地址,指向一个数组,取这个数组的偏移量2中的值。 如果我混用f(int buf[][10])和f(int buf[][]),我就会得到一个编译警告或者错误: void f2(int ppi[][2]){} void f3(int *ppi[2]){} int p2[3][2]={ {1,2},{3,4}, }; f2(p2);正确的用法 f3(p2);警告:传递参数 1 (属于 ‘f3’)时在不兼容的指针类型间转换。 由于f3的生成代码是2次跳转,因此传入p2作为参数的时候,会把一个真正的数组元素的值作为地址看待,再次计算一个内存地址偏移量中的值,可能导致程序崩溃。 再看一个程序,看看运行的结果是什么。 int main(void) { int arr[2][3] = { {0,1,3}, {4,5,6} }; int i1=(int)arr; int i2=(int)(arr+1); printf("i2-i1=%d\n",i2-i1); printf("%x\n",arr+1); printf("%x\n",*(arr+1)); printf("%d\n",**(arr+1))); return 0; } 关于这个话题,最好的相关参考文献:《C专家编程》
(十二)const限定的传递性 (1)如何理解复杂const的指针类型定义? char * const cp; ( * 读成 pointer to ) 等效于const char* p cp is a const pointer to char const char * p; p is a pointer to const char; 先向右看, 再向左看, thinking in C++ 说的很清楚了 (2)const对于函数声明: 是个很严格的概念,const对象被调的过程必须保证其使用了带const的函数。例如: > cat t.cpp struct a{ int x; bool operator==(const a& ia){return x==ia.x;}//这里是编译不过的!!!!!!!! }; bool f(const a& ia, const a& ib){ return ia==ib;//因为这里的==操作了const a&,而operator==没有被定义为const函数 } int main(int argc, char *argv[]){ return 0; } 问题解决的方案: bool operator==(const a& ia) const {return x==ia.x;} f中被比较的a类对象是const的,传递给operator==函数,函数不能改变它,因此==必须也是const的。
(十三)数据类型的限定性检查 (1)使用C风格的初始化 > cat t.cpp #include<stdio.h> struct e{//结构体有3个成员 int x; int y; e& operator=(const e& ie){*this=ie;} ~e(){} }; int main(void){ e buf[]={//用两个成员的{}来初始化 {1,2}, {1,3}, {1,4} }; printf("%d %d %d\n",buf[0].y,buf[1].y,buf[2].y); return 0; } 编译没有问题,但是如果增加了e的构造函数,编译就出错。 原因:只有那些没有定义构造函数且所有数据成员全部为public的类,才可以应用C风格的初始化方式(大括号方式),这是为了与C兼容 (2)成员函数中的static变量,作用和类的static变量相同 #include<stdio.h> struct B{ void inc(){ static int i=0; printf("%d\n",++i); } }; int main(void){ B b1,b2; b1.inc(); b2.inc(); } > ./a.out 1 2 (3)explicit的作用域 class i{ public: int* a;int b; i(int* x){ printf("ctor\n"); a=x; } i(const i& ii){printf("copy ctor\n");a=ii.a;} explicit i(){printf("ctor default\n");} i& operator=(const i& ii){printf("operator\n"); a=ii.a;} }; int main(int argc, char *argv[]){ i i1; int x=20; int *b=&x; i1=b; printf("i1.a=%d,p=%d\n",*(i1.a),i1.a); return 0; } 程序像的输出是 ctor default ctor operator i1.a=20,p=2293596 这里int* b=&x被隐式转换成了i的对象i1=b,但是我的无参数构造函数 explicit i()...是加了explicit关键字的,为什么仍然编译通过并正确执行呢? 解释: explicit 只对有一个参数(或者有多个参数,但除了第一个,其他参数都有默认值)的构造函数起作用 (4)dynamic_cast的有效性: 只要dynamic_cast输入的参数是一个内容正确的左值,哪怕它是其他类型的指针或者引用转型过来的,只要它本身内容正确(指向了正确的虚函数表),RTTI就能成功。 #include <cstdlib> #include <cstdio> #include <typeinfo> using namespace std; class A{}; class C{ public: virtual void g(){} }; class D:public C{}; int main(int argc, char *argv[]) { D d ; A *a=(A*)&d; C* pa=(C*)a; C& pc=*pa; try{ C& pc2=dynamic_cast<C&>(pc); D& pd=dynamic_cast<D&>(pc); }catch(bad_cast){ printf("bad_cast\n"); }catch(...){ printf("other exception\n"); } return EXIT_SUCCESS; } 程序不会抛出任何异常。如果我把"D d"的声明改为"C d"的声明,"D& pd=dynamic_cast<D&>(pc)"就会抛出std::bad_cast异常 Plus: dynamic_cast的输入参数如果无效,是指针是返回NULL,是引用时抛出bad_cast异常 (5)union里面的struct必须是plain old data,不能含有ctor,dtor,operator=函数 (6)#define宏定义的变量,在编译之后消失了,不利于理解程序合调试,因为没有符号存在。C++为了解决这个问题引入了enum类型,这个类型信息在编译时作为const常量存在,编译后仍然存在符号表信息,利于调试。 #define MONDAY 1 class b{ public: const static int i=0;//if not const, compile error enum{friday=5};//equal to const int }; const char* buf="abc"; int main(void){ buf="xyz"; int x=b::friday; int y=MONDAY; return 0; } Plus: 注意类当中的static变量,如果加const可以在声明时初始化,不加const必须在类声明之外初始化。 (十四)使用STL时的类型限制 (1)自定义迭代器需要注意的问题 下面这个这个程序的目的是,自定义一个迭代器的类型,模仿STL的访问方式,打印数组的全部内容。 #include<cstdio> #include<cstdlib> #include<algorithm> #include<iterator> #include<iostream> using namespace std; class array{ int * pi; public: array(){ pi=new int[5]; pi[0]=3; pi[1]=44; pi[2]=5; pi[3]=1; pi[4]=26; } virtual ~array(){ if(pi){delete[] pi;pi=0;} } class Iter{//自己实现的一个迭代器 int* data; public: Iter(int* i){data=i;} Iter(){data=0;} Iter& operator=(const Iter& i){data=i.data;} bool operator!=(const Iter& i){return data!=i.data;} int operator*(){return *data;} void operator++(){++data;} void operator--(){--data;} }; Iter begin(){return Iter(&pi[0]);} Iter end(){return Iter(&pi[5]);} }; int main(int argc, char *argv[]) { array l; array::Iter it; for(it=l.begin();it!=l.end();++it){cout<<*it<<' ';} cout<<'\n'; //copy(l.begin(),l.end(),ostream_iterator<int>(cout, " ")); //不加这一句,运行没有问题 return 0; } ->问题: 我把上面那行注释了的"copy(l.begin(),l.end(),ostream_iterator<int>(cout, " ")); "变成有效,编译就过不去了 ->原因的解释: 因为,用于标准库算法的迭代器内需要定义有五个公有的类型:iterator_category, value_type, difference_type, distance_type, pointer, reference,其方法有两种:(1)手动定义这五个类型(2)从std::iterator继承 引用《C++程序设计(特别版)》里的一句话: “内部类型int *就是int[]的一个迭代器,类型list<int>::iterator是list类的一个迭代器”不是对内部类型没有要求,而是对内部类型的迭代器有一个默认的解释。iterator_traits 有关于指针类型的偏特化版本. (2)ostream_iterator, 传给cout的对象必须能强制转化为基本类型,或者重载<< #include<algorithm> #include<iterator> #include<iostream> using namespace std; struct e{ float v;char c; operator float()const {return v;} //operator char() const {return c;} //should conflict with operator float() }; int main(int argc, char *argv[]) { int l=4; e p[]={ {1.5,'x'}, {0.5,'a'}, {1.2,'b'}, {0.7,'y'} }; copy(p,p+l,ostream_iterator<e>(cout," ")); cout<<'\n'; return 0;} 上面的operator float()和operator char()只能用一个,因为互相冲突 (3)friend的一个使用场景 例如,要设计一个单线程的简单singleton,我把 ctor,dtor,copyctor,"="重载ctor都声明为private, 用一个静态函数来创建instance。然后由于我只有创建函数没有销毁函数,我使用auto_ptr来声明这个对象,让编译器来完成对象的释放。 class s{ static auto_ptr<s> pInst; s(){} ~s(){} s(const s& os){printf("s.copy ctor\n");} s& operator = (const s& os){printf("s.operator= called\n");} public: static s& getInst(){ if(pInst.get()==0) pInst.reset(new s()); return *pInst; } }; auto_ptr<s> s::pInst; 上面这个程序是编译不通过的,因为auto_ptr的析构函数去delete <s>,而s的析构函数是私有的,因此在s类的最后面我们还需要加上 friend class auto_ptr<s>; 这样的语句才能编译通过。一个替代的解决方案是不使用auto_ptr,而去使用atexit这样的函数注册一个销毁函数,让程序退出时系统自动调用。 (4) class Iter:public std::iterator<bidirectional_iterator_tag, int> 这样的话就能 copy(l.begin(),l.end(),ostream_iterator<int>(cout, " "));来打印到标准输出 因为: 用于标准库算法的迭代器内需要定义有五个公有的类型:iterator_category, value_type, difference_type, distance_type, pointer, reference,其方法有两种: 1.手动定义这五个类型 2.从std::iterator继承
(十五)迭代器自身的类型 在用STL编写庞大程序的时候,如何才能知道一个迭代器指向的对象的真正类型呢? 能否把编译时确定的信息(特化的类型)保存下来以后可以用? 我们的法宝是使用一个iterator_traits对象,它是iterator的内置对象,保留了特化的类型。(通过typedef一个通用的名字来做到的) 对于stl::iterator_traits的一个非常好的解释: 它就是得到一系列的typedef来指示iterator指向对象的类型,原文来自http://msdn.microsoft.com/en-us/library/zdxb97eh(VS.80).aspx template<class Iterator> struct iterator_traits { typedef typename Iterator::iterator_category iterator_category; typedef typename Iterator::value_type value_type; typedef typename Iterator::difference_type difference_type; typedef typename Iterator::pointer pointer; typedef typename Iterator::reference reference; }; template<class Type> struct iterator_traits<Type*> { typedef random_access_iterator_tag iterator_category;//那种类型的迭代器 typedef Type value_type;//--------------->最关键的地方!!!!保存类型信息!!!! typedef ptrdiff_t difference_type; typedef Type *pointer; typedef Type& reference; }; template<class Type> struct iterator_traits<const Type*> { typedef random_access_iterator_tag iterator_category; typedef Type value_type; typedef ptrdiff_t difference_type; typedef const Type *pointer; typedef const Type& reference; }; 例子程序 // iterator_traits.cpp // compile with: /EHsc(该选项仅对于VC编译器) #include <iostream> #include <iterator> #include <vector> #include <list> using namespace std; template< class it > void function( it i1, it i2 ) { iterator_traits<it>::iterator_category cat; cout << typeid( cat ).name( ) << endl; while ( i1 != i2 ) { iterator_traits<it>::value_type x; x = *i1; cout << x << " "; i1++; }; cout << endl; }; int main( ) { vector<char> vc( 10,'a' ); list<int> li( 10 ); function( vc.begin( ), vc.end( ) ); function( li.begin( ), li.end( ) ); } Output: struct std::random_access_iterator_tag a a a a a a a a a a struct std::bidirectional_iterator_tag 0 0 0 0 0 0 0 0 0 0 Plus: iterator不但可以用来访问元素,也可以用于赋值 typedef vector<int> vi; vi v(3); vi::iterator it=v.begin(); for(it;it!=v.end();++it)*it=9; copy(v.begin(),v.end(),ostream_iterator<int>(cout,"_"));
(十六)运行时的类型信息 (1)typeid的作用,可以得到动态运行时的信息(对于多态类) >cat type.cpp #include<iostream> using namespace std; class Base { public: virtual void vvfunc() {} }; class Derived : public Base {}; using namespace std; int main() { Derived* pd = new Derived; Base* pb = pd; cout << typeid( pb ).name() << endl; //prints "class Base *" 静态信息 cout << typeid( *pb ).name() << endl; //prints "class Derived" 动态信息 cout << typeid( pd ).name() << endl; //prints "class Derived *"静态信息 cout << typeid( *pd ).name() << endl; //prints "class Derived" 动态信息 delete pd; } 在solaris上面CC的输出结果是 > ./a.out Base* Derived Derived* Derived typeid 将返回一个派生类的type_info引用。但是expression必须指向一个多态类,否则返回的将是静态类信息。此外,指针必须被提领,以便使用它所指向的对象,没有提领指针,结果将是指针的type_info(这是一个静态信息),而不是它所指向的对象的type_info (2)static_cast能够处理类型运算符重载并解析 一个类,重载(char*)强制类型转换运算符,当我使用static_cast<char*>()的时候,该重载仍然是有效的。 #include <cstdio> #include <cstdlib> #include <iostream> using namespace std; struct s{ char buf[4]; s(){strcpy(buf,"abc");} operator char*(){return "kkk";} }; struct c{ char *buf; c(){buf="xyz";} }; int main(void){ s s1; printf("string1 =%s\n",&s1);//打印字符串,效果同s.buf c c1; printf("string2 =%s\n",*((char**)&c1));//打印字符串 printf("string3 =%s\n",(char*)s1); cout<<static_cast<char*>(s1)<<'\n';//这里,重载的(char*)起了作用 return 0; } (3)虚函数表的存储结构研究: #include<stdio.h> class B{//对于含有虚函数的类,内存结构中的首元素是指向虚表的指针。 int x; virtual void f(){printf("f\n");} virtual void g(){printf("g\n");} virtual void h(){printf("h\n");} public: explicit B(int i) {x=i;} }; typedef void (*pf)(); int main(void){ B b(20); int * pb=(int*)&b; printf("private x=%d\n",pb[1]); pf *pvt=(pf*)pb[0];//虚函数表指针是b的第一个元素,它指向一个保存指针的表 pf f1=(pf)pvt[0]; pf f2=(pf)pvt[1]; pf f3=(pf)pvt[2]; (*f1)(); (*f2)(); (*f3)(); printf("pvt[3]=%d\n",pvt[3]);//虚函数表结束符号,gcc是0 return 0; } 程序输出 private x=20 f g h pvt[3]=0
(十七)new/delete重载 (1)new和delete运算符的重载,可以用来跟踪代码中内存申请和释放的过程。 下面的例子是重载类中的new和delete class a{ public: void* operator new(size_t){ printf("a::new\n"); return ::new a; } void* operator new[](size_t l){ printf("a::new[%d]\n",l); return ::new a[l]; } void operator delete(void* p){ printf("a::delete\n"); ::delete (a*)p; } void operator delete[](void* p){ printf("a::delete[]\n"); ::delete[] (a*)p; } }; int main(void){ a* pa=new a; delete pa; pa=new a[2]; delete[] pa; return 0; } 输出 > CC t.C && ./a.out a::new a::delete a::new[2] a::delete[] (2)replacement new需要注意的地方。例如 class c{ int x; public: explicit c(int ix){x=ix;printf("ctor\n");} ~c(){printf("dtor\n");} }; int main(void){ try{ char mem[sizeof(c)*2]; c* pc1=new(mem) c(2); c c3(4); //加上这句以后,delete pc1就是非法退出, 不加这句就没事........................................... delete pc1;//不能去delete内存池中的东西,否则出错 ???????? //pc1->~c();//用显示调用析构函数而不用delete总是安全的。 }catch(...){ printf("get exception\n"); } return 0; } 上面的c* pc1=new(mem) c(2); 和delete pc1;//不能去delete内存池中的东西,否则出错 ???????? 这种方式就是错误的,因为你用的是new的放置语法,而放置语法要求显式的调用析构函数,同时不用的内存需要自己释放时可以free掉,但是在堆栈上的自己费心。更多详细资料可以问问《C++程序设计语言(特别版)第2版》 (3)在很多实现中,不考虑构造和析构的话,new/malloc,delete/free是等效的,举VC的例子 #if !_VC6SP2 || _DLL void *__CRTDECL operator new[](size_t count) _THROW1(std::bad_alloc) { // try to allocate count bytes for an array return (operator new(count)); } #endif /* !_VC6SP2 || _DLL */ _C_LIB_DECL int __cdecl _callnewh(size_t size) _THROW1(_STD bad_alloc); _END_C_LIB_DECL void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc) { // try to allocate size bytes void *p; while ((p = malloc(size)) == 0) if (_callnewh(size) == 0) { // report no memory static const std::bad_alloc nomem; _RAISE(nomem); } return (p); } (4)在重载的operator new调用的时候,如果分配的是有析构函数的对象数组,那么传进来的size_t会多出一个整数字节的大小,用于记录数组大小(delete[] 需要循环调用各对象的析构函数) 下面这个小程序是重载全局的new/delete操作符来实现对象的分配和释放: #include <new> #include <cstdio> #include <cstdlib> using namespace std; void* operator new(size_t size) throw(bad_alloc) { printf("operator new:%d Byte\n",size); void* m= malloc(size); if(!m) puts("out of memory"); return m; } void operator delete(void* m)throw(){ puts("operator delete"); free(m);} class B{ int s; public: B(){/*puts("B::B()");*/} ~B(){/*puts("B::~B()");*/} }; int main(int argc, char* argv[]){ int* p = new int(4); delete p; B* s = new B; delete s; B* sa = new B[10]; delete []sa; int* pi=new int[3]; delete []pi; return 0; } 程序的输出是 > gcc n.C && ./a.out operator new:4 Byte operator delete operator new:4 Byte operator delete operator new:48 Byte ->问题出在这里,new为类指针数组分配的时候,4x10应该是10个字节,多出来的8个字节是做什么的? operator new:12 Byte 回答: 是编译的时候就做到了. 如: class B xxxxxxxxxxxxx; p=new B[num]; 那么编译器会处理成(注意:不同的编译器会有所不同): +--------------------------------------------------------------+ |num|var[0]|var[1]|var[2]|var[3]|........|var[num-1]| +--------------------------------------------------------------+ push n ;n=num*var_size+4 call 我重载的new .................................... push B::~B()的地址 push B::B()的地址 *((int*)p)=num; ((int*)p)++; push num push var_size push p call vector_constructor_iterator ;这里会循环调用B::B(),次数是num +--------------------------------------------------------------+ 对类类型,delete一个数组时(比如,delete []sa;),要为每一个数组元素调用析构函数。但对于delete表达式(比如,这里的delete []sa),它并不知道数组的元素个数(只有new函数和delete函数知道)。因此,必须有一种手段来告诉delete表达式的数组大小是多少。那么一种可行的方式就是,多分配一个大小为4字节的空间来记录数组大小,并可以约定前四字节来记录大小。那么,由new函数分配的地址与new表达式返回的地址应该相差4个字节(这可以写程序来验证)。对于非类类型数组和不需要调用析构函数的类类型数组,这多于的四字节就不需要了。 (5)同理,可以重载全局的new/delete,形如
void* operator new( size_t size ){ if( 0 == size ) // 注意!!!! size = 1; while(1){ 分配size字节内存; if(分配成功) return 指向内存的指针; new_handler g= set_new_handler(0); set_new_handler(g); if( g)(*g)(); else throw std::bad_alloc(); } } void operator delete( void* p){ if( 0 == p) // 须要注意 return; ... } 上面的new_handler是用户自定义的全局set_new_handler处理函数,newhandler形式是: void mynewhandler(){ if( 使得operator new成功 ) { 例如等待一段时间,再次分配内存 return; } // 主动退出 或 abort/exit 直接退出程序 或 set_new_handler(其他newhandler或者0); 或 set_new_handler(0) 或 throw bad_alloc()//比较好 } (十八)如何拷贝一个文件----标准C/C++运行库里面没有拷贝文件的函数,必须自己完成 (1)标准c的逐字节拷贝 #include<stdio.h> int main(void){ FILE* pin=fopen("in.data","rb"); FILE* pout=fopen("out.data","wb"); int c; while((c=fgetc(pin))!=EOF){ fputc(c,pout);} fclose(pin); fclose(pout); return 0; } (2)Iostream的多字节拷贝
#include<iostream> #include<fstream> using namespace std; int main(void){ ifstream fi; fi.open("in.data",ios::binary); ofstream fo; fo.open("out.data",ios::binary); char buf[1024]; do{ fi.read(buf,sizeof(buf)); fo.write(buf,fi.gcount()); }while(fi.good()); fi.close(); fo.close(); return 0; } 可以把do-while的循环用一句话代替: fo<<fi.rdbuf() (3)STL算法拷贝,逐字节进行
#include<fstream> #include<iterator> #include<algorithm> using namespace std; int main(void){ ifstream fi; fi.open("in.data",ios::binary); ofstream fo; fo.open("out.data",ios::binary); copy(istreambuf_iterator<char>(fi),istreambuf_iterator<char>(),ostreambuf_iterator<char>(fo)); fi.close(); fo.close(); return 0; } |
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论