C语言深度解剖笔记5之指针与数组

指针部分要点:

NULL 就是NULL,它被宏定义为0:#define NULL 0。

如何将数值存储到指定的内存地址

假设现在需要往内存0x12ff7c 地址上存入一个整型数0x100。

内存地址就是一个指针,因为指针变量就是存了一个内存的地址。

所以:我们可以用如下的方法:

int *p = (int *)0x12ff7c;
*p = 0x100;

也可以写为*(int *)0x12ff7c = 0x100;这行代码其实和上面的两行代码没有本质的区别。先将地址0x12ff7c 强制转换,告诉编译
器这个地址上将存储一个int 类型的数据;然后通过钥匙“*”向这块内存写入一个数据

数组部分要点:

int a[5];sizeof(a[5])这个并不报错,sizeof 是关键字不是函数。函数求值是在运行的时候,关键字sizeof 求值是在编译的时候。虽然并不存在a[5]这个元素,但是这里也并没有去真正访问a[5],而是仅仅根据数组元素的类型来确定其值。所以这里使用a[5]并不会出错。

&a[0]和&a 到底有什么区别?

a[0]是一个元素,a 是整个数组,虽然&a[0]和&a的值一样,但其意义不一样。前者是数组首元素的首地址,而后者是数组的首地址。记为取地址时,没0的是数组的地址。而数组名是数组首元素的地址。举个例子:湖南的省政府在长沙,而长沙的市政府也在长沙。两个政府都在长沙,但其代表的意义完全不同。

数组名a可以当右值,不能当作左值。a 作为右值时其意义与&a[0]是一样,代表的是数组首元素的首地址,而不是数组
的首地址,编译器会认为数组名作为左值代表的意思是a 的首元素的首地址,但是这个地址开始的一块内存是一个总体,我们只能访问数组的某个元素而无法把数组当一个总体进行访问。

指针与数组之间有关系吗?

指针就是指针,指针变量在32 位系统下,永远占4 个byte,其值为某一个内存的地址。
指针可以指向任何地方,但是不是任何地方你都能通过这个指针变量访问到。
数组就是数组,其大小与元素的类型和个数有关。定义数组时必须指定其元素的类型
和个数。数组可以存任何类型的数据,但不能存函数。

指针和数组易混淆的点

1.关于字符串的存储

A),char *p = “abcdef”;
B),char a[] = “123456”;//这个也是占7个字节,包含一个\0.char a[]={'1','2','3','4','5','6'};这样的存储方式是不包含\0.

A)定义了一个指针变量p,p 本身在栈上占4 个byte,p里面存储的是abcdef的首地址。abcdef是字符串,放在静态存储区的(全局变量也在)

其空间大小为7 个byte,包含一个\0.byte,这块内存也没有名字。对这块内存的访问完全是匿名的访问n比如现在需要读取字符‘e’,我们有两种方式:

以指针的形式访问和以下标的形式访问指针

1),以指针的形式:*(p+4)。先取出p 里存储的地址值然后加上4 个字符的偏移量,得到新的地址,然后解引用,得到这个地址存储的值为e.

2),以下标的形式:p[4],编译器总是把以下标的形式的操作解析为以指针的形式的操作。p[4]这个操作会被解析成:先取出p 里的地址值,然后加上中括号中4 个元素的偏移量,计算出新的地址,然后从新的地址中取出值。也就是说以下标的形式访问在本质上与以指针的形式访问没有区别,只是写法上不同罢了

以指针的形式访问和以下标的形式访问数组

例子B)定义了一个数组a,a 拥有7 个char 类型的元素,其空间大小为7。数组a 本身在栈上面。对a 的元素的访问必须先根据数组的名字a 找到数组首元素的首地址,然后根据偏移量找到相应的值.比如现在需要读取字符‘5’,我们有两种方式

1),以指针的形式:*(a+4)。a 这时候代表的是数组首元素的首地址,然后加上4 个字符的偏移量,得到新的地址,然后解引用,取值。

2),以下标的形式:a[4]。编译器总是把以下标的形式的操作解析为以指针的形式的操作。a[4]这个操作会被解析成:a 作为数组首元素的首地址,然后加上中括号中4 个元素的偏移量,计算出新的地址,然后从新的地址中取出值。

