C++学习小结

来源:互联网 发布:优化发展环境的讲话 编辑:程序博客网 时间:2024/06/08 00:19

1、编译器优化相关(delete和printf)

int _tmain(int argc, _TCHAR* argv[])
{
 char* p = new char[100];
 strcpy(p,"shit");
 delete p;
 strcpy(p+4,"bitch");
 printf(p);
return 0;
}

debug模式:
delete时会把p指向的空间全部写入“feee”。这时程序运行的结果不可预知,释放掉的这块空间仍处于空闲状态那就输出"铪铪bitch",如果此块空间已被系统回收,程序的现行地址无法映射到该物理内存,那就会报错,无法访问该地址。
release:
delete时会在p指向的空间开头的四个字节里写入一个地址,此地址指向另一个地址,另一个地址又指向第三个地址,最终有一个地址又指向p空间开头delete写入的那个地址。printf在输出的时候会检测前四个字节,并尝试当作地址去访问,如上面程序那样,最后printf就会阻塞在一个循环里,无法输出任何东西。如果人为通过写入字符来破坏掉delete填入的这个地址的完整性,printf就不会阻塞。但是,和前面叙述一样,输出字符前提是这块空间没有被系统回收,也就是上面这程序的现行地址仍然能够映射到具体的物理地址。

2、IEEE 745浮点数格式

 IEEE 浮点格式参数总结
参数
浮点格式
单精度
双精度
扩展双精度(Intel x86)
扩展双精度(SPARC)
尾数宽度
23
52
63
112
前导有效位
隐含
隐含
显式
隐含
有效数字宽度
24
53
64
113
偏置指数宽度
8
11
15
15
偏置值Bias
+127
+1023
+16383
+16383
符号位宽度
1
1
1
1
存储格式宽度
32
64
80
128

举一个例子
将十进制-0.75表示成单精度IEEE754

-0.75表示成-3/4 即二进制的-0.11
规格化 :将将小数点移到最左边1的后面 移动宽度即为指数宽度  左移为正 右移负
规格化表示为 -1.1 * 2^-1
根据IEEE单精度表示公式为 (-1)^s  *  1.f  *  2^(e+127)
                                           s 符号    f尾数    e指数
指数会加上偏移后储存 偏移见表
所以这个数表示为
-1.1*2^-1
=(-1)^1 * (1+0.10000 0000 0000 0000 0000 000)  *  2^-1
=(-1)^1 * (1+0.10000 0000 0000 0000 0000 000)  *  2^(-1 + 127)

即 1     01111110         10000000000000000000000
   负   移码指数126               1.1的.1尾数部分

3、const.volatile

const
1、编译时编译器直接把const修饰对象的常量值替换到代码当中,对象就以常量的形式和指令一起被送往cpu执行,并没有相应的内存空间,但是,如果程序中包含有其他对此对象地址访问的指令,那系统就会给这个对象分配空间,另,如果此对象是全局的,那该对象就存放于全局内存中的rodata段,并且受到系统保护,不允许被直接赋值以及通过内存地址修改。
2、防止在后续代码编写中对该对象的意外修改,起到一个提示和限制作用。
3、const可以修饰变量,函数参数(参数不应被函数体修改),函数返回值(返回值不应被修改 ),函数体(此方法不应修改类成员,且不可调用其他非const方法)。
4、常量对象只能调用常量方法。
volatile
防止编译器对被修饰对象的优化,保证每一次对该对象的读写都从内存中读写,而不使用寄存器中缓存数据。

4、全局内存

bss:
所有初始化为0或者未初始化的全局变量都存放于此。即使有大量这类数据它占用可执行文件的空间仍然很小。
data:
如果对应内存中全是零,编译器仍然把它当作bss来看待,这样就达到了最大的优化效果。data存放的是初始化为非零值非const类型的全局变量。
rodata:
ro代表readonly,即只读数据(const)。关于rodata类型的数据,要注意以下几点:
常量并不是一定放在rodata里,有的立即数直接编码在指令里(参考const全局变量),存放在代码段(.text)中。
 对于字符常量,编译器会自动去掉重复的字符,保证一个字符串在一个可执行文件(EXE/SO)中只存在一份拷贝。
 rodata是在多个进城是共享的,这可以提高空间利用率。
 在有的嵌入式系统中,rodata放在ROM(如norflash)里,运行时直接读取ROM,无需要加载到RAM内存中。
由此可见,把在运行过程中不会改变的数据设为rodata类型的,是有很多好处的:在多个进程间共享,可以大大提高空间利用率,甚至不占用RAM空间。同时由于rodata在只读的内存页面(page)中,是受保护的,任何试图对它的修改都会被及时发现,这可以帮助提高程序的稳定性。

5、虚表

  1. 类中有虚函数才会构建虚表。
  2. 类实例化时,虚表存于new出对象空间的最前端,四个字节的虚表指针。
  3. 父类的虚表会被子类继承,并非拷贝指针,而是拷贝虚表本身,子类重载虚函数时对虚表内的相应函数指针进行修改,添加虚函数则在虚表里添加新的函数指针。
  4. 子类继承多个父类,每个有虚表的父类都会把本身的虚表派生给子类,子类添加虚函数时则在继承的第一个虚表里添加,重载则修改相应位置的函数指针。

