当前位置: 首页 > news >正文

做视频网站的上市公司信息流广告投放流程

做视频网站的上市公司,信息流广告投放流程,dede和wordpress哪个安全,在线网页设计网站new的过程当我们使用关键字new在堆上动态创建一个对象时#xff0c;它实际上做了三件事#xff1a;获得一块内存空间、调用构造函数、返回正确的指针。当然#xff0c;如果我们创建的是简单类型的变量#xff0c;那么第二步会被省略。假如我们定义了如下一个类A#xff1a…new的过程 当我们使用关键字new在堆上动态创建一个对象时它实际上做了三件事获得一块内存空间、调用构造函数、返回正确的指针。当然如果我们创建的是简单类型的变量那么第二步会被省略。假如我们定义了如下一个类A class A {    int i; public:    A(int _i) :i(_i*_i) {}    void Say()  { printf(i%dn, i); } }; //调用new A* pa new A(3); 那么上述动态创建一个对象的过程大致相当于以下三句话只是大致上 A* pa (A*)malloc(sizeof(A)); pa-A::A(3); return pa; 虽然从效果上看这三句话也得到了一个有效的指向堆上的A对象的指针pa但区别在于当malloc失败时它不会调用分配内存失败处理程序new_handler而使用new的话会的。因此我们还是要尽可能的使用new除非有一些特殊的需求。 new的三种形态 到目前为止本文所提到的new都是指的“new operator”或称为“new expression”但事实上在C中一提到new至少可能代表以下三种含义new operator、operator new、placement new。 new operator就是我们平时所使用的new其行为就是前面所说的三个步骤我们不能更改它。但具体到某一步骤中的行为如果它不满足我们的具体要求 时我们是有可能更改它的。三个步骤中最后一步只是简单的做一个指针的类型转换没什么可说的并且在编译出的代码中也并不需要这种转换只是人为的认识 罢了。但前两步就有些内容了。 new operator的第一步分配内存实际上是通过调用operator new来完成的这里的new实际上是像加减乘除一样的操作符因此也是可以重载的。operator new默认情况下首先调用分配内存的代码尝试得到一段堆上的空间如果成功就返回如果失败则转而去调用一个new_hander然后继续重复前面 过程。如果我们对这个过程不满意就可以重载operator new来设置我们希望的行为。例如 class A { public:    void* operator new(size_t size)    {        printf(operator new calledn);        return ::operator new(size);    } }; A* a new A(); 这里通过::operator new调用了原有的全局的new实现了在分配内存之前输出一句话。全局的operator new也是可以重载的但这样一来就不能再递归的使用new来分配内存而只能使用malloc了 void* operator new(size_t size) {    printf(global newn);    return malloc(size); } 相应的delete也有delete operator和operator delete之分后者也是可以重载的。并且如果重载了operator new就应该也相应的重载operator delete这是良好的编程习惯。 new的第三种形态——placement new是用来实现定位构造的因此可以实现new operator三步操作中的第二步也就是在取得了一块可以容纳指定类型对象的内存后在这块内存上构造一个对象这有点类似于前面代码中的 “p-A::A(3);”这句话但这并不是一个标准的写法正确的写法是使用placement new #include new.h void main() {    char s[sizeof(A)];    A* p (A*)s;    new(p) A(3); //p-A::A(3);    p-Say(); } 对头文件new或new.h的引用是必须的这样才 可以使用placement new。这里“new(p) A(3)”这种奇怪的写法便是placement new了它实现了在指定内存地址上用指定类型的构造函数来构造一个对象的功能后面A(3)就是对构造函数的显式调用。这里不难发现这块指定的地址既 可以是栈又可以是堆placement对此不加区分。但是除非特别必要不要直接使用placement new 这毕竟不是用来构造对象的正式写法只不过是new operator的一个步骤而已。使用new operator地编译器会自动生成对placement new的调用的代码因此也会相应的生成使用delete时调用析构函数的代码。如果是像上面那样在栈上使用了placement new则必须手工调用析构函数这也是显式调用析构函数的唯一情况 p-~A(); 当我们觉得默认的new operator对内存的管理不能满足我们的需要而希望自己手工的管理内存时placement new就有用了。STL中的allocator就使用了这种方式借助placement new来实现更灵活有效的内存管理。 处理内存分配异常 正如前面所说operator new的默认行为是请求分配内存如果成功则返回此内存地址如果失败则调用一个new_handler然后再重复此过程。于是想要从operator new的执行过程中返回则必然需要满足下列条件之一 l         分配内存成功 l         new_handler中抛出bad_alloc异常 l         new_handler中调用exit()或类似的函数使程序结束 于是我们可以假设默认情况下operator new的行为是这样的 void* operator new(size_t size) {    void* p null    while(!(p malloc(size)))    {        if(null new_handler)           throw bad_alloc();        try        {           new_handler();        }        catch(bad_alloc e)        {           throw e;        }        catch(…)        {}    }    return p; } 在默认情况下new_handler的行为是抛出一个bad_alloc异常因此 上述循环只会执行一次。但如果我们不希望使用默认行为可以自定义一个new_handler并使用std::set_new_handler函数使其 生效。在自定义的new_handler中我们可以抛出异常可以结束程序也可以运行一些代码使得有可能有内存被空闲出来从而下一次分配时也许会成 功也可以通过set_new_handler来安装另一个可能更有效的new_handler。例如 void MyNewHandler() {    printf(“New handler called!n”);    throw std::bad_alloc(); } std::set_new_handler(MyNewHandler); 这里new_handler程序在抛出异常之前会输出一句话。应该注意在 new_handler的代码里应该注意避免再嵌套有对new的调用因为如果这里调用new再失败的话可能会再导致对new_handler的调用 从而导致无限递归调用。——这是我猜的并没有尝试过。 在编程时我们应该注意到对new的调用是有可能有异常被抛出的因此在new的代码周围应该注意保持其事务性即不能因为调用new失败抛出异常来导致不正确的程序逻辑或数据结构的出现。例如 class SomeClass {    static int count;    SomeClass() {} public:    static SomeClass* GetNewInstance()    {        count;        return new SomeClass();  } } 静态变量count用于记录此类型生成的实例的个数在上述代码中如果因new分配内存失败而抛出异常那么其实例个数并没有增加但count变量的值却已经多了一个从而数据结构被破坏。正确的写法是 static SomeClass* GetNewInstance() {    SomeClass* p new SomeClass();    count;    return p; } 这样一来如果new失败则直接抛出异常count的值不会增加。类似的在处理线程同步时也要注意类似的问题 void SomeFunc() {    lock(someMutex); //加一个锁    delete p;    p new SomeClass();    unlock(someMutex); } 此时如果new失败unlock将不会被执行于是不仅造成了一个指向不正确地址的指针p的存在还将导致someMutex永远不会被解锁。这种情况是要注意避免的。参考C箴言争取异常安全的代码 STL的内存分配与traits技巧 在《STL原码剖析》一书中详细分析了SGI STL的内存分配器的行为。与直接使用new operator不同的是SGI STL并不依赖C默认的内存分配方式而是使用一套自行实现的方案。首先SGI STL将可用内存整块的分配使之成为当前进程可用的内存当程序中确实需要分配内存时先从这些已请求好的大内存块中尝试取得内存如果失败的话再尝试 整块的分配大内存。这种做法有效的避免了大量内存碎片的出现提高了内存管理效率。 为了实现这种方式STL使用了placement new通过在自己管理的内存空间上使用placement new来构造对象以达到原有new operator所具有的功能。 template class T1, class T2 inline void construct(T1* p, const T2 value) {    new(p) T1(value); } 此函数接收一个已构造的对象通过拷贝构造的方式在给定的内存地址p上构造一个新对 象代码中后半截T1(value)便是placement new语法中调用构造函数的写法如果传入的对象value正是所要求的类型T1那么这里就相当于调用拷贝构造函数。类似的因使用了 placement new编译器不会自动产生调用析构函数的代码需要手工的实现 template class T inline void destory(T* pointer) {    pointer-~T(); } 与此同时STL中还有一个接收两个迭代器的destory版本可将某容器上指定范 围内的对象全部销毁。典型的实现方式就是通过一个循环来对此范围内的对象逐一调用析构函数。如果所传入的对象是非简单类型这样做是必要的但如果传入的 是简单类型或者根本没有必要调用析构函数的自定义类型例如只包含数个int成员的结构体那么再逐一调用析构函数是没有必要的也浪费了时间。为 此STL使用了一种称为“type traits”的技巧在编译器就判断出所传入的类型是否需要调用析构函数 template class ForwardIterator inline void destory(ForwardIterator first, ForwardIterator last) {    __destory(first, last, value_type(first)); } 其中value_type()用于取出迭代器所指向的对象的类型信息于是 templateclass ForwardIterator, class T inline void __destory(ForwardIterator first, ForwardIterator last, T*) {    typedef typename __type_traitsT::has_trivial_destructor trivial_destructor;    __destory_aux(first, last, trivial_destructor()); } //如果需要调用析构函数 templateclass ForwardIterator inline void __destory_aux(ForwardIterator first, ForwardIterator last, __false_type) {    for(; first last; first)        destory(*first); //因first是迭代器*first取出其真正内容然后再用取地址 } //如果不需要就什么也不做 tempalteclass ForwardIterator inline void __destory_aux(ForwardIterator first, ForwardIterator last, __true_type) {}   因上述函数全都是inline的所以多层的函数调用并不会对性能造成影响最终编译 的结果根据具体的类型就只是一个for循环或者什么都没有。这里的关键在于__type_traitsT这个模板类上它根据不同的T类 型定义出不同的has_trivial_destructor的结果如果T是简单类型就定义为__true_type类型否则就定义为 __false_type类型。其中__true_type、__false_type只不过是两个没有任何内容的类对程序的执行结果没有什么意义但 在编译器看来它对模板如何特化就具有非常重要的指导意义了正如上面代码所示的那样。__type_traitsT也是特化了的一系列模 板类 struct __true_type {}; struct __false_type {}; template class T struct __type_traits { public:    typedef __false _type has_trivial_destructor;    …… }; template //模板特化 struct __type_traitsint    //int的特化版本 { public:    typedef __true_type has_trivial_destructor;    …… }; …… //其他简单类型的特化版本 如果要把一个自定义的类型MyClass也定义为不调用析构函数只需要相应的定义__type_traitsT的一个特化版本即可 template struct __type_traitsMyClass { public:    typedef __true_type has_trivial_destructor;    …… }; 模板是比较高级的C编程技巧模板特化、模板偏特化就更是技巧性很强的东 西STL中的type_traits充分借助模板特化的功能实现了在程序编译期通过编译器来决定为每一处调用使用哪个特化版本于是在不增加编程复杂 性的前提下大大提高了程序的运行效率。更详细的内容可参考《STL源码剖析》第二、三章中的相关内容。 带有“[]”的new和delete 我们经常会通过new来动态创建一个数组例如 char* s new char[100]; …… delete s; 严格的说上述代码是不正确的因为我们在分配内存时使用的是new[]而并不是简单的new但释放内存时却用的是delete。正确的写法是使用delete[] delete[] s; 但是上述错误的代码似乎也能编译执行并不会带来什么错误。事实上new与new[]、delete与delete[]是有区别的特别是当用来操作复杂类型时。假如针对一个我们自定义的类MyClass使用new[] MyClass* p new MyClass[10]; 上述代码的结果是在堆上分配了10个连续的MyClass实例并且已经对它们依次调 用了构造函数于是我们得到了10个可用的对象这一点与Java、C#有区别的Java、C#中这样的结果只是得到了10个null。换句话说使用 这种写法时MyClass必须拥有不带参数的构造函数否则会发现编译期错误因为编译器无法调用有参数的构造函数。 当这样构造成功后我们可以再将其释放释放时使用delete[] delete[] p; 当我们对动态分配的数组调用delete[]时其行为根据所申请的变量类型会有所不 同。如果p指向简单类型如int、char等其结果只不过是这块内存被回收此时使用delete[]与delete没有区别但如果p指向的是复杂 类型delete[]会针对动态分配得到的每个对象调用析构函数然后再释放内存。因此如果我们对上述分配得到的p指针直接使用delete来回收 虽然编译期不报什么错误因为编译器根本看不出来这个指针p是如何分配的但在运行时DEBUG情况下会给出一个Debug assertion failed提示。 到这里我们很容易提出一个问题——delete[]是如何知道要为多少个对象调用析构函数的要回答这个问题我们可以首先看一看new[]的重载。 class MyClass {    int a; public:    MyClass() { printf(ctorn); }    ~MyClass() { printf(dtorn); } }; void* operator new[](size_t size) {    void* p operator new(size);    printf(calling new[] with size%d address%pn, size, p);    return p; } // 主函数 MyClass* mc new MyClass[3]; printf(address of mc%pn, mc); delete[] mc; 运行此段代码得到的结果为VC2005 calling new[] with size16 address003A5A58 ctor ctor ctor address of mc003A5A5C dtor dtor dtor 虽然对构造函数和析构函数的调用结果都在预料之中但所申请的内存空间大小以及地址的 数值却出现了问题。我们的类MyClass的大小显然是4个字节并且申请的数组中有3个元素那么应该一共申请12个字节才对但事实上系统却为我们申 请了16字节并且在operator new[]返后我们得到的内存地址是实际申请得到的内存地址值加4的结果。也就是说当为复杂类型动态分配数组时系统自动在最终得到的内存地址前空出了 4个字节我们有理由相信这4个字节的内容与动态分配数组的长度有关。通过单步跟踪很容易发现这4个字节对应的int值为0x00000003也就是 说记录的是我们分配的对象的个数。改变一下分配的个数然后再次观察的结果证实了我的想法。于是我们也有理由认为new[] operator的行为相当于下面的伪代码 template class T T* New[](int count) {    int size sizeof(T) * count 4;    void* p T::operator new[](size);    *(int*)p count;    T* pt (T*)((int)p 4);    for(int i 0; i count; i)        new(pt[i]) T();    return pt; } 上述示意性的代码省略了异常处理的部分只是展示当我们对一个复杂类型使用new[] 来动态分配数组时其真正的行为是什么从中可以看到它分配了比预期多4个字节的内存并用它来保存对象的个数然后对于后面每一块空间使用 placement new来调用无参构造函数这也就解释了为什么这种情况下类必须有无参构造函数最后再将首地址返回。类似的我们很容易写出相应的delete[]的实 现代码 template class T void Delete[](T* pt) {    int count ((int*)pt)[-1];    for(int i 0; i count; i)        pt[i].~T();    void* p (void*)((int)pt – 4);    T::operator delete[](p); } 由此可见在默认情况下operator new[]与operator new的行为是相同的operator delete[]与operator delete也是不同的是new operator与new[] operator、delete operator与delete[] operator。当然我们可以根据不同的需要来选择重载带有和不带有“[]”的operator new和delete以满足不同的具体需求。 把前面类MyClass的代码稍做修改——注释掉析构函数然后再来看看程序的输出 calling new[] with size12 address003A5A58 ctor ctor ctor address of mc003A5A58 这一次new[]老老实实的申请了12个字节的内存并且申请的结果与new[] operator返回的结果也是相同的看来是否在前面添加4个字节只取决于这个类有没有析构函数当然这么说并不确切正确的说法是这个类是否需 要调用构造函数因为如下两种情况下虽然这个类没声明析构函数但还是多申请了4个字节一是这个类中拥有需要调用析构函数的成员二是这个类继承自需要 调用析构函数的类。于是我们可以递归的定义“需要调用析构函数的类”为以下三种情况之一 1 显式的声明了析构函数的 2 拥有需要调用析构函数的类的成员的 3 继承自需要调用析构函数的类的 类似的动态申请简单类型的数组时也不会多申请4个字节。于是在这两种情况下释放内存时使用delete或delete[]都可以但为养成良好的习惯我们还是应该注意只要是动态分配的数组释放时就使用delete[]。 释放内存时如何知道长度 但这同时又带来了新问题既然申请无需调用析构函数的类或简单类型的数组时并没有记录 个数信息那么operator delete或更直接的说free()是如何来回收这块内存的呢这就要研究malloc()返回的内存的结构了。与new[]类似的是实际上在 malloc()申请内存时也多申请了数个字节的内容只不过这与所申请的变量的类型没有任何关系我们从调用malloc时所传入的参数也可以理解这一 点——它只接收了要申请的内存的长度并不关系这块内存用来保存什么类型。下面运行这样一段代码做个实验 char *p 0; for(int i 0; i 40; i 4) {    char* s new char[i];    printf(alloc %2d bytes, address%p distance%dn, i, s, s - p);    p s; } 我们直接来看VC2005下Release版本的运行结果DEBUG版因包含了较多的调试信息这里就不分析了 alloc 0 bytes, address003A36F0 distance3815152 alloc 4 bytes, address003A3700 distance16 alloc 8 bytes, address003A3710 distance16 alloc 12 bytes, address003A3720 distance16 alloc 16 bytes, address003A3738 distance24 alloc 20 bytes, address003A84C0 distance19848 alloc 24 bytes, address003A84E0 distance32 alloc 28 bytes, address003A8500 distance32 alloc 32 bytes, address003A8528 distance40 alloc 36 bytes, address003A8550 distance40 每一次分配的字节数都比上一次多4distance值记录着与上一次分配的差值第 一个差值没有实际意义中间有一个较大的差值可能是这块内存已经被分配了于是也忽略它。结果中最小的差值为16字节直到我们申请16字节时这个差 值变成了24后面也有类似的规律那么我们可以认为申请所得的内存结构是如下这样的 从图中不难看出当我们要分配一段内存时所得的内存地址和上一次的尾地址至少要相距8个字节在DEBUG版中还要更多那么我们可以猜想这8个字节中应该记录着与这段所分配的内存有关的信息。观察这8个节内的内容得到结果如下 图中右边为每次分配所得的地址之前8个字节的内容的16进制表示从图中红线所表示可 以看到这8个字节中的第一个字节乘以8即得到相临两次分配时的距离经过试验一次性分配更大的长度可知第二个字节也是这个意义并且代表高8位也就 说前面空的这8个字节中的前两个字节记录了一次分配内存的长度信息后面的六个字节可能与空闲内存链表的信息有关在翻译内存时用来提供必要的信息。这就 解答了前面提出的问题原来C/C在分配内存时已经记录了足够充分的信息用于回收内存只不过我们平常不关心它罢了。           转载于:https://www.cnblogs.com/lwcode/archive/2008/12/18/1357177.html
http://www.zqtcl.cn/news/466959/

