Questions from tencent

##指针和引用的区别

###相同点:

  • 都是地址的概念;
    • 指针指向一块内存,它的内容是所指内存的地址;
    • 引用是某块内存的别名。
      ###区别:
  1. 指针是一个实体,而引用仅是个别名;
  2. 引用使用时无需解引用(*),指针需要解引用;
  3. 引用只能在定义时被初始化一次,之后不可变;指针可变;
  4. 引用没有 const,指针有 const;
  5. 引用不能为空,指针可以为空;
  6. “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得
    到的是指针本身(所指向的变量或对象的地址)的大小;
  7. 指针和引用的自增(++)运算意义不一样;
  8. 从内存分配上看:程序为指针变量分配内存区域,而引用不需要分配内存区域。

##static关键字的作用

  1. 在函数体,局部的static变量。生存期为程序的整个生命周期;作用域却在函数体内(它在什么地方能被访问(空间))。 一个被声明为静态的变量在这一函数被调用过程中维持其值不变。因为它分配在静态存储区,函数调用结束后并不释放单元,但是在其它的作用域无法访问。当再次调用这个函数时,这个局部的静态变量还存在,而且作用在它的访问空间,因此访问到的是上次调用后的值。

  2. 在文件模块内(但在函数体外),一个被声明为静态的全局变量可以被模块内所有函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。限制静态全局变量的作用域。

  3. 在文件模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。函数默认情况下是全局函数,可以被任意其它文件模块调用。

##const实现机制,比如:const int i,是怎么做到i只可读的?

实现机制:这些在编译期间完成,对于内置类型,如int, 编译器可能使用常数直接替换掉对此变量的引用。而对于结构体不一定。

看下面的例子:

const int j=100;    
int *p=const_cast<int*>(&j);    
*p=200;    
cout<<j<<endl;    输出为什么是100呢?
cout<<*p<<endl; //输出是改过的200

编译器在优化代码时把cout<<j直接优化成cout<<100了,所以虽然p和&j的值一样,但cout<<j不再通过访问j的地址输出。(反汇编时也有看到直接把数字压栈push 100 )

这是因为,const型在压栈时,是使用的直接的数,就有点像C的#define a 100

##volatile的含义
变量可能在编译器的控制或监控之外改变,告诉编译器不要优化该变量,如被系统时钟更新的变量。

volatile 的本意是“易变的”, 因为访问寄存器要比访问内存单元快的多,所以编译器一般都会作减少存取内存的优化,但有可能会读脏数据。当要求使用 volatile声明变量值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。精确地说就是,遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问;如果不使用 volatile,则编译器将对所声明的语句进行优化。(简洁的说就是:volatile关键词影响编译器编译的结果,用 volatile 声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不要进行编译优化,以免出错)

volatile 的本质:

  • 编译器的优化:在本次线程内, 当读取一个变量时,为提高存取速度,编译器优化时有时会先把变量读取到一个寄存器中;以后,再取变量值时,就直接从寄存器中取值;当变量值在本线程里改变时,会同时把变量的新值 copy 到该寄存器中,以便保持一致。当变量在因别的线程等而改变了值,该寄存器的值不会相应改变,从而造成应用程序读取的值和实际的变量值不一致。当该寄存器在因别的线程等而改变了值,原变量的值不会改变,从而造成应用程序读取的值和实际的变量值不一致。

  • volatile 应该解释为“直接存取原始内存地址”比较合适,“易变的”这种解释简直有点误导人。

##动态链接和静态链接的区别

动态链接是指在生成可执行文件时不将所有程序用到的函数链接到一个文件,
因为有许多函数在操作系统带的dll文件中,当程序运行时直接从操作系统中找。
而静态链接就是把所有用到的函数全部链接到exe文件中。

动态链接是只建立一个引用的接口,而真正的代码和数据存放在另外的可执行模块中,在运行时再装入;而静态链接是把所有的代码和数据都复制到本模块中,运行时就不再需要库了。

##在C++程序中调用被C编译器编译过的函数,为什么要加extern“C”

