剑指XX游戏读后感(1):寻找自己的节奏/placement new/全局对象缺点/堆和栈/malloc和new/hash和map/安卓root/root和system

--------------------------------------------------------------------------------------------------------

写在前面:本文为本系列的首文,本系列主要是根据一位大牛学长的《剑指XX游戏》的文章的读后感,以及自己的相关补充,帮助提高自己的技术,提前为半年后的校招蓄力。原文地址:《剑指XX游戏》http://blog.csdn.net/qp120291570/article/category/1848977,作者:拳四郎。Now let's start!

--------------------------------------------------------------------------------------------------------

本文讲述了学长面试XX游戏的首战,貌似战况不是很喜人,不过也暴露出了很多问题,下面来一一解析:

问题1: 什么是placement new?

在C++中new和delete我们已经很熟悉了,根据C++11的文档,new的函数声明式如下:

/*throwing */   void* operator new (std::size_t size);
/*nothrow*/     void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) noexcept;
/*placement*/ void* operator new (std::size_t size, void* ptr) noexcept;

第1种方法是我们平时最常用的new,即throwing new,第2个nothrow new和throwing new的区别是,在分配内存失败的时候,就返回空指针,而不是抛出异常。第3个方法就是本次重点讨论的placement new了。在开始介绍placement new之前,我想先复习一下new的用法:

class A{
    int a;
public:
    A(int input):a(input){ }
};

int main(){
    A* pa = new A(8);
}

// 上述的程序使用了new关键字动态创建了一个A的对象并用指针pa指向它,在执行的过程中,new operator语句的执行过程大致可以分为三句话:
//  A* pa = (A*)malloc(sizeof(A));     
//  pa->A::A(8);
//  return pa;

除了最基本的用法,我们还可以重载operator new(局部的和全局的都能重载):

void* operator new(size_t size){        //重载全局的operator new
    std::cout << "global new called" << std::endl;
    return malloc(size);
}

class A{
    int a;
public:
    A(int input):a(input){ }
    void* operator new(size_t size){    //重载类的的operator new
           std::cout << "operator new called" << std::endl;
           return ::operator new(size);
    }
};

int main(){
    A* pa = new A(8);
}
//输出:   operator new called
//                global new called
好了,下面开始进入主题了:placement new是operator new的一个重载,是new operator的一个执行步骤(上文所述,第2步)。如果你想在已经分配的内存中创建一个对象,使用new是行不通的。也就是说placement new允许你在一个已经分配好的内存中(栈或者堆中)构造一个新的对象。原型中void*p实际上就是指向一个已经分配好的内存缓冲区的的首地址。我们知道使用new操作符分配内存需要在堆中查找足够大的剩余空间,这个操作速度是很慢的,而且有可能出现无法分配内存的异常(空间不够)。placement new就可以解决这个问题。我们构造对象都是在一个预先准备好了的内存缓冲区中进行,不需要查找内存,内存分配的时间是常数。而且不会出现在程序运行中途出现内存不足的异常。所以,placement new非常适合那些对时间要求比较高,长时间运行不希望被打断的应用程序。 placement new使用方法如下:

class Num{
    int i;
public:
    Num(int input):i(input){}
    void print(){cout << "I'm " << i << endl;}
};

int main(){
    char* buff = new char[ sizeof(Num) ];
    memset( buff, 0, sizeof(Num) );
    Num *obj1 = new (buff)Num(1);
    obj1 -> print();    
    obj1->~Num();<span style="white-space:pre">	</span>//手动析构,以便其他对象使用该块内存
    Num *obj2 = new (buff)Num(2);<span style="white-space:pre">	</span>//再次使用该块内存
    obj2 -> print();
    obj2->~Num();
    delete [] buff;<span style="white-space:pre">	</span>//释放内存
    return 0;
}
在同一块已经分配的内存上,我先实例化了obj1,析构以后,又实例化了obj2,再释放内存。

总结:

  1. 为了维持一致性,在重载了operator new 以后, 也要记得重载相应的operator delete
  2. new operator不能被重载
  3. placement new有助于提升系统分配内存空间的速度,因为不用查找空余内存块再分配的过程,直接使用指定内存块。

new 或者malloc最多能申请多大的内存?

Linux在32位下,用户态能申请到3G内存,还有1G留给内核。在内核态,可以直接申请到4G内存。
Windows在32下,一定不可能超过4G,但是具体多少,还需要查看相关文档。

linux下用top命令显示有内存空间,但malloc一个64mbuffer的时候失败,什么原因,为啥会出现这情况?试着malloc一个1m的buffer可能成功么?

内存中的可用空间(内存碎片会造成上述现象,外部碎片和内部碎片都会造成)。
可能会成功。