我们可以看到,指针和数组根本就是两个完全不一样的东西。只是它们都可以“以指针形式”或“以下标形式”进行访问。一个是完全的匿名访问,一个是典型的具名+匿名访问,上面所说的偏移量4 代表的是4 个元素,而不是4 个byte。只不过这里刚好是char 类型数据1 个字符的大小就为1 个byte

a 和&a 的区别

&a + 1: 取数组a 的首地址,该地址的值加上sizeof(a) 的值,即&a + 5*sizeof(int),也就是下一个数组的首地址,显然当前指针已经越过了数组的界限。

*(a+1): a,&a 的值是一样的,但意思不一样,a 是数组首元素的首地址,也就是a[0]的首地址,&a 是数组的首地址,a+1 是数组下一元素的首地址,即a[1]的首地址,&a+1 是下一个数组的首地址。所以输出2

*(ptr-1): 因为ptr 是指向a[5](下一个数组的首地址),并且ptr 是int * 类型,所以*(ptr-1) 是指向a[4] ,输出5

指针和数组的定义与声明

1.定义为数组,声明为指针)(不可取)

文件1 中定义如下:
char a[100];

文件2 中声明如下:
extern char *a;
定义分配内存,而声明没有,extern 告诉编译器a 这个名字已经在别的文件中被定义了,

这个问题不会产生编译错误和链接错误,但是运行时会发生错误。

文件2里面编译器认为a是指针,所以它认定这个指针在某个地方占了一个char的大小的内存,

文件1里面a是数组,运行时文件2中的a指针指向的那个地址应该是一个sizeof(char)大小的内存来存放它的内容,所以它把件1中的的第一个char长作为指针的空间,所以文件1中数组中的第一个数组元素的值a[0]就做了指针的值。

2定义为指针,声明为数组

文件1
char *p = “abcdefg”;
文件2
extern char p[];

文件1 中,编译器分配4 个byte 空间,并命名为p。同时p 里保存了字符串常量“abcdefg”的首字符的首地址。这个字符串常量本身保存在内存的静态区,其内容不可更改。在文件2中,编译器认为p 是一个数组,其大小为4 个byte,数组内保存的是char 类型的数据。p保存的是字符串常量abcdefg的首地址,我们就假设这个地址是0x0000FF00(这里先不考虑大小端)文件2中编译器把 指针变量p当做一个包含4个char类型数据的数组 ,按char类型取出p[0],p[1]p[2]p[3]的值,分别是0x00,0x00 0xFF,0x00,也就是说文件2中的数组存储的是文件1中p指针所保存的字符串的首地址。而我们想要的是让这个数组保存abcdefg这个字符串也就是说要的是字符串保存的内存区间。如果我们给p[i]赋值,则原来p[i]中保存的地址将会被覆盖,导致无法指向原来的内存。

指针这个和数组的对比

指针:保存数据的地址,任何存入指针变量p 的数据都会被当作地址来处理。p 本身的地址由编译器另外存储,存储在哪里,我们并不知

数组:保存数据,数组名a 代表的是数组首元素的首地址而不是数组的首地址。&a 才是整个数组的首地址。a 本身的地址由编译器另外存储·,

指针数组和数组指针

指针数组:首先它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身
决定。它是“储存指针的数组”的简称。
数组指针:首先它是一个指针,它指向一个数组。在32 位系统下永远是占4 个字节,
至于它指向的数组占多少字节,不知道。它是“指向数组的指针”的简称。

A),int *p1[10];
B),int (*p2)[10];

“[]”的优先级比“*”要高。p1 先与“[]”结合,构成一个数组的定义,数组名为p1,int *修饰的是数组的内容,即数组的每个元素,这是一个数组,其包含10 个指向int 类型数据的指针,即指针数组

()”的优先级比“[]”高,“*”号和p2 构成一个指针的定义,指针变量名为p2,int 修饰的是数组的内容,即数组的每个元素。数组在这里并没有名字,是个匿名数组,p2 是一个指针,它指向一个包含10 个int 类型数据的数组,即数组指针

地址的强制转换

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___?

