指针的一些有趣现象

发布于 2019-09-07  99 次阅读


指针

常量折叠

首先看下面一段代码

#include <bits/stdc++.h>
#define pf(a) printf("%d\n",a) ;
using namespace std ;

signed main()
{

	const int num_1 = 10 ;
	int *ptr_1 = const_cast<int*>(&num_1) ;
	*ptr_1 = 100 ;
	pf( num_1 ) ;
	pf( &num_1 ) ;
	pf( ptr_1 ) ;
	pf( *ptr_1 ) ;
	pf( const_cast<int&>(num_1) ) ;
	
}

预测一下结果?

结果是

10

7667268

7667268

100

首先发现的问题是

  1. 直接输出num_1,好像因为const而没有被改值;
  2. num_1的地址和ptr_1指向的地址是一样的;
  3. 去除num_1的const限定符之后输出num_1,发现num_1的值发生了变化;

特别说明一下

C++标准转换运算符const_cast

C++提供了四个转换运算符:

  • const_cast <datatype> (expression)
  • static_cast <datatype> (expression)
  • reinterpret_cast <datatype> (expression)
  • dynamic_cast <datatype> (expression)

在这里不多讨论,详情可以点击《IBM的C++指南

  • const_cast<int*>
  • const_cast<int&>

在这之后可以去除num_1的限定修饰

然后通过指针修改了num_1的值

实现原因就在于C++对于指针的转换是任意的

它不会检查类型,即是任何指针之间都可以进行互相转换

而且

ptr_1指向的地址与num_1是一样的,这个事实也辅佐验证了上述行为的合理性。

但是为什么直接输出num_1,还是显示原本的值。

其中的关键是“常量折叠”编译优化技术

不同于宏,此举动也会分配内存空间。

"常量折叠"是 就是在编译器进行语法分析的时候,将常量表达式计算求值,并用求得的值来替换表达式,放入常量表。

所知道的是:

被优化的常量 ,因为不会被修改,所以在运行的时候会从寄存器中直接读取数据。

而强制修改之后,内存中的值发生了改变,但是寄存器中的数据并“跟不上”。

为了佐助这种说法,我们可以给num加上volatile修饰

这样num就是“易变的”,强制每次都从内存中读取数据

volatile const int num=0 ;
int *ptr = (int *) &num ;
*ptr = 1 ;
pf( num ) ;

输出结果是

1

迷途指针

signed main()
{
	int *ptr = (int *)malloc( sizeof(int)*10 ) ;
	pf( ptr ) ;
	for( int i=0 ; i<10 ; ++i )
	{
		ptr[i] = i ;
		pf( ptr[i] ) ;
	}
	free( ptr ) ;
	pf( ptr ) ;
	pf( ptr[9] ) ;
	ptr = NULL ;
	pf( ptr ) ;
}

代码输出是:

857296
0
1
2
3
4
5
6
7
8
9
857296
9
0

 

也就是 free之后,读取ptr指向的内存,仍然存在!

大家都知道:

  • 任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的
  • new 对应的必须 delete

现在看来,free掉的只是内存的地址,ptr所指向的地址却没有变

再尝试以下:

       int *ptr = new int ;
	*ptr = 9 ;
	pf( ptr ) ;
	pf( *ptr ) ;
	free( ptr ) ;
	int *ptt = new int ;
	pf( ptt ) ;
	*ptt = 1000 ;
	*ptr = 10 ;
	pf( ptr ) ;
	pf( ptt ) ;
	pf( *ptr ) ;
	pf( *ptt ) ;

输出:

2036944
9
2036944
2036944
2036944
10
10

非常有趣的是,后一个指针指向了前一个指针同样的地址,而后续的改变大概也可以理解。

所以说,不管是free还是delete,之后都要将指针置NULL

另一个是不要返回指向栈内存的指针,因为指向一个已经被释放了的内存是相当危险的。

“真空”的内存?

这个其实跟指针的关系并不大,主要是内存分配的问题

首先是:

void fuc()
{
	int *ptr ;
	pf( ptr[-1] );
	ptr[-1] = 100 ;
}

signed main()
{
	int a , b , c=5 ;
	fuc() ;
	pf( c ) ;
}

输出

5

10

这应该非常好理解,只要稍微了解

代码区、常量区、静态区(全局区)、堆区、栈区

就应该可以知道其中原理。

但是问题在于,这种“寻址”十分不稳定,只需简单的语句就可以破坏

void fuc()
{
	int *ptr ;
	for( int i=0 ; i<10 ; ++i ) ;
	pf( ptr[-1] );
	ptr[-1] = 100 ;
}

改为这样之后,程序就会崩溃。

还有这种情况

signed main()
{
	int a=1 , b=2 , c=5 , *ptr=&c;
	//fuc() ;
	pf( ptr[-1] ) ;
}

是相当难正确寻址的!

再不说在不同机器上的情况了。

但是可以看看以下的:

signed main()
{
	int *a=(int *)malloc( sizeof(int)*8);
	a[7] = 11 ;
	a[6] = 12 ;
	a[5] = 13 ;
	a[4] = 14 ;
	a[3] = 15 ;
	int *b=(int *)malloc( sizeof(int)*5);
	for( int i=1 ; i<=5 ; ++i )
		pf( b[ -sizeof(int) -i ] ) ;
}

输出结果

11
12
13
14
15

就算是

signed main()
{
	int *a=(int *)malloc( sizeof(int)*8);
	a[7] = 11 ;
	a[6] = 12 ;
	a[5] = 13 ;
	a[4] = 14 ;
	a[3] = 15 ;
	for( int i=1 ; i<=100 ; ++i ) ;
	for( int i=1 ; i<=100 ; ++i ) ;
	puts("");
	int qwq=10;
	int *b=(int *)malloc( sizeof(int)*5);
	for( int i=1 ; i<=5 ; ++i )
		pf( b[ -sizeof(int)-i ] ) ;
}

不会改变结果。

要想破坏这种状态,进入同样的空间即可。

所以其实这种类似真空的状态,要说难以理解其实也不然。

指针与内存的妙处,还有很多,难以详尽

 

喜欢这篇文章吗,不妨分享给朋友们吧!

科学是第一生产力