使用全局对象有什么缺点,内存是如何分配与回收的?

  1. (函数耦合度高,移植难)使用全局对象的函数依赖于全局对象的存在和类型,这使得在不同上下文环境中重用该函数更加困难
  2. (维护难,维护代价高)如果程序必须被修改,则全局依赖增加了引入错误的可能性,而且既使只对局部做修改也要求程序员必须理解整个程序
  3. (debug难)如果全局对象得到一个不正确的值,则必须查找整个程序以判断错误发生的位置
  4. (递归设计难)当一个函数使用全局对象时,递归更加难以正确完成,递归在程序调用自身时才发生
  5. (多线程-数据竞争)在线程存在的情况下,我们必须做特殊的编码,以便同步各个线程对于全局对象的读和写操作

说一下进程和线程的堆栈内存管理

线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.
堆:是大家共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是记得用完了要还给操作系统,要不然就是内存泄漏。
栈:是个线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立,因此,栈是thread safe的。

使用malloc申请对象指针内存,然后编译,是否会通过,在什么时候会出错?对其使用free的话会出现什么错误?

先上代码:
class Object{
public:
    Object(int i):id(i){ cout<<"Constructor"<<endl; }
    ~Object(){ cout<<"Destructor"<<endl; }
    void sayHi(){ cout<<"Hi,I am No."<<id<<endl; }
private:
    int id;
};

int main(){
    Object *p;
    p = new Object(10);
    p->sayHi();
    delete p;
    //free(p);
    return 0;
}

//使用delete p可以自动调用析构函数,而free(p)不会调用任何函数,就直接释放内存
  • 如果在析构函数中,如果有释放内存空间的语句,那使用free的话,就会造成内存泄露。更有甚者,将new语句换成 p = (Object*)malloc(sizeof(Object));  ,则构造函数也不会执行,所有变量都是系统默认初始值,有可能出现用户自定义类型未被初始化的情况
  • 但是,程序编译运行都没有错误。

请说说malloc/free和new/delete的本质区别

  • malloc/free是C/C++语言的标准库函数,new/delete是C++的运算符。
  • 对于用户自定义的对象而言,用maloc/free无法满足动态管理对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。因此C++需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。

c++代码编译成可执行文件的过程

  1. 编译预处理:宏定义指令、条件编译指令、头文件包含指令
  2. 编译、优化阶段: 编译程序所要作得工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码
  3. 汇编过程:把汇编语言代码翻译成目标机器指令的过程
  4. 链接程序:静态链接和动态链接
编译阶段将源程序(*.c)转换成为目标代码(,一般是obj文件,至于具体过程就是上面说的那些阶段),连接阶段是把源程序转换成的目标代码(obj文件)与你程序里面调用的库函数对应的代码连接起来形成对应的可执行文件(exe文件)就可以了

请简述Hash表和map的区别

其实就是比较哈希表和红黑树。
  • 构造函数。hash_map需要hash函数,等于函数;map只需要比较函数(小于函数).
  • 存储结构。hash_map采用hash表存储,map一般采用红黑树(RB Tree)实现。因此其memory数据结构是不一样的
  • 适用情况:总体来说,hash_map 查找速度会比map快,而且查找速度基本和数据量大小无关,属于常数级别;而map的查找速度是log(n)级别。并不一定常数就比log(n) 小,hash还有hash函数的耗时,明白了吧,如果你考虑效率,特别是在元素达到一定数量级时,考虑考虑hash_map。但若你对内存使用特别严格,希望程序尽可能少消耗内存,那么一定要小心,hash_map可能会让你陷入尴尬,特别是当你的hash_map对象特别多时,你就更无法控制了,而且 hash_map的构造速度较慢。
权衡三个因素: 查找速度, 数据量, 内存使用。

请简述Android系统架构


  • 最上层是Java应用程序层(Applicatipons):全部用Java完成,调用Application Framework提供的API完成。
  • 第二层是应用程序框架层(Application Framework):为开发人员提供了可以完全访问核心应用程序所使用的API框架
  • 第三层是系统运行时库:包括程序库和Android运行库两部分。前者为一些C/C++库,后者(Android运行时库)又分为核心库和Dalvik虚拟机两部分。核心库提供了Java语言核心库的大多数功能,这里主要通过JNI的方式向应用程序框架层提供调用底层程序库的接口。Dalvik虚拟机是为了能同时高效地运行多个VMs而实现的
  • 最底层为Linux内核层,Android对Linux内核做了一定的增强

请简述Android的root原理,是否可以还原?

