精华内容
下载资源
问答
  • c++初始化和类型转换

    千次阅读 2012-08-15 16:35:58
    规则2.1 禁止用memcpy、memset初始化非POD对象 说明:POD 全称是“Plain Old Data”,是C++ 98标准(ISO/IEC 14882, first edition, 1998-09-01)中引入的一个概念, POD类型主要包括int, char, float,double,...

    0.1         声明、定义与初始化

    规则2.1 禁止用memcpy、memset初始化非POD对象

    说明:POD 全称是“Plain Old Data”,是C++ 98标准(ISO/IEC 14882, first edition, 1998-09-01)中引入的一个概念, POD类型主要包括int, char, float,double,enumeration,void,指针等原始类型及其集合类型,不能使用封装和面对对象特性(如用户定义的构造/赋值/析构函数、基类、虚函数等)。

     

    由于非POD类型比如非集合类型的class对象,可能存在虚函数,内存布局不确定,跟编译器有关,滥用内存拷贝可能会导致严重的问题。

    即使对集合类型的class,使用直接的内存拷贝和比较,破坏了信息隐蔽和数据保护的作用,也不提倡memcpy、memset操作。

    示例:×××产品程序异常退出(core dump)。

    经过现场环境的模似,程序产生COREDUMP,其原因是:在初始化函数内使用memset(this, 0, sizeof(*this))进行了类的初始化,将类的虚函数表指针被清空,从而导致使用空指针。

    解决方案:使用C++构造函数初始化,不要使用memset函数初始化类对象。

    建议2.1 变量使用时才声明并初始化

    说明:变量在使用前未赋初值,是常见的低级编程错误。使用前才声明变量并同时初始化,非常方便地避免了此类低级错误。

    在函数开始位置声明所有变量,后面才使用变量,作用域覆盖整个函数实现,容易导致如下问题:

    l         程序难以理解和维护:变量的定义与使用分离。

    l         变量难以合理初始化:在函数开始时,经常没有足够的信息进行变量初始化,往往用某个默认的空值(比如零)来初始化,这通常是一种浪费,如果变量在被赋于有效值以前使用,还会导致错误。

    遵循变量作用域最小化原则与就近声明原则, 使得代码更容易阅读,方便了解变量的类型和初始值。特别是,应使用初始化的方式替代声明再赋值。

    示例:

    //不好的例子:声明与初始化分离

    string  name; //声明时未初始化:调用缺省构造函数

    //…….

    name=”zhangsan”; //再次调用赋值操作符函数;声明与定义在不同的地方,理解相对困难

     

    //好的例子:声明与初始化一体,理解相对容易

    string  name(”zhangsan”); //调用一次构造函数

     

    建议2.2 避免构造函数做复杂的初始化,可以使用“init”函数

    说明:正如函数的变量都在函数内部初始化一样,类数据成员最好的初始化场所就是构造函数,数据成员都应该尽量在构造函数中初始化。

    以下情况可以使用init()函数来初始化:

    l         需要提供初始化返回信息。

    l         数据成员初始化可能抛异常。

    l         数据成员初始化失败会造成该类对象初始化失败,引起不确定状态。

    l         数据成员初始化依赖this指针:构造函数没结束,对象就没有构造出来,构造函数内不能使用this成员;

    l         数据成员初始化需要调用虚函数。在构造函数和析构函数中调用虚函数,会导致未定义的行为。

    示例:数据成员初始化可能抛异常:

    class CPPRule

    {

    public:

        CPPRule():size_(0), res (null) {};   //仅进行值初始化

        long init(int size)

        {

            //根据传入的参数初始化size_, 分配资源res

        }

    private:

        int size_;

        ResourcePtr* res;

    };

    //使用方法:

    CPPRule a;

    a.init(100);

    建议2.3 初始化列表要严格按照成员声明顺序来初始化它们

    说明:编译器会按照数据成员在类定义中声明的顺序进行初始化,而不是按照初始化列表中的顺序,如果打乱初始化列表的顺序实际上不起作用,但会造成阅读和理解上的混淆;特别是成员变量之间存在依赖关系时可能导致BUG。

    示例:

    //不好的例子:初始化顺序与声明顺序不一致

    class Employee

    {

    public:

        Employee(constchar* firstName, constchar* lastName)

            : firstName_(firstName), lastName_(lastName)

            , email_(firstName_ + "." + lastName_ + "@huawei.com") {};

    private:

        string email_, firstName_, lastName_;

    };

    类定义email_是在firstName_, lastName_之前声明,它将首先初始化,但使用了未初始化的firstName_和lastName_,导致错误。

    在成员声明时,应按照成员相互依赖关系按顺序声明。

    建议2.4 明确有外部依赖关系的全局与静态对象的初始化顺序

    说明:如果全局对象A的成员变量有外部依赖,比如依赖另外一个全局变量B,在A的构造函数中访问B,隐含的规则就是B先于A初始化,然而全局与静态对象的初始化与析构顺序未有严格定义,无法确保B已经完成初始化,而每次生成可执行程序都可能发生变化,这类BUG难以定位。

    通常采用单件(Singleton)模式或者把有依赖关系的全局对象放在一个文件中定义来明确初始化顺序。同一个文件中,若全局对象a在全局对象b之前定义,则a一定会在b之前初始化;但是不同文件中的全局对象就没有固定的初始化顺序。可以在main()或 pthread_once() 内初始化一个运行期间不回收的指针。

    0.2         类型转换

    避免使用类型分支来定制行为:类型分支来定制行为容易出错,是企图用C++编写C代码的明显标志。这是一种很不灵活的技术,要添加新类型时,如果忘记修改所有分支,编译器也不会告知。使用模板和虚函数,让类型自己而不是调用它们的代码来决定行为。

    规则2.2 使用C++风格的类型转换,不要使用C风格的类型转换

    说明:C++的类型转换由于采用关键字,更醒目,更容易查找,编程中强迫程序员多停留思考片刻,谨慎使用强制转换。

    C++使用const_cast, dynamic_cast, static_cast, reinterpret_cast等新的类型转换,它们允许用户选择适当级别的转换符,而不是像C那样全用一个转换符。

    dynamic_cast:主要用于下行转换,dynamic_cast具有类型检查的功能。dynamic_cast有一定的开销,建议在调测代码中使用。

    static_cast:和C风格转换相似可做值的强制转换,或上行转换(把派生类的指针或引用转换成基类的指针或引用)。该转换经常用于消除多重继承带来的类型歧义,是相对安全的。下行转换(把基类的指针或引用转换成派生类的指针或引用)时,由于没有动态类型检查,所以不安全的,不提倡下行转换。

    reinterpret_cast:用于转换不相关的类型。reinterpret_cast强制编译器将某个类型对象的内存重新解释成另一种类型,相关代码可移植不好。建议对reinterpret_cast<> 的用法进行注释,有助于减少维护者在看到这种转换时的顾虑。

    const_cast:用于移除对象的 const属性,使对象变得可修改。

    示例:

    externvoid Fun(DerivedClass* pd);

    void Gun(BaseClass* pb)

    {

        //不好的例子: C风格强制转换,转换会导致对象布局不一致,编译不报错,运行时可能会崩溃

        DerivedClass* pd = (DerivedClass *)pb;   

     

        //好的例子: C++风格强制转换,明确知道pb实际指向DerivedClass

        DerivedClass* pd = dynamic_cast< DerivedClass *>(pb); 

     

        if(pd)

            Fun(pd);

    }

     

    建议2.5 避免使用reinterpret_cast

    说明:reinterpret_cast用于转换不相关类型。尝试用reinterpret_cast将一种类型强制转换另一种类型,这破坏了类型的安全性与可靠性,是一种不安全的转换。不同类型之间尽量避免转换。

    建议2.6 避免使用const_cast

    说明:const_cast用于移除对象的 const性质。

    const属性提供一种安全感,让程序员知道这个定义是固定不变的,从而不需要担心后面的变化。如果const属性在程序员不知道的地方被消除,会带来很多严重的后果。

    示例:不好的例子

    unsignedconst int arraySize = 1024;

    int &newArraySize = const_cast<int&>(arraySize);

    newArraySize = 2048;

    这里如果不通过引用或者指针访问arraySize,那么arraySize的值始终是1024。可是如果被作为一个指针或者引用传给其他函数进行取值的话,会发现值变成了2048。

    示例:不好的例子:强制去掉入参的const属性,导致函数可以对入参进行修改。

    void setObj(TBase const *obj)

    {

        //m_pObj的定义为:

        TBase *m_pObj;

        m_pObj = const_cast<TBase*>(obj);

        m_pObj->value = 123;

    }

    建议2.7 使用虚函数替换dynamic_cast

    说明:很多刚从C语言转过了的程序员习惯这样的思路:若对象的类型是T1,则做某种处理;若对象的类型是T2,则做另外的处理等等。但C++提供了更好的解决方案:虚函数。

    虚函数与dynamic_cast类型转换相比:

    l         虚函数更安全,不会出现强制转换错的情况;

    l         虚函数效率更高:用函数指针,避免条件判断;

    l         虚函数不需要在编码时确定对象的真实类型,而dynamic_cast必须告知要转成的类型,运行时若类型不当返回空指针或者抛异常;

    l         虚函数适用性更强:虚函数是真正动态绑定;类型转换当增加或删除一个派生类时,dynamic_cast必须增减相应的代码。

    展开全文
  • arm架构的初始化过程步骤

    千次阅读 2014-12-22 17:13:53
    1 禁止MMU,关闭中断,禁止cache; 2 根据硬件设计配制好处理器时钟、DRAM时钟、定时器时钟;...5 使能cache,使能MMU,跳转到DRAM内存中运行继续初始化,包括根据具体应用以及系统中的硬件配置初始化各个

    1 禁止MMU,关闭中断,禁止cache;

    2 根据硬件设计配制好处理器时钟、DRAM时钟、定时器时钟;

    3 根据系统中所用的flash和DRAM芯片容量和电气参数设置它们的起始地址、容量、刷新频率等;

    4 将固化在flash芯片中的程序搬移到DRAM内存中;

    5 使能cache,使能MMU,跳转到DRAM内存中运行继续初始化,包括根据具体应用以及系统中的硬件配置初始化各个功能模块、安装好异  常中断处理程序、使能中断等;

    6 进行操作系统相关初始化;

     

    禁止MMU,关闭中断,禁止cache

    通过写系统控制协处理器的寄存器1 的第0 位可以允许和禁止MMU。在复位后这位
    是0,MMU 被禁止。

     

    关闭中断与打开中断

    中断是一种高效的对话机制,但有时并不想程序运行的过程中中断运行,比如正在打印东西

    ,但程序突然中断了,又让另外一个程序输出打印内容,这样在打印机上就会乱得不得了,

    同时有两份以上的文件交错地打印在一张纸上。像不可剥夺的资源,就一定要关闭中断,让

    它占有这个资源。在ARM里,没有像x86那样有清除中断指令CLI。那么在ARM里是怎么样实现

    关中断和开中断的呢?下面就来看看ARM的关中断和开中断实现。

    void Lock(void)
    {
     stmdb sp!, {r0}
     mrs r0, cpsr
     orr r0,r0,#0xC0
     msr cpsr_cxsf,r0
     ldmia sp!,{r0}

    }
    上面这段程序是通过设置CPSR的第6,7位来实现的,因为第6,7位是设置为1时,就不再响应

    中断。


    void UnLock(void)
    {
     stmdb sp!, {r0}
     mrs r0, cpsr
     bic r0,r0,#0xC0
     msr cpsr_cxsf,r0
     ldmia sp!,{r0}
    }
    上面是重新开中断的命令,同样是设置CPSR的第6,7位,但它的值是0,就可接收中断了。

    如果在多个任务之间进行共享数据,一般是需要使用关中断和开中断实现数据同步的,其实

    中这种关中断和开中断,就是进入临界区和退出临界区。
    如果是像PC机那样有多个CPU的话,关中断并不能防止这种情况。

     

     

    系统的在应用编程(IAP)以及在系统编程功能(ISP)等。

     

    中断向量表
    ARM要求中断向量表必须放置在从0地址开始,连续8X4字节的空间内。
    每当一个中断发生以后,ARM处理器便强制把PC指针置为向量表中对应中断类型的地址值。因为每个中断只占据向量表中1个字的存储空间,只能放置一条ARM指令,使程序跳转到存储器的其他地方,再执行中断处理。
    中断向量表的程序实现通常如下表示:
    AREA Boot ,CODE, READONLY
    ENTRY
    B ResetHandler
    B UndefHandler
    B SWIHandler
    B PreAbortHandler
    B DataAbortHandler
    B
    B IRQHandler
    B FIQHandler
    其中关键字ENTRY是指定编译器保留这段代码,因为编译器可能会认为这是一段亢余代码而加以优化。链接的时候要确保这段代码被链接在0地址处,并且作为整个程序的入口。

     

     Q:为什么在中断向量表中不直接LDR PC,"异常地址".而是使用一个标号,然有再在后面使用DCD定义这个标号
    A:因为LDR指令只能跳到当前PC 4kB范围内,而B指令能跳转到32MB范围,而现在这样在LDR PC, "xxxx"这条指令不远处用"xxxx"DCD定义一个字,而这个字里面存放最终异常服务程序的地址,这样可以实现4GB全范围跳转.
    Q: LDR 不是可以全空间跳转的吗 《ARM微控制器基础与实战》程序清单5.3.
    A: LDR伪指令通过设置指令缓冲池才能实现全范围跳转,而LDR指令则只能实现4KB范围跳转.

     

    MEMMAP=0:开机默认值,Boot装载模式----向量表(0x00000000-0x0000003c)映射的是BootBlock中的0x7FFFE000-0x7FFFF03c中的值;芯片复位时,启动boot装载程序,boot装载程序检查P0.14口的状态和用户的异常向量,判断是进入ISP状态还是启动用户程序,若启动用户程序,则自动设置MEMMAP=1(片内flash启动)或3(片外程序存储器启动)。

    很奇怪的,我在实验中,当使用无片内flash的LPC2210时即使设置P0.14为高低都没关系,芯片会跳过继而执行片外flash中的代码.
        MEMMAP=1:中断向量表就在片内flash中,地址就是0x00000000-0x0000003c,相当于没有映射;
        MEMMAP=2:最为主要的设置,即是重映射的关键之所在,当设置MEMMAP=2  时,中断向量表(0x00000000-0x0000003c)映射的是片内SRAM中的0x40000000-0x4000003c中的值,而因为是SRAM,所以在程序运行的过程中是可以改变的,这样就可以达到重映射的目的(中断向量表可以随时修改)
        MEMMAP=3:中断向量表就在片外flash中,中断向量表(0x00000000-0x0000003c)映射到是片外flash中的0x80000000-0x8000003c中的值;功能上与MEMMAP=1时的差不多,因为一旦程序固化到flash中,即为只读,只是数值映射而已!
    设置MEMMAP
    ; Memory Mapping (when Interrupt Vectors are in RAM)
    MEMMAP          EQU     0xE01FC040      ; Memory Mapping Control
                    IF      :DEF:REMAP
                    LDR     R0, =MEMMAP
                    IF      :DEF:EXTMEM_MODE
                    MOV     R1, #3
                    ELIF    :DEF:RAM_MODE
                    MOV     R1, #2
                    ELSE
                    MOV     R1, #1
                    ENDIF
                    STR     R1, [R0]
                    ENDIF


    MEMMAP有两个控制位 MEMMAP[1:0]
    00    BOOT装载程序模式
    01    User FLASH模式
    10    用户RAM模式
    11    用户外部存储器模式
    10模式也就是RAM模式 我们访问地址0X00是跟访问RAM地址0X40000000中的数据是完全一样的 向RAM中写进数据 然后通过数据窗口观察0X0地址的变化 应该是同步变化的


    存储器类型和时序配置

       主要是对系统存储器控制器(MMU)的初始化。由于存储器控制器并不是ARM架构的一部分,不同芯片的实现方式各不相同。由于运算能力和寻址能力的强大,基于ARM内核的微处理器系统一般都需要外扩展各种类型的存储器。对于存储器系统的初始化一般包括如下几个方面:
    --存储器类型、时序和总线宽度的配置
    --存储器地址的配置
    (1)存储器类型
       基于ARM微处理系统的存储器一般有如下几类:SARM,DRAM,Flah,同时,即使同类存储器也有访问速度上的不同。其中,SRAM和Flah属于静态存储器,可以共用存储器端口,而DRAM有动态刷新和地址复用等特征,需要专门的存储器端口。
    (2)时序
       存储器端口的接口时序优化对系统性能影响非常大,因为系统运行的速度瓶颈一般都存在于存储器的访问,因此希望存储器的访问尽可能快,但又要考虑由此带来的系统稳定性问题。
    (3)总线宽度
       ARM微处理器架构支持8/16/32位的数据总线宽度访问存储器和外设,对于特定的存储器来说,需要设定数据总线的宽度。
    (4)存储器地址的配置
       ARM微处理器架构理论上可以支持4GB的地址空间,而对于一个实际的系统来说,配置的物理地址远没有这么多,因此,如何配置存储器的地址,也是一个重要的问题。
    (5)存储器地址重映射
       存储器地址重映射就是可以通过软件配置来改变一块存储器物理地址的方法,是当前许多先进控制器所具有的功能。
    进行地址重映射的原因:提高系统的运行效率。
    进行地址重映射的注意:保证程序流程的连续性。
    有的ARM处理器不具有地址重映射的功能,可以采样代码搬移加跳转的方式完成上述功能。
    初始化堆栈
    因为ARM有7种执行状态,每一种状态的堆栈指针寄存器(SP)都是独立的。因此,对程序中需要用到的每一种模式都要给SP定义一个堆栈地址。方法是改变状态寄存器内的状态位,使处理器切换到不同的状态,让后给SP赋值。注意:不要切换到User模式进行User模式的堆栈设置,因为进入User模式后就不能再操作CPSR回到别的模式了,可能会对接下去的程序执行造成影响。
    这是一段堆栈初始化的代码示例,其中只定义了三种模式的SP指针:
    MRS R0,CPSR
    BIC R0,R0,#MODEMASK 安全起见,屏蔽模式位以外的其他位
    ORR R1,R0,#IRQMODE
    MSR CPSR_cxfs,R1
    LDR SP,=UndefStack

    ORR R1,R0,#FIQMODE
    MSR CPSR_cxsf,R1
    LDR SP,=FIQStack

    ORR R1,R0,#SVCMODE
    MSR CPSR_cxsf,R1
    LDR SP,=SVCStack

    初始化应用程序执行环境
    如果使用分散加载描述文件调整堆栈和堆放置,则链接器创建 __user_initial_stackheap() 函数,并使用链接器定义的符号作为这些区域的名称。

    映像一开始总是存储在ROM/Flash里面的,其RO部分即可以在ROM/Flash里面执行,也可以转移到速度更快的RAM中执行;而RW和ZI这两部分是必须转移到可写的RAM里去。所谓应用程序执行环境的初始化,就是完成必要的从ROM到RAM的数据传输和内容清零。
    下面是在ADS下,一种常用存储器模型的直接实现:
    LDR r0,=|Image$$RO$$Limit| 得到RW数据源的起始地址
    LDR r1,=|Image$$RW$$Base| RW区在RAM里的执行区起始地址
    LDR r2,=|Image$$ZI$$Base| ZI区在RAM里面的起始地址
    CMP r0,r1 比较它们是否相等
    BEQ �
    0 CMP r1,r3
    LDRCC r2,[r0],#4
    STRCC r2,[r1],#4
    BCC �
    1 LDR r1,=|Image$$ZI$$Limit|
    MOV r2,#0
    2 CMP r3,r1
    STRCC r2,[r3],#4
    BCC �
    程序实现了RW数据的拷贝和ZI区域的清零功能。其中引用到的4个符号是由链接器第一输出的。
    |Image$$RO$$Limit|:表示RO区末地址后面的地址,即RW数据源的起始地址
    |Image$$RW$$Base|:RW区在RAM里的执行区起始地址,也就是编译器选项RW_Base指定的地址
    |Image$$ZI$$Base|:ZI区在RAM里面的起始地址
    |Image$$ZI$$Limit|:ZI区在RAM里面的结束地址后面的一个地址
    程序先把ROM里|Image$$RO$$Limt|开始的RW初始数据拷贝到RAM里面|Image$$RW$$Base|开始的地址,当RAM这边的目标地址到达|Image$$ZI$$Base|后就表示RW区的结束和ZI区的开始,接下去就对这片ZI区进行清零操作,直到遇到结束地址|Image$$ZI$$Limit|
    改变处理器模式
    因为在初始化过程中,许多操作需要在特权模式下才能进行(比如对CPSR的修改),所以要特别注意不能过早的进入用户模式。
    内核级的中断使能也可以考虑在这一步进行。如果系统中另外存在一个专门的中断控制器,这么做总是安全的。
    呼叫主应用程序
    当所有的系统初始化工作完成之后,就需要把程序流程转入主应用程序。最简单的一种情况是:
    IMPORT main

    B main
    直接从启动代码跳转到应用程序的主函数入口,当然主函数名字可以由用户随便定义。
    在ARM ADS环境中,还另外提供了一套系统级的呼叫机制。
    IMPORT __main

    B __main
    __main()是编译系统提供的一个函数,负责完成库函数的初始化和初始化应用程序执行环境,最后自动跳转到main()函数。

     

    ARM 介绍

    ARM微处理器的工作状态一般有两种,并可在两种状态之间切换:
    第一种为ARM状态,此时处理器执行32位的字对齐的ARM指令;
    第二种为Thumb状态,此时处理器执行16位的、半字对齐的Thumb指令。
    在程序的执行过程中,微处理器可以随时在两种工作状态之间切换,并且,处理器工作状态的转变并不影响处理器的工作模式和相应寄存器中的内容。但ARM微处理器在开始执行代码时,应该处于ARM状态。

    ARM处理器状态
    进入Thumb状态:当操作数寄存器的状态位(位0)为1时,可以采用执行BX指令的方法,使微处理器从ARM状态切换到Thumb状态。此外,当处理器处于Thumb状态时发生异常(如IRQ、FIQ、Undef、Abort、SWI等),则异常处理返回时,自动切换到Thumb状态。
    进入ARM状态:当操作数寄存器的状态位为0时,执行BX指令时可以使微处理器从Thumb状态切换到ARM状态。此外,在处理器进行异常处理时,把PC指针放入异常模式链接寄存器中,并从异常向量地址开始执行程序,也可以使处理器切换到ARM状态。

     

    ARM处理器模式
    ARM微处理器支持7种运行模式,分别为:
    用户模式(usr):ARM处理器正常的程序执行状态。
    快速中断模式(fiq):用于高速数据传输或通道处理。
    外部中断模式(irq):用于通用的中断处理。
    管理模式(svc):操作系统使用的保护模式。
    数据访问终止模式(abt):当数据或指令预取终止时进入该模式,可用于虚拟存储及存储保护。
    系统模式(sys):运行具有特权的操作系统任务。
    定义指令中止模式(und):当未定义的指令执行时进入该模式,可用于支持硬件协处理器的软件仿真。

    ARM处理器模式
    ARM微处理器的运行模式可以通过软件改变,也可以通过外部中断或异常处理改变。大多数的应用程序运行在用户模式下,当处理器运行在用户模式下时,某些被保护的系统资源是不能被访问的。
    除用户模式以外,其余的所有6种模式称之为非用户模式,或特权模式;其中除去用户模式和系统模式以外的5种又称为异常模式,常用于处理中断或异常,以及需要访问受保护的系统资源等情况。

    ARM寄存器
    ARM处理器共有37个寄存器。其中包括:31个通用寄存器,包括程序计数器(PC)在内。这些寄存器都是32位寄存器。以及6个32位状态寄存器。
    关于寄存器这里就不详细介绍了,有兴趣的人可以上网找找,很多这方面的资料。

    异常处理
    当正常的程序执行流程发生暂时的停止时,称之为异常,例如处理一个外部的中断请求。在处理异常之前,当前处理器的状态必须保留,这样当异常处理完成之后,当前程序可以继续执行。处理器允许多个异常同时发生,它们将会按固定的优先级进行处理。当一个异常出现以后,ARM微处理器会执行以下几步操作:

    进入异常处理的基本步骤:
    将下一条指令的地址存入相应连接寄存器LR,以便程序在处理异常返回时能从正确的位置重新开始执行。将CPSR复制到相应的SPSR中。根据异常类型,强制设置CPSR的运行模式位。
    强制PC从相关的异常向量地址取下一条指令执行,从而跳转到相应的异常处理程序处。如果异常发生时,处理器处于Thumb状态,则当异常向量地址加载入PC时,处理器自动切换到ARM状态。
    ARM微处理器对异常的响应过程用伪码可以描述为:
    R14_ = Return Link
    SPSR_= CPSR
    CPSR[4:0] = Exception Mode Number
    CPSR[5] = 0 ;当运行于 ARM 工作状态时
    If == Reset or FIQ then;当响应 FIQ 异常时,禁止新的 FIQ 异常
    CPSR[6] = 1
    PSR[7] = 1
    PC = Exception Vector Address

    异常处理完毕之后,ARM微处理器会执行以下几步操作从异常返回:
    将连接寄存器LR的值减去相应的偏移量后送到PC中。
    将SPSR复制回CPSR中。
    若在进入异常处理时设置了中断禁止位,要在此清除

     

    每一种异常模式拥有自己的物理的R13。应用程序初始化该R13,使其指向该异常模

    式专用的栈地址。当进入异常模式时,可以将需要使用的寄存器保存在R13所指的栈中;

    当退出异常处理程序时,将保存在R13所指的栈中的寄存器值弹出。这样就使异常处理程

    序不会破坏被其中断程序的运行现场。

    1。请问cache和write-buffer的关系,在WB和cache同时打开的时候,数据是如何流动的呢?考虑到WB的特性,是不是较大段的数据被写入WB?

     

    WB的设计是为了防止处理器流水线被写数据总线操作(写主存,写外围设备寄存器等)拉住。典型写数据总线时机有三种:一是cache处于write through策略下的写操作;二是cache处于write back策略下,dirty数据由于cache行替换或者被程序主动清空而写回主存,三是不经过cache,直接对数据总线的写操作。有了WB之后,被写回数据总线的内容在进入WB之后,处理器和cache就可以立刻继续使用了。这就是WB和cache的关系。

    2。还有就是所谓“cachable bit”和“bufferable bit”的问题,这个问题来自于《arm-arm》partB 的5.4节,那里有一张表,我对这个表死活不理解。这个问题以前也问过,但是没有人回答。

     

    ARM各系列的处理器上的cache设计有所不同,所以有的cache只有write through策略,有的cache只有write back策略,还有的cache是write back策略但允许一定的write through行为,因此C和B位的四种组合对这三种cache而言有不同的含义。第一列和第二列分别针对write through型cache和write back型cache进行解释,其含义可以参考我对第1个问题的回答,第三列针对write back策略但允许一定的write through行为的cache,第一行好理解,第二行之所以在B位为0的情况下依然是bufferable,应该是因为硬件上的设计原因(节省硬件资源或者由于目标设计频率限制),第三行的含义是,当C==1,B==0时,cache使用write through策略,WB开启,第四行的含义是,当C==1,B==1时,cache使用write back策略,WB开启。
    可以看到,对第三种类型的cache,C和B位不再“严格”是其本来控制cacheable和bufferable的含义,而是利用这两位的“组合”来控制cache和WB的表现行为,这样做比另外再增加一位来选择cache的write back策略和write through策略硬件上节约了资源,效果上却差不多,少了cache和WB几种意义不大的组合,应该说还是挺巧妙的。

    展开全文
  • 在Java多线程程序中,有时候需要采用延迟初始化来降低初始化类和创建对象的开销。双 重检查锁定是常见的延迟初始化技术,但它是一个错误的用法。本文将分析双重检查锁定的 错误根源,以及两种线程安全的延迟初始化...

    前言

    在Java多线程程序中,有时候需要采用延迟初始化来降低初始化类和创建对象的开销。双 重检查锁定是常见的延迟初始化技术,但它是一个错误的用法。本文将分析双重检查锁定的 错误根源,以及两种线程安全的延迟初始化方案。

    双重检查锁定的由来

    在Java程序中,有时候可能需要推迟一些高开销的对象初始化操作,并且只有在使用这些 对象时才进行初始化。此时,程序员可能会采用延迟初始化。但要正确实现线程安全的延迟初 始化需要一些技巧,否则很容易出现问题。比如,下面是非线程安全的延迟初始化对象的示例 代码。

    public class UnsafeLazyInitialization {
        private static Instance instance;
    
        public static Instance getInstance() {
            if (instance == null) // 1:A线程执行 
                instance = new Instance(); // 2:B线程执行 
            return instance;
        }
    }
    

    在UnsafeLazyInitialization类中,假设A线程执行代码1的同时,B线程执行代码2。此时,线 程A可能会看到instance引用的对象还没有完成初始化(出现这种情况的原因见3.8.2节)。 对于UnsafeLazyInitialization类,我们可以对getInstance()方法做同步处理来实现线程安全 的延迟初始化。示例代码如下

    public class SafeLazyInitialization {
        private static Instance instance;
    
        public synchronized static Instance getInstance() {
            if (instance == null)
                instance = new Instance();
            return instance;
        }
    }
    

    由于对getInstance()方法做了同步处理,synchronized将导致性能开销。如果getInstance()方 法被多个线程频繁的调用,将会导致程序执行性能的下降。反之,如果getInstance()方法不会被 多个线程频繁的调用,那么这个延迟初始化方案将能提供令人满意的性能。
    在早期的JVM中,synchronized(甚至是无竞争的synchronized)存在巨大的性能开销。因此, 人们想出了一个“聪明”的技巧:双重检查锁定(Double-Checked Locking)。人们想通过双重检查 锁定来降低同步的开销。下面是使用双重检查锁定来实现延迟初始化的示例代码。

    public class DoubleCheckedLocking { // 1 
        private static Instance instance; // 2
    
        public static Instance getInstance() { // 3 
            if (instance == null) { // 4:第一次检查 
                synchronized (DoubleCheckedLocking.class) { // 5:加锁 
                    if (instance == null) // 6:第二次检查
                        instance = new Instance(); // 7:问题的根源出在这里 
                } // 8
            } // 9 
            return instance; // 10 
        } // 11 
    }
    

    如上面代码所示,如果第一次检查instance不为null,那么就不需要执行下面的加锁和初始 化操作。因此,可以大幅降低synchronized带来的性能开销。上面代码表面上看起来,似乎两全 其美。·多个线程试图在同一时间创建对象时,会通过加锁来保证只有一个线程能创建对象。 ·在对象创建好之后,执行getInstance()方法将不需要获取锁,直接返回已创建好的对象。 双重检查锁定看起来似乎很完美,但这是一个错误的优化!在线程执行到第4行,代码读 取到instance不为null时,instance引用的对象有可能还没有完成初始化。

    问题的根源

    前面的双重检查锁定示例代码的第7行(instance=new Singleton();)创建了一个对象。这一 行代码可以分解为如下的3行伪代码。

    memory = allocate(); // 1:分配对象的内存空间
    ctorInstance(memory); // 2:初始化对象
    instance = memory; // 3:设置instance指向刚分配的内存地址

    上面3行伪代码中的2和3之间,可能会被重排序2和3之间重排序之后的执行时序如 下。

    memory = allocate(); // 1:分配对象的内存空间
    instance = memory; //3:设置instance指向刚分配的内存地址 // 注意,此时对象还没有被初始化!
    ctorInstance(memory); //2:初始化对象

    根据《The Java Language Specification,Java SE 7 Edition》(后文简称为Java语言规范),所有 线程在执行Java程序时必须要遵守intra-thread semantics。intra-thread semantics保证重排序不会 改变单线程内的程序执行结果。换句话说,intra-thread semantics允许那些在单线程内,不会改 变单线程程序执行结果的重排序。上面3行伪代码的2和3之间虽然被重排序了,但这个重排序 并不会违反intra-thread semantics。这个重排序在没有改变单线程程序执行结果的前提下,可以 提高程序的执行性能。 为了更好地理解intra-thread semantics,请看如下所示的示意图(假设一个线程A在构 造对象后,立即访问这个对象)。 只要保证2排在4的前面,即使2和3之间重排序了,也不会违反intra-thread semantics。

    ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200608174634903.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM1NzY0Mjk1,size_16,colo
    下面,再让我们查看多线程并发执行的情况。如下图
    在这里插入图片描述
    由于单线程内要遵守intra-thread semantics,从而能保证A线程的执行结果不会被改变。但 是,当线程A和B上图的时序执行时,B线程将看到一个还没有被初始化的对象。 回到本文的主题,DoubleCheckedLocking示例代码的第7行(instance=new Singleton();)如果 发生重排序,另一个并发执行的线程B就有可能在第4行判断instance不为null。线程B接下来将 访问instance所引用的对象,但此时这个对象可能还没有被A线程初始化!下表是这个场景的 具体执行时序。
    在这里插入图片描述
    这里A2和A3虽然重排序了,但Java内存模型的intra-thread semantics将确保A2一定会排在 A4前面执行。因此,线程A的intra-thread semantics没有改变,但A2和A3的重排序,将导致线程 B在B1处判断出instance不为空,线程B接下来将访问instance引用的对象。此时,线程B将会访 问到一个还未初始化的对象。 在知晓了问题发生的根源之后,我们可以想出两个办法来实现线程安全的延迟初始化。

    1)不允许2和3重排序。
    2)允许2和3重排序,但不允许其他线程“看到”这个重排序。

    后文介绍的两个解决方案,分别对应于上面这两点。

    基于volatile的解决方案

    对于前面的基于双重检查锁定来实现延迟初始化的方案(指DoubleCheckedLocking示例代 码),只需要做一点小的修改(把instance声明为volatile型),就可以实现线程安全的延迟初始 化。请看下面的示例代码。

    public class SafeDoubleCheckedLocking {
        private volatile static Instance instance;
    
        public static Instance getInstance() {
            if (instance == null) {
                synchronized (SafeDoubleCheckedLocking.class) {
                    if (instance == null) instance = new Instance(); // instance为volatile,现在没问题了 
                }
            }
            return instance;
        }
    } 
    

    注意 这个解决方案需要JDK 5或更高版本(因为从JDK 5开始使用新的JSR-133内存模 型规范,这个规范增强了volatile的语义)。 当声明对象的引用为volatile后,重排序在多线程 环境中将会被禁止。上面示例代码将按如下的时序执行。
    在这里插入图片描述
    这个方案本质上是通过禁止重排序,来保证线程安全的延迟初始 化。

    基于类初始化的解决方案

    JVM在类的初始化阶段(即在Class被加载后,且被线程使用之前),会执行类的初始化。在 执行类的初始化期间,JVM会去获取一个锁。这个锁可以同步多个线程对同一个类的初始化。 基于这个特性,可以实现另一种线程安全的延迟初始化方案(这个方案被称之为 Initialization On Demand Holder idiom)。

    public class InstanceFactory {
        private static class InstanceHolder {
            public static Instance instance = new Instance();
        }
    
        public static Instance getInstance() {
            return InstanceHolder.instance; // 这里将导致InstanceHolder类被初始化
        }
    }
    

    假设两个线程并发执行getInstance()方法,下面是执行的示意图。
    在这里插入图片描述
    这个方案的实质是:允许重排序,但不允许非构造线程(这 里指线程B)“看到”这个重排序。 初始化一个类,包括执行这个类的静态初始化和初始化在这个类中声明的静态字段。根 据Java语言规范,在首次发生下列任意一种情况时,一个类或接口类型T将被立即初始化。

    1)T是一个类,而且一个T类型的实例被创建。
    2)T是一个类,且T中声明的一个静态方法被调用。
    3)T中声明的一个静态字段被赋值。
    4)T中声明的一个静态字段被使用,而且这个字段不是一个常量字段。
    5)T是一个顶级类(Top Level Class,见Java语言规范的§7.6),而且一个断言语句嵌套在T 内部被执行。

    在InstanceFactory示例代码中,首次执行getInstance()方法的线程将导致InstanceHolder类被 初始化(符合情况4)。 由于Java语言是多线程的,多个线程可能在同一时间尝试去初始化同一个类或接口(比如 这里多个线程可能在同一时刻调用getInstance()方法来初始化InstanceHolder类)。因此,在Java 中初始化一个类或者接口时,需要做细致的同步处理。 Java语言规范规定,对于每一个类或接口C,都有一个唯一的初始化锁LC与之对应。从C 到LC的映射,由JVM的具体实现去自由实现。JVM在类初始化期间会获取这个初始化锁,并且 每个线程至少获取一次锁来确保这个类已经被初始化过了(事实上,Java语言规范允许JVM的 具体实现在这里做一些优化,见后文的说明)。 对于类或接口的初始化,Java语言规范制定了精巧而复杂的类初始化处理过程。Java初始 化一个类或接口的处理过程如下(这里对类初始化处理过程的说明,省略了与本文无关的部 分;同时为了更好的说明类初始化过程中的同步处理机制,笔者人为的把类初始化的处理过程 分为了5个阶段)。
    第1阶段:通过在Class对象上同步(即获取Class对象的初始化锁),来控制类或接口的初始 化。这个获取锁的线程会一直等待,直到当前线程能够获取到这个初始化锁。 假设Class对象当前还没有被初始化(初始化状态state,此时被标记为state=noInitializa- tion),且有两个线程A和B试图同时初始化这个Class对象。
    在这里插入图片描述
    在这里插入图片描述

    第2阶段:线程A执行类的初始化,同时线程B在初始化锁对应的condition上等待。
    在这里插入图片描述
    在这里插入图片描述
    第3阶段:线程A设置state=initialized,然后唤醒在condition中等待的所有线程。
    在这里插入图片描述在这里插入图片描述

    第4阶段:线程B结束类的初始化处理。
    在这里插入图片描述在这里插入图片描述
    线程A在第2阶段的A1执行类的初始化,并在第3阶段的A4释放初始化锁;线程B在第4阶 段的B1获取同一个初始化锁,并在第4阶段的B4之后才开始访问这个类。根据Java内存模型规 范的锁规则,这里将存在如下的happens-before关系。 这个happens-before关系将保证:线程A执行类的初始化时的写入操作(执行类的静态初始 化和初始化类中声明的静态字段),线程B一定能看到。

    第5阶段:线程C执行类的初始化的处理。
    在这里插入图片描述在这里插入图片描述

    在第3阶段之后,类已经完成了初始化。因此线程C在第5阶段的类初始化处理过程相对简 单一些(前面的线程A和B的类初始化处理过程都经历了两次锁获取-锁释放,而线程C的类初 始化处理只需要经历一次锁获取-锁释放)。 线程A在第2阶段的A1执行类的初始化,并在第3阶段的A4释放锁;线程C在第5阶段的C1 获取同一个锁,并在在第5阶段的C4之后才开始访问这个类。根据Java内存模型规范的锁规 则,将存在如下的happens-before关系。 这个happens-before关系将保证:线程A执行类的初始化时的写入操作,线程C一定能看
    到。 注意 这里的condition和state标记是本文虚构出来的。Java语言规范并没有硬性规定一 定要使用condition和state标记。JVM的具体实现只要实现类似功能即可。 注意 Java语言规范允许Java的具体实现,优化类的初始化处理过程(对这里的第5阶 段做优化)。
    在这里插入图片描述
    通过对比基于volatile的双重检查锁定的方案和基于类初始化的方案,我们会发现基于类 初始化的方案的实现代码更简洁。但基于volatile的双重检查锁定的方案有一个额外的优势: 除了可以对静态字段实现延迟初始化外,还可以对实例字段实现延迟初始化。
    字段延迟初始化降低了初始化类或创建实例的开销,但增加了访问被延迟初始化的字段 的开销。在大多数时候,正常的初始化要优于延迟初始化。

    如果确实需要对实例字段使用线程 安全的延迟初始化,请使用上面介绍的基于volatile的延迟初始化的方案;
    如果确实需要对静态字段使用线程安全的延迟初始化,请使用上面介绍的基于类初始化的方案。

    先赞后看,养成习惯。欢迎收看一个行走的熊猫程序猿,下期再见

    关注
    文章持续更新,可以微信搜索「 熊猫程序猿a 」第一时间催更
    公众号

    展开全文
  • 单例模式之懒汉单例(延迟初始化)多线程再解析1、多线程下的懒汉单例: public class Lazysingleton { private static Lazysingleton m_instance = null; // 私有默认构造方法,外界无法直接实例化 private ...

    单例模式之懒汉单例(延迟初始化)多线程再解析

    1、多线程下的懒汉单例:

    	public class Lazysingleton {
    		private static Lazysingleton m_instance = null;
    
    		// 私有默认构造方法,外界无法直接实例化
    		private Lazysingleton() {
    		}
    
    		// 静态工厂方法
    		public static Lazysingleton getInstance() throws InterruptedException {
    			// 延迟加载
    			if (m_instance == null) {
    				// 模拟创建对象的准备工作
    				Thread.sleep(3000);
    				m_instance = new Lazysingleton();// 初始化这个单例
    			}
    			return m_instance;
    		}
    	}
    	public class MyThread extends Thread {
    
    		@Override
    		public void run() {
    			try {
    				System.out.println(Lazysingleton.getInstance().hashCode());
    			} catch (InterruptedException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
    		}
    	}
    	public class TestLazy1 {
    
    		public static void main(String[] args) {
    			MyThread t1=new MyThread();
    			MyThread t2=new MyThread();
    			MyThread t3=new MyThread();
    			
    			t1.start();
    			t2.start();
    			t3.start();
    		}
    	}

        
    打印结果说明创建出来三个对象,并不是单例,多线程下懒汉单例是非线程安全的。

    2、多线程下单例模式非线程安全的解决方案:

    2.1、声明synchronized关键字,实现同步方法

    加入同步方法得到相同实例对象,此方法运行效率非常低,是同步运行,下一个线程想要取得对象,必须等上一个线程释放锁后,才能运行。


    2.2、使用同步代码块

    与使用synchronized同步方法一样是同步运行,效率非常低。得到相同实例对象。

    2.3、部分代码上锁,进行单独同步,非线程安全


    2.4、使用DCL双重检查锁定


    使用DCL双重检查锁定成功解决懒汉模式的多线程问题,DCL也是大多数多线程结合单例模式使用的解决方案。

    DCL是常见的延迟初始化技术,但有一个错误的用法。使用DCL需要一些技巧。

    存在错误的根源:

    • a.多线程试图在同一时间创建对象,会通过加锁来保证只有一个线程创建对象。

    • b.在对象创建好后,执行getInstance()方法将不需要获取锁,直接返回创建好的对象。
       

    问题:当代码读取到m_instance不为空,m_instance引用的对象有可能还没有完成初始化。就会出出现问题。

     

    m_instance = new Lazysingleton();

    可以分解为:

    • memory=allocate();1.分配对象的内存空间

    • ctorInstance(memory);2.初始化对象

    • instance=memory;3.设置instance指向刚分配的内存地址


    在Java内存模型中为了优化代码会重排代码,会导致线程看到一个还没被初始化的对象。


    3、线程安全的延迟初始化方案:

    3.1、基于volatile的解决

    声明volatile,初始化代码重排就会被禁止,此方案是通过禁止代码重排来实现线程安全的延迟加载。
    创建对象的过程,实例化对象一般分为三个过程。

    • 1、分配内存空间。

    • 2 、初始化对象。

    • 3 、将内存空间地址赋值给对象的引用。

    但是由于重排序的缘故,步骤2、3可能会发生重排序,其过程如下

    • 1、分配内存空间

    • 2、将内存空间的地址赋值给对应的引用

    • 3、初始化对象

     如果不加volatile的话,可能线程1在初始化的时候重排序了,线程2看到singleton != null,已经返回singleton,其实线程1还没有完成初始化,仅仅只不过是分配了内存空间而已!

     

    3.2、基于类初始化的解决方案(使用静态内置类实现单例)

    这里有几个需要注意的点:

    1.从外部无法访问静态内部类MyObiectHandler,只有当调用getInstance方法的时候,才能得到单例对象INSTANCE。

    2.INSTANCE对象初始化的时机并不是在单例类Singleton被加载的时候,而是在调用getInstance方法,使得静态内部类MyObiectHandler被加载的时候。因此这种实现方式是利用classloader的加载机制来实现懒加载,并保证构建单例的线程安全。

     

    除了使用DCL解决多线程单例模式的非线程安全的问题,使用静态内置类也可以实现同样的效果。










    每天努力一点,每天都在进步。

     

    展开全文
  • Swift初始化init中的一些坑

    千次阅读 2016-10-19 13:26:45
    Swift初始化init中的一些坑
  • ARM处理器系统初始化过程

    千次阅读 2009-12-01 10:38:00
    1 禁止MMU,关闭中断,禁止cache;2 根据硬件设计配制好处理器时钟、DRAM时钟、定时器时钟...5 使能cache,使能MMU,跳转到DRAM内存中运行继续初始化,包括根据具体应用以及系统中的硬件配置初始化各个功能模块、安装好
  • 在Java多线程中,有时候可能需要采用延迟初始化来降低初始化类和创建对象的开销。双重检查锁(饿汉式单例中经常用)是常见的延迟初始化方案,但它是一个错误的用法。本文将分析双重检查锁定的错误根源,以及两张线程...
  • 深入浅出Java类和对象的初始化

    千次阅读 2016-01-03 21:23:51
    最近项目调试中出现了类初始化的问题,虽然事后证明是Eclipse的问题,但也暴露了对Java初始化机制的欠缺,在此翻译一篇javaworld上的文章。这篇文章很好,深入浅出地介绍了Java的初始化细节。 -------------分割线--...
  • 双重检查锁定与延迟初始化

    千次阅读 2014-11-14 13:49:39
    本来是看到多线程中关于...本文转自双重检查锁定与延迟初始化 ifeve.com 主要是介绍并发相关内容的网站,有自己原创内容,也有翻译外文,很给力。 双重检查锁定的由来 在java程序中,有时候可能需要推迟一
  • 转自:http://blog.csdn.net/weide001/article/details/4251689 控制全局变量初始化顺序 ...  全局变量的初始化分两种。一种是static initialization,用常量来初始化,在程序load的时侯就完成了。另一种就是dy
  • 从枚举的初始化说起

    千次阅读 2010-02-24 17:40:00
    从上面的IL代码中,我们可以看出这两种初始化方式已经不再理解为一样的了。对比Code #03和Code #05,你会发现,改变的仅仅是L_0019行: ldc.i4.0 -> ldc.i4.1       也就是说,使用枚举的第一...
  • 14.1 类的初始化 型如下面这样的类:Class data{Public: int inval; char* ptr;};可用如下方式初始化,而不需要提供构造函数:Data local1 = {0, 0}; //称为 显式初始化表 这是因为其数据成员都是公有的。对于...
  • 在Java多线程程序中,有时候需要采用延迟初始化来降低初始化类和创建对象的开销。双重检查锁定是常见的延迟初始化技术,但它是一个错误的用法。本文将分析双重检查锁定的错误根源,以及两种线程安全的延迟初始化方案...
  • 【翻译】内核组件初始化体系结构 为了全面了解内核组件,你不仅需要了解特定的程序做了什么,也要知道这些程序什么时候谁调用。核子系统的初始化是一项基本任务,这些任务由内核根据它自己的模式来处理。这个...
  • Debug 与 Release 版本 变量 初始化(zz)

    千次阅读 2013-07-16 19:12:35
    //z 2013-07-16 19:09:30 IS2120@BG57IV3.T40682136 .K[T83,L895,R28,V1279] Surviving the Release Version By Joseph M. Newcomer, 16 Jul 2001 Introduction ...OK, your program works....
  • 【翻译】设备注册和初始化 第5章和第6章中,我们了解了内核是如何识别网卡以及内核执行初始化过程以使网卡能够和驱动程序通讯。本章中,我们讨论初始化的其它阶段:l 网络设备什么时候,如何注册到内核l 网络设备...
  • 很多场景中,我们需要序列的对象中存在循环引用,在许多的json库中,这会导致stackoverflow。在功能强大的fastjson中,你不需要担心这个问题。例如: A a = new A();   B b =  new B(a);   a.setB(b);  ...
  • 引用(Reference)与指针Moakap引用是一个现有对象的别名。...一旦一个引用被初始化去指向一个特定的对象,那么在该引用的整个生命周期,都绑定到用于初始化的那个对象上。 常量引用于非常量引用 
  • 0x00 面试问到了段时间阿里推面试,二面问到了Python序列漏洞,问我了解吗。我说平时用过,也大概知道其序列后是一种什么形式,但序列漏洞没怎么关注过。心里想,Python序列漏洞难道不是和Java还有PHP...
  • C++ 引用的本质是什么?

    千次阅读 2017-04-07 23:45:16
    C++中的引用本质上是 一种限制的指针。由于引用限制的指针,所以引用是占据内存的。在使用高级语言的层面上,是没有提供...并且引用创建时必需初始化,创建后还不能修改。下面是找到的相关资料,来证明以上结论。
  • 如果引用变量没有引用对象,那么它将视为null。当变量为null发出NullReferenceException. C#和JavaScript中的引用变量在概念上与C和C+中的指针相似。引用类型默认为null若要指示它们没有引用任何对象,请执行以下...
  • 从逆向分析角度看C++ 中的引用

    千次阅读 2013-03-13 22:00:12
    《C++ Primer》里面是这样说的“引用(Reference)就是对象的另一个名字,引用只是它绑定的对象的另一个名字,作用在引用上的所有操作事实上都是作用在该引用绑定的对象上”,这句话概括得很彻底 2. 引用占有...
  • 引用代替指针

    千次阅读 2013-06-20 19:36:00
    建议26:用引用代替指针(1) ...在建立引用时,要用一个具有类型的实体去初始化这个引用,建立这个“外号”与实体之间的对应关系。 对于引用的理解与使用,主要存在两个的问题: 它与指针之间的区别。 未充分
  • 1>f:\mycode\cpptest\main.cpp(68): warning C4189: “nRet”: 局部变量已初始化但不引用 编译器认为,既然我们已经声明/定义了某变量,那我们就有使用它的意图。所以当它检测到我们未对此变量进行实际的使用时,就...
  • 2) 引用初始化以后不能改变,指针可以改变所指的对象。 2) 不存在指向空值的引用,但是存在指向空值的指针。 3. 描述实时系统的基本特性 在特定时间完成特定的任务,实时性与可靠性 4. 全局...
  • 原文地址:点击打开链接一、本节内容本节内容包括:对标准库的扩充: 智能指针和引用计数RAII 与引用计数std::shared_ptrstd::unique_ptrstd::weak_ptr二、RAII 与引用计数了解 Objective-C/Swift 的程序员应该知道...
  • C++中引用(reference)的用法详解

    万次阅读 2013-12-26 18:22:52
    C++中引用(reference)的用法详解 TOC 1.简介 2.引用的语法 3.引用使用技巧  3.1 引用和多态  3.2 作为参数  3.3 作为返回值  3.4 什么时候使用引用 4. 参考资料 1.简介  引用是C++引入的新语言特性。从...
  • 引用计数

    千次阅读 2007-07-29 13:06:00
    引用计数是这样一个技巧,它允许多个有相同值的对象共享这个值的实现。这个技巧有两个常用动机。第一个是简化跟踪堆中的对象的过程。一旦一个对象通过调用new分配出来,最要紧的就是记录谁拥有这个对象,因为其...
  • 这些C++工程师面试题答案收藏好喽

    万次阅读 多人点赞 2019-10-02 10:31:00
    来源:牛客网 编辑:公众号【编程珠玑】 在《这些C++工程师面试题你都会了吗?》分享了一些面试题,应读者强烈要求给出答案,这里给出一部分,答案仅供参考!...初始化:未经初始化的全局静态变量会自动...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 63,291
精华内容 25,316
关键字:

内存在初始化前禁止被引用