C++语言支持函数重载,c语言不支持函数重载。函数被C++编译后在库中的名称与C语言不同。假设某个函数的原型是void foo(int x, int y).该函数被c编译器编译后在库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字

C++提供了C连接交换指定符号extern"C"解决名字匹配问题

##判断一个数是否是2的n次方

设数为x
!(x & (x - 1))

##linux下统计一个文件中有多少个给定字符串
grep -o ‘while’ list.c | wc -l

##常问的几个linux命令

###查看cpu信息

cat /proc/cpuinfo
cat /proc/meminfo

###查看硬盘信息
df -lh

###查看网卡信息
dmesg | grep eth

###更常用的命令(显示系统核心版本号、名称、机器类型等)
uname -a

##ipcs命令
ipcs:检查系统上共享内存的分配

###用途
报告进程间通信设施状态。

ipcs 命令往标准输出写入一些关于活动进程间通信设施的信息。如果没有指定任何标志,ipcs 命令用简短格式写入一些关于当前活动消息队列、共享内存段、信号量、远程队列和本地队列标题。

Linux下ipcs指令的用法详解。ipcs是Linux下显示进程间通信设施状态的工具。可以显示消息队列、共享内存和信号量的信息。对于程序员可能更有用些,普通的系统管理员一般用不到此指令。

ipcrm:手动解除系统上共享内存的分配

用途: 删除消息队列、信号集、或者共享内存标识。

##文件描述符fd
fd只是一个整数,在open时产生。起到一个索引的作用,进程通过PCB中的文件描述符表找到该fd所指向的文件指针filp。

文件描述符的操作(如: open)返回的是一个文件描述符,内核会在每个进程空间中维护一个文件描述符表, 所有打开的文件都将通过此表中的文件描述符来引用;

而流(如: fopen)返回的是一个FILE结构指针, FILE结构是包含有文件描述符的,FILE结构函数可以看作是对fd直接操作的系统调用的封装, 它的优点是带有I/O缓存
每个进程在PCB(Process Control Block)即进程控制块中都保存着一份文件描述符表,文件描述符就是这个表的索引,文件描述表中每个表项都有一个指向已打开文件的指针,现在我们明确一下:已打开的文件在内核中用file结构体表示,文件描述符表中的指针指向file结构体。

##内存分配

内存分配方式有三种:

  1. 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。
  2. 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
  3. 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。

##如何定位内存泄露

内存泄漏是指堆内存的泄漏。堆内存是指程序从堆中分配的、大小任意的(内存块的大小可以在程序运行期决定)、使用完后必须显示释放的内存。应用程序一般使用malloc、realloc、new等函数从堆中分配到一块内存,使用完后,程序必须负责相应的调用free或delete释放该内存块。否则,这块内存就不能被再次使用,我们就说这块内存泄漏了。

C++程序缺乏相应的手段来检测内存信息,只能使用top指令观察进程的动态内存总额。而且程序退出时,我们无法获知任何内存泄漏信息

使用Linux命令回收内存,可以使用ps、kill两个命令检测内存使用情况和进行回收。在使用超级用户权限时使用命令“ps”,它会列出所有正在运行的程序名称和对应的进程号(PID)。kill命令的工作原理是向Linux操作系统的内核送出一个系统操作信号和程序的进程号(PID)

##linux 的内存管理机制是什么?

Linux 虚拟内存的实现需要 6 种机制的支持:地址映射机制、内存分配回收机制、缓存和刷新机制、请求页机制、交换机制和内存共享机制内存管理程序通过映射机制把用户程序的逻辑地址映射到物理地址。当用户程序运行时,如果发现程序中要用的虚地址没有对应的物理内存,就发出了请求页要求。如果有空闲的内存可供分配,就请求分配内存(于是用到了内存的分配和回收),并把正在使用的物理页记录在缓存中(使用了缓存机制)。如果没有足够的内存可供分配,那么就调用交换机制;腾出一部分内存。另外,在地址映射中要通过 TLB(翻译后援存储器)来寻找物理页;交换机制中也要用到交换缓存,并且把物理页内容交换到交换文件中,也要修改页表来映射文件地址。