Android的内核就是Linux,所以Android获取root其实和Linux获取root权限是一回事儿。
  • 你想在Linux下获取root权限的时候就是执行sudo或者su,接下来系统会提示你输入root用户的密码,密码正确就获得root权限了。Android本身就不想让你获得Root权限,大部分手机出厂的时候根本就没有su这个程序。所以你想获得Android的root权限,第一步就是要把编译好的su文件拷贝到Android手机的/system/bin或者/system/xbin/目录下。我们先假设你可以把su放在xbin下,接下来你可以在Android手机的adb shell或者串口下输入su了。
  • Linux下su以后输入密码就可以root了,但Android里的su和Linux里的su是不一样的,Android里的su不是靠验证密码的,而是看你原来的权限是什么。意思就是如果你是root,那你可以通过su切换到别的用户,比如说shell,wifi,audio什么的。但如果你是root之外的其他用户,就不能切换回root了,会提示你permission denied。
  • 其实Android系统的破解的根本原理就是替换掉系统中的su程序,因为系统中的默认su程序需要验证实际用户权限(只有root和 shell用户才有权运行系统默认的su程序,其他用户运行都会返回错误)。而破解后的su将不检查实际用户权限,这样普通的用户也将可以运行su程序, 也可以通过su程序将自己的权限提升。手机Root后,最重要的是,给手机安装了su程序和superuser apk。 su一般被安装在/system/xbin 或者 /system/bin 下面。可以理解成root 破解就是在你系统中植入“木马su”,说它是“木马”一点儿都不为过,假如恶意程序在系统中运行也可以通过su来提升自己的权限的这样的结果将会是灾难性 的。所以一般情况下root过手机都会有一个SuperUser应用程序来让用户管理允许谁获得root权限,也算是给系统加了一层保险吧!
  • 还原的话讲原来的su替换即可。

android中system和root用户有什么区别?

  • Root是Linux等类UNIX系统中的超级管理员用户帐户,该帐户拥有整个系统至高无上的权利,所有对象他都有可以操作的权利,所以很多黑客在入侵系统的时候,都要把权限提升到Root权限,也就是将自己的非法帐户添加到Root用户组。类比于Administrator是Windows NT内核系统中的超级管理员用户帐户,也拥有最高的权限。但不同的是,在WINDOWS下Administrator的资源和别的用户资源是共享的,简单的说,别的用户可以访问Administrator的文件。而Linux中,别的用户是不能访问Root用户的家目录(/root)下文件的。因此,Linux比Windows更安全。由于Root权限对于系统具有最高的统治权,便可方便的对于系统的部件进行删除或更改。
  • system也是Linux的一个用户名,常见的情形为在未破解的Android手机上,当你链接真机在PC上执行adb shell时,adb 是以system的用户规则进行操作的。system与普通的App区别在于为了整个Android系统的运行在"/"目录下有一些"system"生成的目录及文件。

从操作系统的角度描述Android运行一个app?

  • 每一个Android应用程序进程都有一个Dalvik虚拟机实例。这样做的好处是Android应用程序进程之间不会相互影响,也就是说,一个Android应用程序进程的意外中止,不会影响到其它的Android应用程序进程的正常运行。
  • 每一个Android应用程序进程都是由一种称为Zygote的进程fork出来的。Zygote进程是由init进程启动起来的,也就是在系统启动的时候启动的。Zygote进程在启动的时候,会创建一个虚拟机实例,并且在这个虚拟机实例将所有的Java核心库都加载起来。每当Zygote进程需要创建一个Android应用程序进程的时候,它就通过复制自身来实现,也就是通过fork系统调用来实现。这些被fork出来的Android应用程序进程,一方面是复制了Zygote进程中的虚拟机实例,另一方面是与Zygote进程共享了同一套Java核心库。这样不仅Android应用程序进程的创建过程很快,而且由于所有的Android应用程序进程都共享同一套Java核心库而节省了内存空间。

Android系统如何保护app种的sql数据不被篡改?

  • 未曾Root过的手机,每个App只能访问自己的data文件夹下的数据库,没有访问其他app/data文件夹的权限,所以无法随意修改其他应用的sqlite数据。
  • Root过的手机都可以进入到/data/data/<package_name>/databases目录下面,在这里就可以查看到数据库中存储的所有数据。如果是一般的数据还好,但是当涉及到一些账号密码,或者聊天内容的时候,我们的程序就会面临严重的安全漏洞隐患。
面试中,还涉及到一些网络编程和Node.js的问题
最后,学长的一句话深深敲醒了我:找到自己的节奏,稳扎稳打,然后抓住每一个机会,把局势掌握在自己手里!是的,就该这样,继续加油!

--------------------------------------------------------------------------------------------------------

参考文献:

深入C++的new,作者:songthin

http://www.cplusplus.com/reference/new/operator%20new/,C++文档

new、operator new、placement new,作者:执迷不悟~

--------------------------------------------------------------------------------------------------------



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值