首先p是一个结构体类型的指针,给指针加整数就是加整数倍的类型的大小。p + 0x1 的值为0x100000+sizof(Test)*0x1

unsigned long)p + 0x1,涉及到强制转换,将指针变量p 保存的值强制转换成无符号的长整型数

(unsigned int*)p + 0x1 的值呢?这里的p 被强制转换成一个指向无符号整型的指针。所以其值为:0x100000+sizof(unsigned int)*0x1,等于0x100004。

(unsigned int*)p + 0x1 的值呢?这里的p 被强制转换成一个指向无符号整型的指针。所以其值为:0x100000+sizof(unsigned int)*0x1,等于0x100004,加指针就是加类型。

再来看一个题

int main()
{
int a[4]={1,2,3,4};
int *ptr1=(int *)(&a+1);
int *ptr2=(int *)((int)a+1);
printf("%x,%x",ptr1[-1],*ptr2);
return 0;
}

int *ptr1=(int *)(&a+1);是将数组取地址,接着加1.也就是下一个数组的地址。(数组加1也是加类型,数组也是一种数据类型)

接着将&a+1 的值强制转换成int*类型,赋值给int* 类型的变量ptr1,ptr1[-1]被解析成*(ptr1-1)(访问指针与数组的两种方式),即ptr1 往后退4 个byte。所以其值为0x4。%x输出是地址,而如果以%d输出的话,得到的是4,

int *ptr2=(int *)((int)a+1);a是数组首元素的首地址,将其转换为int,也就是将这个地址

多级数组和多级指针。

二级数组:它的内存布局是实际上一维数组里又存储了数组,

先来看个题:

int a[5][5];
int (*p)[4];
p = a;
问&p[4][2] - &a[4][2]的值为多少?

&a[4][2]表示的
是&a[0][0]+4*5*sizeof(int) + 2*sizeof(int)(先是首地址,接着经过了4个数组的元素,而这4个元素每一个里面也是存了5个元素,接着是第五个元素里的前两个。)

&p[4][2] :,p 是指向一个包含4 个元素的数组的指针,则p+1 表示的是指针p 向后移动了一个“包含4 个int 类型元素的数组”,,p[4]相对于p[0]来说是向后移动了4 个“包含4 个int 类型元素的数组”,即&p[4]表示的是&p[0]+4*4*sizeof(int)。。由于p 被初始化为&a[0],那么&p[4][2]表示的是&a[0][0]+4*4*sizeof(int)+2* sizeof(int),,解决这类问题的最好办法就是画内存布局图

二级指针

char **p;。p 是一个指针变量,毫无疑问在32 位系统下占4 个byte。
它与一级指针不同的是,一级指针保存的是数据的地址,二级指针保存的是一级指针的地
址。

任何指针变量都可以被初始化为NULL,null),二级指针也不例外,二级指针需要两次钥匙(“*”)才能最终取出了真正的数据

数组参数和 指针参数,

一维数组当函数参数时,会发生降维(降维成一级指针),编译器 总是会把他解析成一个指向其受元素首地址的指针。

一维指针当函数参数的时候也会发生实参做一份拷贝并传递给被调用的函数。看下面的例子:

void GetMemory(char * p, int num)
{
p = (char *)malloc(num*sizeof(char));
}

int main()
{
char *str = NULL;
GetMemory(str,10);
strcpy(str,”hello”);
free(str);//free 并没有起作用,内存泄漏
return 0;
}

对实参做一份拷贝并传递给被调用的函数。即对str 做一份拷贝,假设其拷贝名为_str.在运行strcpy(str,”hello”)语句的时候发生错误。这时候观察str 的值,发现仍然为NULL。也就是说str 本身并没有改变,我们malloc 的内存的地址并没有赋给str,而是赋给了_str。而这个_str 是编译器自动分配和回收的,我们根本就无法使用。所以想这样获取一块内存是不行的

有两个办法解决:

第一:用return。给定义的函数return一个指针变量,返回的指针变量就用我们在main函数中定义的指针变量str接收。这样malloc的地址就被str接收了

