zoukankan      html  css  js  c++  java
  • C/C++内存与运行时深入研究(2)

    发现程序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(π[0]);}
    Iter end(){return Iter(π[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;
    }

    作者:BuildNewApp
    出处:http://syxchina.cnblogs.comBuildNewApp.com
    本文版权归作者、博客园和百度空间共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则作者会诅咒你的。
    如果您阅读了我的文章并觉得有价值请点击此处,谢谢您的肯定1。
  • 相关阅读:
    springMVC静态资源
    MyBatis Generator
    使用springMVC时的web.xml配置文件
    Semaphore
    spring注解驱动--组件注册
    第1章 初始Docker容器
    docker面试整理
    第5章 运输层
    验证码
    带进度条的上传
  • 原文地址:https://www.cnblogs.com/syxchina/p/2197403.html
Copyright © 2011-2022 走看看