指针定义
指针定义:
计算机把整个内存条分成了一个小小的存储单元。
每一个存储单元都有一个唯一的地址,指针变量就是存放这些地址的对象。
地址:内存单元中每一个字节的编号。
指针:内存中的一个地址.以后理解成地址和指针等价。
指针变量:保存指针的变量.int* px;px就是指针变量。
指针定义格式: 类型* 指针变量名 = &变量。
指针打印用%p;
指针类型
1.2指针的类型
指针的类型:去掉变量名,剩下的的就是指针类型
int* p ; //int* 整形指针
int* arr[3]; //int*[3] 整形数组指针
int (*p)(int a,int b); // int (*)(int a,int b);返回值为整形的函数指针
int (*arr)[4] ; // int (*)[4]; 指针数组,指向数组的指针
指针所指向的类型:去掉*和变量名
int* p ;// int
int *arr[3];// int
int (*p)(int a,int b);// int p(int a,int b);
int (*arr)[4] ;// int a[4];
指针的运算.
指针之间不要相加. int *p,*pp... p+pp;
指针和指针之间可以相减: 得到内存相差几个字节
指针和常量可以加减:p+3; p-3
指针和常量相加,实际上加的是n个所指向的类型大小的和
p++; p--; p+=1
指针之间的相减:指针和指针之间可以相减,得到是内存相差几个字节
指针和常量相减:指针和常量可以相减
int main()
{
int danny = 10;
int* jx = &danny;
double* jx; //2^32
*jx = 123; //danny == *jx 解引用
int *xxx, *zzzz; //定义两个int型指针
printf("danny = %d,*jx = %d\n", danny, *jx);
int rb = 11;
jx = &rb; //指针指向发生改变
*jx = 16;
printf("jx = %d - &rb =%p", jx, &rb);
//指针操作
double db = 123.0;
double * p = &db;
printf("double = %p, p = %p\n", &db, p);
printf("p = %p, p+1 = %p\n", p, p+1);
return 0;
}
图解指针操作
内存四区
第一个区域:代码段–>放代码
第二个区域:数据段–>如全局变量,静态的…,常量 除了局部变量 本段最重要
第三个区域:栈区 -->放局部变量(栈区内存空间比较小),内存自动释放
第四个区域:堆区 -->动态申请内存;从大地址到小地址分配,其他都是从小到大(为什么从大到小分配)
堆区的内存都是手动释放
堆和栈的区别可以用如下的比喻来看出:
使用栈就象我们去饭馆里吃饭,
只管点菜(发出申请)、付钱、和吃(使用),
吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,
他的好处是快捷,但是自由度小。
使用堆就象是自己动手做喜欢吃的菜肴,
比较麻烦,但是比较符合自己的口味,而且自由度大。
malloc 申请内存
格式 p=(类型) malloc(申请内存大小)
realloc 释放内存
格式 p=(类型)realloc(p,改变内存大小)
常见几种指针
- const指针
const int* p; cosnt修饰的是指针所指向的内存空间,指针指向可以发生改变
int const *p; 和第一种一样.第一种方式多.
int * const p; 指针指向不可以发生改变,但指针所指向的内存空间可以修改.
const int * const p; //指针和指针所指向的内存空间都不可以改变
- 二级指针.指针变量中存放的是一级指针变量的地址
格式: 类型** 指针名 = 初始化地址
修改一级指针指向
int** pp = & p; *pp = &y;
修改一级指针所指向的内存空间值
int** pp = & p; **pp = 123;
- 动态分配
动态的在内存中申请一块区域.
函数: malloc <stdlib.h>
void * malloc(需要申请的内存空间大小);
格式: 类型* 指针= (类型*)malloc(内存大小);
申请的内存空间没有初始化.
重新申请内存
void * realloc( void * _Memory, size_t _NewSize);
在原有指针的基础上继续申请内存空间.
-
野指针:没有给指针赋值,指针不知道指向哪里,有可能指向任意一个内存空间,通过指针操作内存空间就会出错,所以要避免野指针出现.
空指针:指向NULL的指针,指向一个特殊的位置,同样的不可以通过指针操作内存空间.
void*指针:通用型指针,可以指向任何类型.在使用之前进行强制类型转换.
内存泄露:申请内存空间找不到了?内存释放: freee(指针);
#include <stdio.h>
#include <stdlib.h>
int main()
{
int x = 0;
int y = 10;
const int* p_x = &x;
p_x = &y;
p_x = 0;
int const * p_xx = &x; //const指针,const靠近谁就修饰的谁,修饰的是指针所指向的内存空间,指针指向可以发生改变,但是内存空间内的值不可以通过指针来改变
p_xx = &y;
int * const p = &x;//const指针,指针指向不可以发生改变,但指针所指向的内存空间可以修改.
//p = &y; //这是错误操作
*p = 110;
int x = 10;
int y = 123;
int * p = &x; //0x345
int *p_x = &y; //0x123;
int** pp = &p;//pp = &p 二级指针,指向指针的指针
printf("p = %p,PP = %p\n", p, pp);
//113
*pp = p_x;// *pp = p ; p = p_x = &y; p指向y
// pp
**pp = 110;
*p = 666;
pp = &p_x;*/
//int* p; P野指针
int *p = NULL; //空指针
printf(" p = %p\n", p);
p = (int*)10;
printf("p = %p\n", p);
*p = 123;
int* const p_mlc = (int*)malloc(sizeof(int));
//p_mlc存在于栈区,malloc申请的内存空间在堆区
//*p_mlc = 123;
//p_mlc = p; 内存泄露
int* p_rlc =realloc(p_mlc, 20);
if (NULL == p_rlc)
{
printf("申请内存失败\n");
}
free(p_mlc);
return 0;
}
图解
地址的强制转换
先看下面这个例子
struct Test
{
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
假设p 的值为0x100000。如下表表达式的值分别为多少?
p + 0x1 = 0x___ ?
(unsigned long)p + 0x1 = 0x___?
(unsigned int*)p + 0x1 = 0x___?
我相信会有很多人一开始没看明白这个问题是什么意思。其实我们再仔细看看,这个知识点似曾相识。一个指针变量与一个整数相加减,到底该怎么解析呢?
还记得前面我们的表达式“a+1”与“&a+1”之间的区别吗?其实这里也一样。指针变量与一个整数相加减并不是用指针变量里的地址直接加减这个整数。这个整数的单位不是byte 而是元素的个数。所以:p + 0x1 的值为0x100000+sizof(Test)*0x1。至于此结构体的大小为20byte,前面的章节已经详细讲解过。所以p +0x1 的值为:0x100014。
(unsigned long)p + 0x1 的值呢?这里涉及到强制转换,将指针变量p 保存的值强制转换成无符号的长整型数。任何数值一旦被强制转换,其类型就改变了。所以这个表达式其实就是一个无符号的长整型数加上另一个整数。所以其值为:0x100001。
(unsigned int*)p + 0x1 的值呢?这里的p 被强制转换成一个指向无符号整型的指针。所以其值为:0x100000+sizof(unsigned int)*0x1,等于0x100004。
上面这个问题似乎还没啥技术含量,下面就来个有技术含量的:在x86 系统下,其值为多少?
intmain()
{
int a[4]={1,2,3,4};
int *ptr1=(int *)(&a+1);//指向a数组后面的内存单元,&a+1表示向后移16个存储单元
int *ptr2=(int *)((int)a+1);//表示a的存储单元的地址增加一个字节
printf("%x,%x",ptr1[-1],*ptr2);//ptr1[-1]其实指向的是a数组的最后一个单元,*ptr1则表示a数组的地址后移一个字节之后的4个连续存储单元所存储的值
return 0;
}
这是我讲课时一个学生问我的题,他在网上看到的,据说难倒了n 个人。我看题之后告诉他,这些人肯定不懂汇编,一个懂汇编的人,这种题实在是小case。下面就来分析分析这个问题:
根据上面的讲解,&a+1 与a+1 的区别已经清楚。
ptr1:将&a+1 的值强制转换成int类型,赋值给int 类型的变量ptr,ptr1 肯定指到数组a 的下一个int 类型数据了。ptr1[-1]被解析成*(ptr1-1),即ptr1 往后退4 个byte。所以其值为0x4。
ptr2:按照上面的讲解,(int)a+1 的值是元素a[0]的第二个字节的地址。然后把这个地址强制转换成int类型的值赋给ptr2,也就是说ptr2 的值应该为元素a[0]的第二个字节开始的连续4 个byte 的内容。
其内存布局如下图:
好,问题就来了,这连续4 个byte 里到底存了什么东西呢?也就是说元素a[0],a[1]里面的值到底怎么存储的。这就涉及到系统的大小端模式了,如果懂汇编的话,这根本就不是问题。既然不知道当前系统是什么模式,那就得想办法测试。大小端模式与测试的方法在第一章讲解union 关键字时已经详细讨论过了,请翻到彼处参看,这里就不再详述。我们可以用下面这个函数来测试当前系统的模式。
int checkSystem()
{
union check
{
int i;
char ch;
} c;
c.i = 1;
return (c.ch ==1);//如果当前系统为大端模式这个函数返回0;如果为小端模式,函数返回1。
}
如果当前系统为大端模式这个函数返回0;如果为小端模式,函数返回1。也就是说如果此函数的返回值为1 的话,*ptr2 的值为0x2000000。如果此函数的返回值为0 的话,*ptr2 的值为0x100。