char * GetMemory(char * p, int num)
{
p = (char *)malloc(num*sizeof(char));
return p;
}
intmain()
{

char *str = NULL;
str = GetMemory(str,10);
strcpy(str,”hello”);
free(str);
return 0;
}

第二:用二级指针。

void GetMemory(char ** p, int num)
{
*p = (char *)malloc(num*sizeof(char));
return p;
}
intmain()
{
char *str = NULL;
GetMemory(&str,10);
strcpy(str,”hello”);
free(str)

return 0;
}

这里的实参是&str 而非str。这样的话传递过去的是str 的地址,是一个值。在函
数内部,用钥匙(“*”)来开锁:*(&str),其值就是str。所以malloc 分配的内存地址是真正
赋值给了str 本身。

函数指针

函数指针就是函数的指针。它是一个指针,指向一个函数

如:char * (*fun1)(char * p1,char * p2);这就是一个函数指针,前面我们介绍过数组指针,如int(*p)[5],所以我们可以看出这fun1是一个指针变量,这个变量指向一个匿名函数,这个函数的参数是(char * p1,char * p2)

函数指针的使用

#include <stdio.h>
#include <string.h>
char * fun(char * p1,char * p2)
{
int i = 0;
i = strcmp(p1,p2);
if (0 == i)
{
return p1;
}
else
{
return p2;
}
}

int main()
{
char * (*pf)(char * p1,char * p2);
pf = &fun;
(*pf) ("aa","bb");
return 0;
}

先定义一个函数,接着在主函数main里面定义一个函数指针char * (*pf)(char * p1,char * p2);,这个函数指针pf指向这个fun函数参数都是一样的;返回值都是char*.接着通过用(*pf)取出存在这个地址上的函数,然后将要写入的字符串实参写入到fun函数参数里。给函数指针赋值时,可以用&fun 或直接用函数名fun。这是因为函数名被编译之后其实就是一个地址。

*(int*)&p是什么

看例子:

void Function()
{
printf("Call Function!\n");
}
intmain()
{
void (*p)();
*(int*)&p=(int)Function;
(*p) ();
return 0;
}

void (*p)();这个的意思是函数指针p指向一个函数,这个函数的返回值和参数都是void,&p 是求指针变量p 的地址,(int*)&p 表示将地址强制转换成指向int 类型数据的指针,*(int*)&p将这个指针(p本身的地址)解引用,得到的就是p(int)Function 表示将函数的入口地址强制转换成int 类型的数据(函数名就是一个地址)。*(int*)&p=(int)Function;表示将函数的入口地址赋值给指针变量p,(*p) ();就是表示对函数的调用(这里(*p)就是将p指向的地址解引用,取出存在这个地址上的函数,然后调用它,)。

(*(void(*) ())0)()

第一步:void(*) (),可以明白这是一个函数指针类型。这个函数没有参数,没有返回值。

第二步:(void(*) ())0,这是将0 强制转换为函数指针类型,0 是一个地址(这是因为0说到底是指针。这个指针是指向一个函数的),也就是说一个函数存在首地址为0 的一段区域内

第三步:(*(void(*) ())0),将这个职责解引用。这是取0 地址开始的一段内存里面的内容,其内容就是保存
在首地址为0 的一段区域内的函数。

第四步:(*(void(*) ())0)(),这是函数调用。

函数指针数组

“char * (*pf)(char * p)”定义的是一个函数指针pf。既然pf 是一个指针,那就可以储存在一个数组里。把上式修改一下:

char * (*pf[3])(char * p);这是一个函数指针数组,它是一个数组(前面介绍过指针数组,指针数组就是储存指针的数组,如int *p1[10];[ ]的结合性比*高),数组名为pf,数组内存储了3 个指向函数的指针,函数的参数是char*p

这些指针指向一些返回值类型为指向字符的指针、参数为一个指向字符的指针的函数

函数指针数组的指针

char * (*(*pf)[3])(char * p);这里的pf 确实是实实在在的指针。这个指针指向一个包含了3 个元素的数组,这个数组里存的是指向函数的指针,;这些指针指向一些返回值类型为指向字符的指针、参数为一个指向字符的指针的函数。

猜你喜欢

转载自blog.csdn.net/woainilixuhao/article/details/86562621