面试题整理

1.char* 和 char[]有什么区别?

[cpp]  view plain copy
  1. char* get_str(void)  
  2. {  
  3.     char str[] = {"abcd"};  
  4.     return str;  
  5. }  

       char str[] = {"abcd"};定义了一个局部字符数组,尽管是数组,但它是一个局部变量,返回它的地址肯定是一个已经释放了的空间的地址。
       此函数返回的是内部一个局部字符数组str的地址,且函数调用完毕后 此数组被销毁,所以你返回的指针也就指向一块被销毁的内存,这种写法是错误的。

[cpp]  view plain copy
  1. char* get_str(void)  
  2. {  
  3.     char *str = {"abcd"};  
  4.     return str;  
  5. }  

      char* str = {"abcd"};表示先定义个字符串常量,并将其地址赋给str。
      此函数返回的是字符串常量的地址,而像这种字符串都是属于全局的,在编译的时候就已经分配了内存了,只有程序退出的时候才会被销毁,所以返回它的地址是没有问题的,但是你最好返回常量指针,因为你不能去改变字符串常量的值。

     这个str就在栈里面,但后面的字符串在常量区,函数返回的时候,先将常量区的地址返回,然后作用域结束,释放str在栈里面的空间。。

[cpp]  view plain copy
  1. const char str[] = "abcd";        //abc存储在堆栈中  
  2. const char *str = "abcd";         //abc存储在静态存储区  

        准确的说,上面两个“abc"都是存储在静态存储区,即常量区。常量区是可读不可写的。所以任何试图对常量区进行写的操作都是非法的,当然了,这也不是一定不可写的,你可以采取某种渠道改变常量区的内存属性,比如改变pe相关节的属性就可以对常量区进行读写,当然了,这个目前可以忽略。。。
       那么为什么str[] = "abc";      可以写呢?   答案就在str[] = "abc";会有一个额外的拷贝过程,即把常量区的 "abc"拷贝到栈内存去,所以就可以写了。

      总结:
所有以" "或' '包含的字符、字符串都是常量,应该是存储在常量区
char *str = "xxxxx",str指向该常量地址。
char str[] = "xxxxx",str在栈上申请空间,将常量内容复制进来,所以是局部变量

首先,数组和指针是不同的数据类型,有本质的区别:
char str[] = "abcd";         //sizeof(str) == 5 * sizeof(char)
char * str = "abcd";        //sizeof(str) == 4(x86) or 8(x64)
数组可以自动转型成指针,指针不能转成数组。

然后,字符串等价于字符数组,而不等于字符指针。根据上一条,字符串可以自动转型成字符指针。

再然后,"abcd"叫做“字符串常量”,任何类型的常量都是右值(没有名字的临时变量),必须让"abcd"成为左值(有名字的变量),才能够修改"abcd"这个字符串。
char str[] = "abcd";              //等号两端是相同的数据类型,右值成为左值
char * str = "abcd";             //等号两端是不同的数据类型,右端自动转型成char*,该char*得到了str的名字,而"abcd"这个char数组仍然没有名字。

char * str是存储在全局静态存储区,所以,虽然是局部变量但函数返回后依然可以拿到正确的值!
char str[] 是存储在栈上的,local variable ,函数返回后,OS就收回空间了,就不复存在了,所以,拿不到正确的结果!

 char str[]="name";与char str[5];str="name" 的不同之处在哪,能不能从内存分配的角度讲一讲,我知道数组名字是一个常量地址(指针),第一个为什么对,第二个为什么错?

       第二个先定义了一个数组,要知道数组名str是数组分配到的空间的首地址,str="name"应该是等号两边类型不匹配的错误。一般的常量应该没有内存地址的,除非有某个变量指向了该常量。
       数组名是地址常量,那么常量当然不允许被重新赋值。
       "name"是一个字符串常量他存储在常量存储区,只能用一个指针指向它却不允许改变:char*p;p="name";
      一般情况下char str[]="name";数组是在栈上的空间由编译器分配,内容可以由用户改变。

2.Page

