面经

C/C++ static关键字作用

  1. 隐藏:当同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性。

  2. 全局生命周期:存储在静态数据区的变量会在程序刚开始运行时就完成唯一的一次初始化。 共有两种变量存储在静态存储区:全局变量和static变量,与全局变量相比,static可以控制变量的可见范围。

  3. 变量默认初始化为0:在静态数据区,内存中所有的字节默认值都是0x00,所以全局变量也具备这一属性。

  4. C++中的类成员声明static

    • static修饰的变量或函数是属于类的,所有对象只有一份拷贝。 因此,不能将静态函数设为虚函数。
    • 静态数据成员是静态存储的,所以必须对它进行初始化。 (程序员手动初始化,否则编译时一般不会报错,但是在Link时会报错误)

vector是如何扩容的

往vector中添加元素时,如果空间不够将会导致扩容。vector有两个属性:size和capacity。size表示已经使用的数据容量,capacity表示数组的实际容量,包含已使用的和未使用的。

vector扩容规则:

  1. 当数组大小不够容纳新增元素时,开辟更大的内存空间,把旧空间上的数据复制过来,然后在新空间中继续增加。
  2. 新的更大的内存空间,一般是当前空间的1.5倍或者2倍,这个1.5或者2被称为扩容因子,不同系统实现扩容因子也不同。

重载和重写的区别

重载:两个函数名相同,但是参数列表不同(个数,类型),返回值类型没有要求,在统一作用域中。

覆盖:子类继承父类,父类中的函数是虚函数,在子类中重新定义了这个虚函数,这种情况是重写。

重写:

(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。

2.5亿个数求不重复数

有2.5亿个整数(这2.5亿个整数存储在一个数组里面,至于数组是放在外存还是内存,没有进一步具体说明);
要求找出这2.5亿个数字里面, 不重复的数字的个数 ; 另外,可用的内存限定为600M;

用一个bit表示一个数是否存在,32bit中无符号整数有4G个,共需4G bits,每个字节8 bits,需要4G/8 = 512M字节;申请512M内存,作为一个数是否存在的标记flag,全清0;申请另外一组bit作为计数器count,全清0;

volatile关键字

volatile直译为易失性的,意在声明某变量有可能被编译器未知的因素更改,如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化。例如:

void waitForSemaphore()
{
   volatile uint16_t* semPtr = WELL_KNOWN_SEM_ADDR;/*well known address to my semaphore*/
   while ((*semPtr) != IS_OK_FOR_ME_TO_PROCEED);
}

如果未进行易失性声明,优化编译器会把变量从内存装入CPU寄存器中, 即使其他线程对内存中的变量进行修改,当前线程只使用寄存器中的值,while循环将是一个死循环;加修饰字之后,编译器每次操作该变量时会从内存中真正取出。

volatile int i=10;
int a = i;
int b = i;

volatile指出i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在b中。而优化做法是,由于编译器发现两次读数据的代码之间的代码没有对i进行过操作,它会自动把上次读的数据放在 b中。而不是重新从i里面读。

参考

https://stackoverflow.com/a/72576/16657286

https://zhuanlan.zhihu.com/p/62060524

常驻内存和共享内存

  • 常驻内存:进程间切换时,操作系统会将数据从内存复制到磁盘上来释放一部分内存。然而,当一部分内存指定为常驻时,它不会与磁盘交换。使频繁访问的数据常驻在内存中可以减少访问该数据所需的磁盘 I/O 操作,如操作系统的中心部分和一些特殊程序,如日历和计算器。
  • 共享内存是进程间通信中最简单的方式之一。共享内存允许两个或更多进程访问同一块内存,当一个进程改变了这块地址中的内容的时候,其它进程都会察觉到这个更改。

DNS协议

DNS (Domain Name System)的作用非常简单,就是根据域名查出IP地址。

客户端在上网的时候,需要设置:本机的IP地址、子网掩码、网关的IP地址、DNS服务器的IP地址。我们就是通过DNS服务器查得域名对应的IP地址。DNS服务器的IP地址,有可能是动态的,每次上网时由网关分配,这叫做DHCP机制;也有可能是事先指定的固定地址。

DNS服务器怎么会知道每个域名的IP地址呢?答案是分级查询。

域名的层级

举例来说,www.example.com真正的域名是www.example.com.root,简写为www.example.com.。因为,根域名.root对于所有域名都是一样的,所以平时是省略的。

根域名的下一级,叫做”顶级域名”(top-level domain,缩写为TLD),比如.com.net;再下一级叫做”次级域名”(second-level domain,缩写为SLD),比如www.example.com里面的.example,这一级域名是用户可以注册的;再下一级是主机名(host),比如www.example.com里面的www,又称为”三级域名”,这是用户在自己的域里面为服务器分配的名称,是用户可以任意分配的。

总结一下,域名的层级结构如下。

主机名.次级域名.顶级域名.根域名

# 即

host.sld.tld.root

DNS记录

域名与IP之间的对应关系,称为”记录”。根据使用场景,”记录”可以分成不同的类型。

1A:地址记录(Address),返回域名指向的IP地址。
(2NS:域名服务器记录(Name Server),返回保存下一级域名信息的服务器地址。该记录只能设置为域名,不能设置为IP地址。
(3CNAME:规范名称记录(Canonical Name),返回另一个域名,即当前查询的域名是另一个域名的跳转。

分级查询

“分级查询”,就是从根域名开始,依次查询每一级域名的NS记录,直到查到最终的IP地址。每一级域名都有自己的NS记录,NS记录指向该级域名的域名服务器。这些服务器知道下一级域名的各种记录。过程大致如下。

1. 从"根域名服务器"查到"顶级域名服务器"的NS记录和A记录(IP地址)
2. 从"顶级域名服务器"查到"次级域名服务器"的NS记录和A记录(IP地址)
3. 从"次级域名服务器"查出"主机名"的IP地址

上面,”根域名服务器”的NS记录和IP地址一般是不会变化的,所以内置在DNS服务器里面。

参考

https://www.ruanyifeng.com/blog/2016/06/dns.html

进程的内存分布

从低地址到高地址,分别包括:

  1. 文本段,也叫代码段,是对象文件或内存中程序的一部分,其中包含可执行指令。通常代码段是共享的,对于经常执行的程序,只有一个副本需要存储在内存中,代码段是只读的,以防止程序以外修改指令。
  2. 初始化的数据段,是程序的虚拟地址空间的一部分,它包含有程序员初始化的全局变量和静态变量,可以进一步划分为只读区域和读写区域。例如,C中的char=“hello world”的全局字符串,以及main(例如全局)之外的int debug=1这样的C语句。
  3. 未初始化的数据段,通常称为bss段,这个段的数据在程序开始之前有内核初始化为0,包含所有初始化为0和没有显示初始化的全局变量和静态变量,
  4. 堆,堆是动态内存分配通常发生的部分。堆是由程序员自己分配的(malloc kmalloc等)。堆区域由所有共享库和进程中动态加载的模块共享。
  5. 栈,存放临时变量,以及每次调用函数时调用栈。每当调用一个函数时,返回到的地址和关于调用者环境的某些信息的地址,比如一些机器寄存器,就会被保存在栈中。然后,新调用的函数在栈上分配空间,用于自动和临时变量。