宏定义不是函数,但是使用起来像函数。预处理器用复制宏代码的方式代替函数的调用,省去了函数压栈退栈过程,提高了效率;而内联函数本质上是一个函数,内联函数一般用于函数体的代码比较简单的函数,不能包含复杂的控制语句,while、switch,并且内联函数本身不能直接调用自身。
==============
使用宏定义一定要注意错误情况的出现,比如宏定义函数没有类型检查,可能传进来任意类型,从而带来错误,如举例。还有就是括号的使用,宏在定义时要小心处理宏参数,一般用括号括起来,否则容易出现二义性
同其它函数不同的是,最好将inline函数定义在头文件,而不仅仅是声明,因为编译器在处理inline函数时,需要在调用点内联展开该函数,所以仅需要函数声明是不够的。
======================
1.1.18 说说const和define的区别。
参考回答
const用于定义常量;而define用于定义宏,而宏也可以用于定义常量。都用于常量定义时,它们的区别有:
const生效于编译的阶段;define生效于预处理阶段。
const定义的常量,在C语言中是存储在内存中、需要额外的内存空间的;define定义的常量,运行时是直接的操作数,并不会存放在内存中。
const定义的常量是带类型的;define定义的常量不带类型。因此define定义的常量不利于类型检查。
=============================
普通函数在被调用的时候,需要寻址(函数入口地址);内联函数不需要寻址。
在内联函数内不允许用循环语句和开关语句。 如果内联函数有这些语句,则编译将该函数视同普通函数那样产生函数调用代码,递归函数是不能被用来做内联函数的。内联函数只适合于只有1~5行的小函数。
堆栈缓存方式不同。栈使用的是一级缓存, 它们通常都是被调用时处于存储空间中,调用完毕立即释放;堆则是存放在二级缓存中,速度要慢些。
==========================
内存分配方式:
在C++中,内存分成5个区,他们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区。
自由存储区,就是那些由malloc等分配的内存块,和堆是十分相似的,不过是用free来结束自己的生命。
全局/静态存储区,全局变量和静态变量被分配到同一块内存中
常量存储区,这是一块比较特殊的存储区,里面存放的是常量,不允许修改。
========================
什么是内存泄露?
简单地说就是申请了一块内存空间,使用完毕后没有释放掉。
(1)new和malloc申请资源使用后,没有用delete和free释放;
(2)子类继承父类时,父类析构函数不是虚函数。
(3)Windows句柄资源使用后没有释放。
怎么检测?
第一:良好的编码习惯,使用了内存分配的函数,一旦使用完毕,要记得使用其相应的函数释放掉。
第二:将分配的内存的指针以链表的形式自行管理,使用完毕之后从链表中删除,程序结束时可检查改链表。
第三:使用智能指针。
第四:一些常见的工具插件,如ccmalloc、Dmalloc、Leaky、Valgrind等等。
=============================
数据段:存放程序中已初始化的全局变量和静态变量的一块内存区域。
代码段:存放程序执行代码的一块内存区域。只读,代码段的头部还会包含一些只读的常数变量。
BSS 段:存放程序中未初始化的全局变量和静态变量的一块内存区域。
可执行程序在运行时又会多出两个区域:堆区和栈区。
堆区:动态申请内存用。堆从低地址向高地址增长。
栈区:存储局部变量、函数参数值。栈从高地址向低地址增长。是一块连续的空间。
最后还有一个共享区,位于堆和栈之间。
========================================
程序启动的过程:
操作系统首先创建相应的进程并分配私有的进程空间,然后操作系统的加载器负责把可执行文件的数据段和代码段映射到进程的虚拟内存空间中。
加载器读入可执行程序的导入符号表,根据这些符号表可以查找出该可执行程序的所有依赖的动态链接库。
加载器针对该程序的每一个动态链接库调用LoadLibrary (1)查找对应的动态库文件,加载器为该动态链接库确定一个合适的基地址。 (2)加载器读取该动态链接库的导入符号表和导出符号表,比较应用程序要求的导入符号是否匹配该库的导出符号。 (3)针对该库的导入符号表,查找对应的依赖的动态链接库,如有跳转,则跳到3 (4)调用该动态链接库的初始化函数
初始化应用程序的全局变量,对于全局对象自动调用构造函数。
进入应用程序入口点函数开始执行。
==================================================
内存顺序(Memory Order)问题
先理清几个前提:
1、CPU不能直接操作内存,CPU是通过 寄存器、缓存 等间接修改内存变量中的值
2、编译器在编译的时候,基于优化可能会对进行指令重排
3、运行期,为了提升执行效率,CPU内部可能也会进行指令重排
内存模型
1.2.7 请简述一下atomoic内存顺序。
参考回答
有六个内存顺序选项可应用于对原子类型的操作:
memory_order_relaxed:在原子类型上的操作以自由序列执行,没有任何同步关系,仅对此操作要求原子性。
memory_order_consume:memory_order_consume只会对其标识的对象保证该对象存储先行于那些需要加载该对象的操作。
memory_order_acquire:使用memory_order_acquire的原子操作,当前线程的读写操作都不能重排到此操作之前。
memory_order_release:使用memory_order_release的原子操作,当前线程的读写操作都不能重排到此操作之后。
memory_order_acq_rel:memory_order_acq_rel在此内存顺序的读-改-写操作既是获得加载又是释放操作。没有操作能够从此操作之后被重排到此操作之前,也没有操作能够从此操作之前被重排到此操作之后。
memory_order_seq_cst:memory_order_seq_cst比std::memory_order_acq_rel更为严格。memory_order_seq_cst不仅是一个"获取释放"内存顺序,它还会对所有拥有此标签的内存操作建立一个单独全序。
除非你为特定的操作指定一个顺序选项,否则内存顺序选项对于所有原子类型默认都是memory_order_seq_cst。
==================================================
1.3.4 说说 C++ 的重载和重写是如何实现的
参考答案
C++利用命名倾轧(name mangling)技术,来改名函数名,区分参数不同的同名函数。命名倾轧是在编译阶段完成的。
==================================================
C++中的构造函数可以分为4类:默认构造函数、初始化构造函数、拷贝构造函数、移动构造函数。
只定义了析构函数,编译器将自动为我们生成拷贝构造函数和默认构造函数。
==================================================