相关文章:

  • 网站收录率嘉兴网站开发公司
  • 优秀的设计网站不备案 没版权 网站
  • 建设 互动 网站 模式网络营销模式不是孤立存在的
  • 怡梦姗网站做么上海21世纪人才网官网登录
  • 家政网站建设方案分析哈尔滨做网站找哪家好
  • 如何建设论坛网站营销宣传策划方案
  • 企业网站推广排名技术网
  • 网站建设网页设计培训学校延边网站建设
  • 自己做网站需要的技术个人简历表格下载
  • 做网站建设小程序ukidc做电影网站
  • 网站内容分析软文范例100字
  • 网站建站策划用vs做网站
  • 如何建自己的网站做农村电子商务的网站有哪些内容
  • 手机销售网站设计怎么推广软件让别人下载
  • 贵州三蒲建设工程有限公司网站莱阳网站制作
  • 外贸买家网站适合初学者模仿的网站
  • 安徽蚌埠怀远县建设局网站米卓网站建设
  • 网站框架怎么建设微信旧版本下载
  • 速贝网站友情链接怎么做企业网站开发的设计流程
  • 网站建设 安庆网站开发免责合同
  • 天津深圳网站开发定制网络工程考研方向
  • 做app网站的公司哪家好济南网站建设市场
  • 自己做网站页面网站国内空间和国外空间
  • 桂城网站制作公司asp.net jsp 网站
  • 太原免费静态网页制作网站如何搭建钓鱼网站
  • 英语门户网站织梦源码修改wordpress登录页面
  • 网络建设和网站建设网站快速收录提交
  • 免费的建设网站软件北京电力交易中心谢开
  • 建设一个网站需要提供什么手续好看的美食网站设计
  • 西宁网站seo公司网站建设和维护释义