In computer operating systemspaging is one of the memory-management schemes by which a computer can store and retrieve data from secondary storage for use in main memory. In the paging memory-management scheme, the operating system retrieves data from secondary storage in same-size blocks called pages. The main advantage of paging over memory segmentation is that it allows the physical address space of a process to be noncontiguous. Before paging came into use, systems had to fit whole programs into storage contiguously, which caused various storage and fragmentation problems

 3.什么是 Thrashing

Thrashing is computer activity that makes little or no progress, usually because memory or other resources have become exhausted or too limited to perform needed operations. When this happens, a pattern typically develops in which a request is made of the operating system by a process or program, the operating system tries to find resources by taking them from some other process, which in turn makes new requests that can't be satisfied. In a virtual storage system (an operating system that manages its logical storage or memory in units called pages), thrashing is a condition in which excessive paging operations are taking place.

A system that is thrashing can be perceived as either a very slow system or one that has come to a halt.

4.volatile关键字

  volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。

5.critical section

临界区

6.C++里面的 dynamic_cast 如何实现,为什么 dynamic_cast 要求被 cast 的指针指向的类至少要有一个 virtual method?

作为四个内部类型转换操作符之一的dynamic_cast和传统的C风格的强制类型转换有着巨大的差别。除了dynamic_cast以外的转换,其行为的都是在编译期就得以确定的,转换是否成功,并不依赖被转换的对象。而dynamic_cast则不然。在这里,不再讨论其他三种转换和C风格的转换。
首先,dynamic_cast依赖于RTTI信息,其次,在转换时,dynamic_cast会检查转换的source对象是否真的可以转换成target类型,这种检查不是语法上的,而是真实情况的检查。
先看RTTI相关部分,通常,许多编译器都是通过vtable找到对象的RTTI信息的,这也就意味着,如果基类没有虚方法,也就无法判断一个基类指针变量所指对象的真实类型, 这时候,dynamic_cast只能用来做安全的转换,例如从派生类指针转换成基类指针.而这种转换其实并不需要dynamic_cast参与.
也就是说,dynamic_cast是根据RTTI记载的信息来判断类型转换是否合法的


7.virtual inheritance 为什么能够避免导出类中有多于一个的 virtual base class 的 copy?
    虚继承和虚基类的定义是非常的简单的,同时也是非常容易判断一个继承是否是虚继承 的,虽然这两个概念的定义是非常的简单明确的,但是在C++语言中虚继承作为一个比较生 僻的但是又是绝对必要的组成部份而存在着,并且其行为和模型均表现出和一般的继承体系 之间的巨大的差异(包括访问性能上的差异),现在我们就来彻底的从语言、模型、性能和 应用等多个方面对虚继承和虚基类进行研究。
    首先还是先给出虚继承和虚基类的定义。
    虚继承:在继承定义中包含了virtual关键字的继承关系;
    虚基类:在虚继承体系中的通过virtual继承而来的基类,需要注意的是:  struct CSubClass : public virtual CBase {}; 其中CBase称之为CSubClass 的虚基类,而不是说CBase就是个虚基类,因为CBase还可以不不是虚继承体系 中的基类。
    有了上面的定义后,就可以开始虚继承和虚基类的本质研究了,下面按照语法、语义、