6、C++重载原理

int fun(int a);
int fun(char a);
1、以"?"标识函数名的开始,后跟函数名。
2、函数名后面以"@@"标识参数表的开始,后跟参数表。Z表示结束。
3、参数表以代号表示。
    X——void,
    D——char,
    E——unsigned char,
    F——short,
    H——int,
    I——unsigned int,
    J——long,
    K——unsigned long,
    M——float,
    N——double,
    _N——bool,

7、分支预测

cpu在执行指令的时候,一级缓存已经存好了接下来需要执行的指令。但是当所需要执行的方向出现多个的时候就需要高正确率的预测来保证程序整体的运行效率,分支预测的大概过程就是通过预测得到有关
程序未来动作的相关信息,并以此为凭从多个分支中选择一个放入CPU指令缓存器中。

8、IOCP核心机制

1、投递队列
send/recv/accept/disconnect等等绑定重叠结构并且未完成的I/O操作。
2、完成队列
投递队列里的I/O操作完成后进入完成队列。
3、闲线程栈
线程空闲就进入此栈,在getqueuedcompletionportstatus获取完成队列里的重叠结构和完成键以及跟对应SOCKET相关的I/O操作信息时,从线程栈里选用栈顶的线程来处理获取的数据。
闲线程栈设为栈的原因:
线程在建立的时候,系统会分配给一定的内存,这一部分内存只是用来定位硬盘页面文件以及当作一定大小的数据缓冲区。而其他的内存则是虚拟的,在硬盘上划分出来配以内存中这部分空间来使用,硬盘页面文件
与内存中的一些内存页映射,但大多数硬盘页面文件都是处于空闲状态,也就是未被映射或者说标记。
cpu仍然只能从内存里读写数据,而硬盘里的数据想要得到处理必须先写入内存。为了保证内存的高效利用率,闲置过长时间的线程所对应的栈空间,线程对象所占空间,线程描述等等相关信息所占的所有空间都被
暂时切断和内存的联系,并且把从硬盘里分配到的虚拟内存还回去,释放所有权,恢复成未被内存中描述信息映射的硬盘页面文件,这样就不可以直接被使用。所以,在使用闲置线程的时候就要考虑到读写的耗时,就采用栈的结构,先进后出。

9、多线程同步

临界区:通过标记代码段来实现多个线程之间公共资源的安全使用。速度快,适合控制数据访问。
互斥量:内核对象,任意时刻,只能有一个线程在访问公共资源。但是比临界区更耗费资源。
信号量;对同时访问公共资源的用户数量进行限制。
事件;用来通知一个事件的发生,从而启动后续任务的执行。
互斥量,信号量,事件都是可以跨进程使用的。
阻塞:在执行某一过程时需要另一个过程执行结果,为了本线程能继续执行,所以一直等待另一过程执行完毕得出结果。
非阻塞:主流程执行的某一过程需要另一过程的执行结果,于是主流程就利用等待此结果的时间做一些其他的事情,另一过程执行完毕则给主流程一个中断,主流程开始执行与此结果相关的运算。
DMA:最初对于网卡,显卡等硬件数据写入内存的设计是都要经过CPU的,CPU是数据转移的中介。但这种设计会加大CPU的负担,这种负担是没有任何意义的,因为CPU只是作为一个中介。
所以,就有了DMA的出现,DMA相当于之前设计里的中介,代替里CPU的一部分功能。所以,数据可以直接写入内存。

10、函数指针

int(*(*P[5]))(int,int)(int,int)
函数指针由返回值类型,指针名,参数名组成。上面表示一个数组里存有5个返回值为函数指针的函数指针,而这个返回的函数指针的返回值为int类型,他们参数类型都是(int,int)

11、数组名和数组指针

int a[5] = {1,2,3,4,5};
int j = *(a+1);
int* p = *((&a+1));
int i = *(p-1);
j的值为2,i的值为5.
指针由首地址和偏移范围两个部分组成。在上面实例中,a和&a的首地址是一样的,但是类型不一样,也就是偏移范围不一样。a是int数组,表示数组里存的是以int型数据为单个元素,所以a+1的偏移量是一个
元素的长度。&a则是表示这整个数组所占空间的地址。和int(*p)[5]意思一样,类型为int[]*。在c++的程序中,我们使用地址+偏移来访问内存,但这个地址并非是cpu直接能够辨认的地址,这只是配给每个进程的单独的线性地址,是虚拟的,编译器在编译的时候把它换成机器码,以便cpu能够找到物理上真正的内存地址,所以编译器必须就要知道程序里所使用的地址(指针)在相应的程序环境里的对应空间大小,也就是指针类型。

int *p[5]和int (*p)[5]分别代表数组p里存5个int*类型数据和指针p指向的数组里存有5个int类型数据。


0 0