神一般的指针
2016-03-09 22:30
246 查看
本篇博客并不对指针的基本概念进行讲述,而是针对指针常出现的一些理解误区进行解析。
void (*)()是一个函数类型的指针,假设别名为Func,所以上式变为 (* (Func)0 )(),首先对0进行强制类型转换,转换为函数地址类型,再通过解引用,获得函数实体,最后是函数调用。所以整个功能是 调用了 函数地址是0x00的函数。
结果如下:
为什么输出的c是5,而*p是6呢,从地址上看,&c和p是一样的。
解析:
首先需要明确的是,c的值其实已经变为6了。但是为什么
原因在于 缓存。 因为第一次定义c的时候,首先是在内存中找到一块4bytes的区域,并赋值为5;但是cpu为了优化执行效率,并不会每次读取c的值时,都从内存中读取,而是为了访问方便和加快访问速度,将c的值缓存到L1 cache或者L2 cache,这样做的话,当以后再需要访问c的时候,就可以从L1 cache/ L2 cache中读取(访问L1的速度是访问内存的200倍左右,参考:/article/5249801.html),这样做的话是能够提升效率的。
所以,当通过强制类型转换,将c所对应的对象由原来不可以被修改 变为 可以被修改。并且通过
那么如何让
经过测试,
为了进一步验证上述的讨论,代码修改如下:
按照上述讨论,sizeof(ac) == 16;(c占用8bytes,a占用4bytes,还有4个字节是因为内存对齐原则,补上的)。
以下是测试结果:
(PS : 可以通过在项目属性中添加 命令行编译选项/d1 reportSingleClassLayoutAc ,使得在编译期间显示类的内存布局图)。
小结 :
对于如下代码:
这段代码,在32bit系统中,内存中将会消耗12个bytes的内存,其中8bytes用于存储a的值(10),4个bytes用于存储b的值(a的地址),64bit系统中地址用8个字节表示;尽管有如上特性,但是sizeof(b)的时候,编译器将翻译为求a对象所对应内存的大小。所以,一般情况下,引用只需要理解为是一个对象名称的别名(就当它是个名字而已),但是,切记引用也会消耗内存的,它所消耗的内存是用来存储地址。
而真正意义上,引用就是指针。
从上面代码,我们可以清楚的发现,所谓引用,就是存储一个”被引用对象”的地址值。
我们再结合引用的性质,来看看:
引用只能在定义时被初始化一次,之后不可变。
这怎么做呢?
引用不能为空。
const 指针必须初始化。
没有引用的引用。
一、基本指针含义
1.1 int *p;
一个指向整型数据的指针。1.2 int **p;
一个指针的指针,它指向的指针指向一个整型数据。**p 是 int 类型,所以 *p 是int *类型,即*p是一个指向int的指针,所以p是一个指向int的指针的指针。1.3 int *p[10];
一个有10个指针的数组,该指针指向整型数据。p[10] 的类型是 int *,所以 用type 替换 int *,也就是 type p[10],即p中每个元素都是type类型。1.4 int (*p)[10];
一个指向有十个整型数据数组的指针。()是为了当一个整体看待,所以用val替换(*p),上式变为int val[10],所以*p 相当于数组名(即数组的首地址),所以p是一个指向数组(该数组包含10个Int数字)的指针。1.5 int * p(int);
就一个函数(不是函数指针),该函数有一个整型参数,返回值为一个指向整型的指针1.6 int (*p[10])(int);
一个有十个指针的数组,该数组中的指针指向一个函数,该函数有一个整型参数并返回一个整型数。根据6,p[10]是一个函数指针,所以p中每个元素都是一个函数指针。1.7 int (*p)(int);
定义了一个参数为int,返回值为int的函数指针,函数指针名是p。如果初始化了 p , 则可以通过 p( int ) 调用函数。1.8 typedef int (*p)(int);
定义函数类型,p为类型名,该函数是接受一个int参数,返回一个int。注意与7.的区别。1.9 解析 ( *(void (*)())0)() 的含义:
有些微处理器从0地址启动,有时为了模拟开机时的情形,需要设计一条C 语句,去执行0地址的内容,于是就有了(*(void (*)())0)() 这条语句。void (*)()是一个函数类型的指针,假设别名为Func,所以上式变为 (* (Func)0 )(),首先对0进行强制类型转换,转换为函数地址类型,再通过解引用,获得函数实体,最后是函数调用。所以整个功能是 调用了 函数地址是0x00的函数。
二、指针对于内存的意义
根据如下代码,引出问题:const int c = 5; int * p = (int *)(&c); *p = 6; cout << c << endl; cout << &c << endl; cout << *p << endl; cout << p << endl;
结果如下:
为什么输出的c是5,而*p是6呢,从地址上看,&c和p是一样的。
解析:
首先需要明确的是,c的值其实已经变为6了。但是为什么
cout << c << endl;的时候,输出的还是5呢?
原因在于 缓存。 因为第一次定义c的时候,首先是在内存中找到一块4bytes的区域,并赋值为5;但是cpu为了优化执行效率,并不会每次读取c的值时,都从内存中读取,而是为了访问方便和加快访问速度,将c的值缓存到L1 cache或者L2 cache,这样做的话,当以后再需要访问c的时候,就可以从L1 cache/ L2 cache中读取(访问L1的速度是访问内存的200倍左右,参考:/article/5249801.html),这样做的话是能够提升效率的。
所以,当通过强制类型转换,将c所对应的对象由原来不可以被修改 变为 可以被修改。并且通过
*p = 6,修改c对象在内存中的值,使得在内存中 c==6。 但是正如上面讨论的那样,因为c之前已经缓存到了cache中,所以当
cout << c的时候,输出的是c在cache中缓存的值,但是
cout << *p的时候,是根据p所记录的地址值去内存中找,所以输出的是6。
那么如何让
cout << c输出的也是6呢?更改c的定义即可,如:
volatile const int c = 5,volatile规定每次读取c的值时,都去内存中找。
三、引用是指针吗?
代码如下:struct Ac { Ac():a(c) { c = 10; } int c; int& a; }; sizeof(ac); //等于8
经过测试,
sizeof(ac) == 8;按照C++语法,引用是对一块内存的别名,即引用不占用内存。但是这里不占内存的意思是,不占对象空间,也就是不会申请另外一块内存用于存储10,然后让a跟这块内存关联。这里8字节是因为有4字节的int与4字节的地址空间。因为标准并未明确表示怎么实现引用,一般编译器的做法就是储存地址值,同指针的“实现”一样。而指针一般是由4个字节来存储(这与编译器和操作系统相关,测试用的是VS2013;linux环境是用8bytes存储地址)。
为了进一步验证上述的讨论,代码修改如下:
struct Ac { Ac():a(c) { c = 10; } double c; double& a; }; sizeof(ac);
按照上述讨论,sizeof(ac) == 16;(c占用8bytes,a占用4bytes,还有4个字节是因为内存对齐原则,补上的)。
以下是测试结果:
(PS : 可以通过在项目属性中添加 命令行编译选项/d1 reportSingleClassLayoutAc ,使得在编译期间显示类的内存布局图)。
小结 :
对于如下代码:
double a = 10; double & b = a;
这段代码,在32bit系统中,内存中将会消耗12个bytes的内存,其中8bytes用于存储a的值(10),4个bytes用于存储b的值(a的地址),64bit系统中地址用8个字节表示;尽管有如上特性,但是sizeof(b)的时候,编译器将翻译为求a对象所对应内存的大小。所以,一般情况下,引用只需要理解为是一个对象名称的别名(就当它是个名字而已),但是,切记引用也会消耗内存的,它所消耗的内存是用来存储地址。
而真正意义上,引用就是指针。
3.1 再说引用
以下代码,来自 VS 2013,以下讨论也是基于此编译器。int d = 10; 00417213 mov dword ptr [d],0Ah int & c = d; 0041721A lea eax,[d] //取d的地址,赋给寄存器eax 0041721D mov dword ptr [c],eax //
从上面代码,我们可以清楚的发现,所谓引用,就是存储一个”被引用对象”的地址值。
我们再结合引用的性质,来看看:
引用只能在定义时被初始化一次,之后不可变。
这怎么做呢?
int * const c = &d;, 所以可以猜测,引用就是一个常指针。指针值不可变,但是指向的变量可变。
引用不能为空。
const 指针必须初始化。
没有引用的引用。
相关文章推荐
- regExp 格式化
- 网站安全配置(Nginx)防止网站被攻击(包括使用了CDN加速之后的配置方法)
- 第一周4.1
- 校验码
- 将博客搬至CSDN
- IOS传值方法- 属性反向传值(delegate)
- Java Web应用下载文件以及下…
- java并发代码同步
- Ubuntu下安装JDK
- 自考C++程序设计试题2008年10月
- 励志篇
- 欢迎您在新浪博客安家
- JSP和Servlet的区别
- 两点间画线
- 抽象方法的成员方法,成员变量abstract的使用,以及其概述
- 移动端微信h5下ul实现横向滚动css代码
- 安卓带EditText的区域整体压缩
- 无路可走时,你才能更快学会飞
- php架构学习-php环境搭建选择
- 小试循环