模型、性能和应用五个方面进行全面的描述。
    1. 语法
       语法有语言的本身的定义所决定,总体上来说非常的简单,如下: struct CSubClass : public virtual CBaseClass {}; 其中可以采用public、protected、private三种不同的继承关键字进行修饰,只要 确保包含virtual就可以了,这样一来就形成了虚继承体系,同时CBaseClass就成为 了CSubClass的虚基类了。
       其实并没有那么的简单,如果出现虚继承体系的进一步继承会出现什么样的状况呢?
       如下所示:
            /*
             * 带有数据成员的基类
             */
            struct CBaseClass1
            {
                CBaseClass1( size_t i ) : m_val( i ) {}
            
                size_t m_val;
            };
            /*
             * 虚拟继承体系
             */
            struct CSubClassV1 : public virtual CBaseClass1
            {
                CSubClassV1( size_t i ) : CBaseClass1( i ) {}
            };            
            struct CSubClassV2 : public virtual CBaseClass1
            {
                CSubClassV2( size_t i ) : CBaseClass1( i ) {}
            };            
            struct CDiamondClass1 : public CSubClassV1, public CSubClassV2
            {
                CDiamondClass1( size_t i ) : CBaseClass1( i ), CSubClassV1( i ), CSubClassV2( i ) {}
            };            
            struct CDiamondSubClass1 : public CDiamondClass1
            {
                CDiamondSubClass1( size_t i ) : CBaseClass1( i ), CDiamondClass1( i ) {}
            };
       注意上面代码中的CDiamondClass1和CDiamondSubClass1两个类的构造函数初始化列 表中的内容。可以发现其中均包含了虚基类CBaseClass1的初始化工作,如果没有这 个初始化语句就会导致编译时错误,为什么会这样呢?一般情况下不是只要在 CSubClassV1和CSubClassV2中包含初始化就可以了么?要解释该问题必须要明白虚 继承的语义特征,所以参看下面语义部分的解释。   
    2. 语义
       从语义上来讲什么是虚继承和虚基类呢?上面仅仅是从如何在C++语言中书写合法的 虚继承类定义而已。首先来了解一下virtual这个关键字在C++中的公共含义,在C++ 语言中仅仅有两个地方可以使用virtual这个关键字,一个就是类成员虚函数和这里  所讨论的虚继承。不要看这两种应用场合好像没什么关系,其实他们在背景语义上 具有virtual这个词所代表的共同的含义,所以才会在这两种场合使用相同的关键字。
       那么virtual这个词的含义是什么呢?
       virtual在《美国传统词典[双解]》中是这样定义的:
           adj.(形容词)
           1. Existing or resulting in essence or effect though not in actual 
              fact, form, or name:
              实质上的,实际上的:虽然没有实际的事实、形式或名义,但在实际上或效
              果上存在或产生的;
           2. Existing in the mind, especially as a product of the imagination. 
              Used in literary criticism of text.
       虚的,内心的:在头脑中存在的,尤指意想的产物。用于文学批评中。 我们采用第一个定义,也就是说被virtual所修饰的事物或现象在本质上是存在的, 但是没有直观的形式表现,无法直接描述或定义,需要通过其他的间接方式或手段 才能够体现出其实际上的效果。 那么在C++中就是采用了这个词意,不可以在语言模型中直接调用或体现的,但是确  实是存在可以被间接的方式进行调用或体现的。比如:虚函数必须要通过一种间接的 运行时(而不是编译时)机制才能够激活(调用)的函数,而虚继承也是必须在运行  时才能够进行定位访问的一种体制。存在,但间接。其中关键就在于存在、间接和共 享这三种特征。 对于虚函数而言,这三个特征是很好理解的,间接性表明了他必须在运行时根据实际 的对象来完成函数寻址,共享性表象在基类会共享被子类重载后的虚函数,其实指向 相同的函数入口。 对于虚继承而言,这三个特征如何理解呢?存在即表示虚继承体系和虚基类确实存在, 间接性表明了在访问虚基类的成员时同样也必须通过某种间接机制来完成(下面模型 中会讲到),共享性表象在虚基类会在虚继承体系中被共享,而不会出现多份拷贝 那现在可以解释语法小节中留下来的那个问题了,“为什么一旦出现了虚基类,就必 须在没有一个继承类中都必须包含虚基类的初始化语句”。由上面的分析可以知道, 虚基类是被共享的,也就是在继承体系中无论被继承多少次,对象内存模型中均只会 出现一个虚基类的子对象(这和多继承是完全不同的),这样一来既然是共享的那么 每一个子类都不会独占,但是总还是必须要有一个类来完成基类的初始化过程(因为 所有的对象都必须被初始化,哪怕是默认的),同时还不能够重复进行初始化,那到 底谁应该负责完成初始化呢?C++标准中(也是很自然的)选择在每一次继承子类中 都必须书写初始化语句(因为每一次继承子类可能都会用来定义对象),而在最下层 继承子类中实际执行初始化过程。所以上面在每一个继承类中都要书写初始化语句, 但是在创建对象时,而仅仅会在创建对象用的类构造函数中实际的执行初始化语句, 其他的初始化语句都会被压制不调用。
       
    3. 模型
       为了实现上面所说的三种语义含义,在考虑对象的实现模型(也就是内存模型)时就 很自然了。在C++中对象实际上就是一个连续的地址空间的语义代表,我们来分析虚 继承下的内存模型。
       3.1. 存在
       也就是说在对象内存中必须要包含虚基类的完整子对象,以便能够完成通过地址 完成对象的标识。那么至于虚基类的子对象会存放在对象的那个位置(头、中间、 尾部)则由各个编译器选择,没有差别。(在VC8中无论虚基类被声明在什么位置, 虚基类的子对象都会被放置在对象内存的尾部)
       3.2. 间接
        间接性表明了在直接虚基承子类中一定包含了某种指针(偏移或表格)来完成通 过子类访问虚基类子对象(或成员)的间接手段(因为虚基类子对象是共享的,  没有确定关系),至于采用何种手段由编译器选择。(在VC8中在子类中放置了 一个虚基类指针vbc,该指针指向虚函数表中的一个slot,该slot中存放则虚基 类子对象的偏移量的负值,实际上就是个以补码表示的int类型的值,在计算虚  基类子对象首地址时,需要将该偏移量取绝对值相加,这个主要是为了和虚表 中只能存放虚函数地址这一要求相区别,因为地址是原码表示的无符号int类型 的值)
       3.3. 共享
        共享表明了在对象的内存空间中仅仅能够包含一份虚基类的子对象,并且通过 某种间接的机制来完成共享的引用关系。在介绍完整个内容后会附上测试代码, 体现这些内容。
    4. 性能
       由于有了间接性和共享性两个特征,所以决定了虚继承体系下的对象在访问时必然 会在时间和空间上与一般情况有较大不同。
       4.1. 时间
           在通过继承类对象访问虚基类对象中的成员(包括数据成员和函数成员)时,都 必须通过某种间接引用来完成,这样会增加引用寻址时间(就和虚函数一样), 其实就是调整this指针以指向虚基类对象,只不过这个调整是运行时间接完成的。 (在VC8中通过打开汇编输出,可以查看*.cod文件中的内容,在访问虚基类对象 成员时会形成三条mov间接寻址语句,而在访问一般继承类对象时仅仅只有一条mov 常量直接寻址语句)
       4.2. 空间
           由于共享所以不同在对象内存中保存多份虚基类子对象的拷贝,这样较之多继承 节省空间。
    5. 应用

       谈了那么多语言特性和内容,那么在什么情况下需要使用虚继承,而一般应该如何使用呢?这个问题其实很难有答案,一般情况下如果你确性出现多继承没有必要,必须要共享基类子对象的时候可以考虑采用虚继承关系(C++标准ios体系就是这样的)。由于每一个继承类都必须包含初始化语句而又仅仅只在最底层子类中调用,这样可能就会使得某些上层子类得到的虚基类子对象的状态不是自己所期望的(因为自己的初始化语句被压制了),所以一般建议不要在虚基类中包含任何数据成员(不要有状态),只可以作为接口类来提供。


8.deadlock's four condition

A deadlock situation can arise if all of the following conditions hold simultaneously in a system:[1]

  1. Mutual Exclusion: At least two resources must be non-shareable.[1] Only one process can use the resource at any given instant of time.
  2. Hold and Wait or Resource Holding: A process is currently holding at least one resource and requesting additional resources which are being held by other processes.
  3. No Preemption: The operating system must not de-allocate resources once they have been allocated; they must be released by the holding process voluntarily.
  4. Circular Wait: A process must be waiting for a resource which is being held by another process, which in turn is waiting for the first process to release the resource. In general, there is a set of waiting processes, P = {P1, P2, ..., PN}, such that P1 is waiting for a resource held by P2, P2 is waiting for a resource held by P3 and so on until PN is waiting for a resource held by P1

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值