赞
踩
本文内容主要基于公众号拓跋阿秀
整理
main函数执行之前,主要就是初始化系统相关资源:
__attribute__((constructor))
main函数执行之后:
__attribute__((destructor))
为什么要内存对齐?
经过内存对齐之后,CPU的内存访问速度大大提升;
内存空间按照byte划分,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变量的时候经常在特定的内存地址访问,这就需要各类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
数据项仅仅能存储在地址是数据项大小的整数倍的内存位置上
对齐规则?
(1)指针:指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元;而引用跟原来的变量实质上是同一个东西,只不过是原变量的一个别名而已。如:
int a=1;int *p=&a;
int a=1;int &b=a;
上面定义了一个整形变量和一个指针变量p,该指针变量指向a的存储单元,即p的值是a存储单元的地址。
而下面2句定义了一个整形变量a和这个整形a的引用b,事实上a和b是同一个东西,在内存占有同一个存储单元。
(2)引用不可以为空,当被创建的时候,必须初始化,而指针可以是空值,可以在任何时候被初始化。
(3)可以有const指针,但是没有const引用;
常量指针:指向常量的指针,在指针定义语句的类型前加const,表示指向的对象是常量。
定义指向常量的指针只限制指针的间接访问操作,而不能规定指针指向的值本身的操作规定性。
常量指针定义const int* pointer=&a
告诉编译器,*pointer
是常量,不能将*pointer
作为左值进行操作。
常量引用:指向常量的引用,在引用定义语句的类型前加const(const int& ref = i
),表示指向的对象是常量。也跟指针一样不能利用引用对指向的变量进行重新赋值操作。
指针常量在指针定义语句的指针名前加const,表示指针本身是常量。在定义指针常量时必须初始化!而这是引用天生具来的属性,不用再引用指针定义语句的引用名前加const。
指针常量定义int* const pointer=&b
告诉编译器,pointer
是常量,不能作为左值进行操作,但是允许修改间接访问值,即*pointer
可以修改。
总结:有一个规则可以很好的区分const是修饰指针,还是修饰指针指向的数据——画一条垂直穿过指针声明的星号(*),如果const出现在线的左边,指针指向的数据为常量;如果const出现在右边,指针本身为常量。而引用本身与天俱来就是常量,即不可以改变指向。
(4)指针可以有多级,但是引用只能是一级(int **p;合法 而 int &&a是不合法的)
(5)指针的值可以为空,但是引用的值不能为NULL,并且引用在定义的时候必须初始化;
(6)指针的值在初始化后可以改变,即指向其它的存储单元,而引用在进行初始化后就不会再改变了。
(7)”sizeof引用”得到的是所指向的变量(对象)的大小,而”sizeof指针”得到的是指针本身的大小;
(8)指针和引用的自增(++)运算意义不一样;
对引用变量ref的++操作是直接反应到指向的变量之上,对引用变量ref重新赋值"ref=j",并不会改变ref的指向,它仍然指向的是i,而不是j。理所当然,这时对ref进行++操作不会影响到j。
(9)如果返回动态内存分配的对象或者内存,必须使用指针,引用可能引起内存泄漏;
(1)指针作为参数进行传递: 改变地址不影响实参值, 改变指针指向的地址存储的值,可对实参进行改变。
(2)将引用作为函数的参数进行传递: 实质上传递的是实参本身,即传递进来的不是实参的一个拷贝,因此对形参的修改其实是对实参的修改,所以在用引用进行参数传递时,不仅节约时间,而且可以节约空间。
#include<iostream> #include<stdlib.h> using namespace std; void un_swap_int(int *a,int *b) { int *temp=a;//改变指针指向,但是没改变指针的所指的内容 a=b; b=temp; } void swap_int(int *a,int *b) { int temp=*a;//改变指针指向,但是没改变指针的所指的内容 *a=*b; *b=temp; } void test(int *p) { int a=1; p=&a;//改变指针指向,但是没改变指针的所指的内容 cout<<"p = " << p <<" "<<"*p = " << *p<<endl<<endl; } void test2(int *&p)//引用作为函数参数进行传递时,实质上传递的是实参本身 { int a=1; p=&a; cout<<p<<" "<<*p<<endl<<endl; } int main(void) { int a=1,b=2; int *p=NULL; un_swap_int(&a,&b); cout<<"a = " << a<<" b = "<<b<<endl<<endl;//a = 1 b = 2 test(p);//p = 0x7fffae2df244 *p = 1 if(p==NULL) cout<<"指针p为NULL"<<endl<<endl;//指针p为NULL }
C++中指针和引用的区别
c++中,引用和指针的区别是什么?
形象的比喻
栈就像我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切
菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。
堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。
C++中堆(heap)和栈(stack)的区别
堆 | 栈 | |
---|---|---|
管理方式 | 堆中资源由程序员控制(容易产生memory leak) | 栈资源由编译器自动管理,无需手工控制 |
内存管理机制 | 系统有一个记录空闲内存地址的链表,当系统收到程序申请时,遍历该链表,寻找第一个空间大于申请空间的堆结点,删 除空闲结点链表中的该结点,并将该结点空间分配给程序(大多数系统会在这块内存空间首地址记录本次分配的大小,这样delete才能正确释放本内存空间,另外系统会将多余的部分重新放入空闲链表中) | 只要栈的剩余空间大于所申请空间,系统为程序提供内存,否则报异常提示栈溢出。(这一块理解一下链表和队列的区别,不连续空间和连续空间的区别,应该就比较好理解这两种机制的区别了) |
空间大小 | 堆是不连续的内存区域(因为系统是用链表来存储空闲内存地址,自然不是连续的),堆大小受限于计算机系统中有效的虚拟内存(32bit 系统理论上是4G),所以堆的空间比较灵活,比较大 | 栈是一块连续的内存区域,大小是操作系统预定好的,windows下栈大小是2M(也有是1M,在 编译时确定,VC中可设置) |
碎片问题 | 对于堆,频繁的new/delete会造成大量碎片,使程序效率降低 | 对于栈,它是有点类似于数据结构上的一个先进后出的栈,进出一一对应,不会产生碎片。 |
生长方向 | 堆向上,向高地址方向增长。 | 栈向下,向低地址方向增长。 |
分配方式 | 堆都是动态分配(没有静态分配的堆) | 栈有静态分配和动态分配,静态分配由编译器完成(如局部变量分配),动态分配由alloca函数分配,但栈的动态分配的资源由编译器进行释放,无需程序员实现。 |
分配效率 | 堆由C/C++函数库提供,机制很复杂。所以堆的效率比栈低很多。 | 栈是其系统提供的数据结构,计算机在底层对栈提供支持,分配专门 寄存器存放栈地址,栈操作有专门指令。 |
int *p[10]
int (*p)[10]
int *p(int)
int (*p)(int)
int *p[10]
表示指针数组,强调数组概念,是一个数组变量,数组大小为10,数组内每个元素都是指向int类型的指针变量。int (*p)[10]
表示数组指针,强调是指针,只有一个变量,是指针类型,不过指向的是一个int类型的数组,这个数组大小是10。int *p(int)
是函数声明,函数名是p,参数是int类型的,返回值是int *类型的。int (*p)(int)
是函数指针,强调是指针,该指针指向的函数具有int类型参数,并且返回值是int类型的。首先整理一下虚函数表的特征:
根据以上特征,**虚函数表类似于类中静态成员变量.静态成员变量也是全局共享,**大小确定,因此最有可能存在全局数据区,测试结果显示:
虚函数表vtable在Linux/Unix中存放在可执行文件的只读数据段中(rodata),这与微软的编译器将虚函数表存放在常量段存在一些差别
由于虚表指针vptr跟虚函数密不可分,对于有虚函数或者继承于拥有虚函数的基类,对该类进行实例化时,在构造函数执行时会对虚表指针进行初始化,并且存在对象内存布局的最前面。
虚函数表存放在哪里
一般分为五个区域: 栈区、堆区、函数区(存放函数体等二进制代码)、全局静态区、常量区
C++中虚函数表位于只读数据段(.rodata),也就是C++内存模型中的常量区;而虚函数则位于代码段(.text),也就是C++内存模型中的代码区。
相同点
不同点
int *p = new float[2]; //编译错误
int p = (int)malloc(2 * sizeof(double));//编译无错误
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。