文章目录
- 前言
- 1.一维数组
- 2.字符数组
- 3.二维数组
- 4.经典指针试题
前言
1、数组名通常表示首元素地址,sizeof(数组名)和&数组名两种情况下,数组名表示整个数组。
2、地址在内存中唯一标识一块空间,大小是4/8字节。32位平台4字节,64位平台8字节。
3、指针变量是用来存放地址的变量。大小是4/8字节。32位平台4字节,64位平台8字节。
4、strlen库函数使用来统计’\0’前的字符个数的。
1.一维数组
首先我想先介绍一下sizeof()关键字。首先sizeof关键字计算的是类型的大小,单位是字节。sizeof()内部的表达是不参与运算。因为sizeof内部表达式在预处理期间便已经计算完毕。
int main()
{
int a = 2;
short b = 3;
printf("%u\n", sizeof(b = a + 3));
//这里a+3表达式是不计算的
//这句代码等价于sizeof(short);
printf("%d\n",b);
return 0;
}
#include<stdio.h>
int main()
{
int a[] = { 1,2,3,4 };
printf("%d\n", sizeof(a));//可参考解析(1)
printf("%d\n", sizeof(a + 0));//可参考解析(2)
printf("%d\n", sizeof(*a));//可参考解析(3)
printf("%d\n", sizeof(a + 1));//可参考解析(4)
printf("%d\n", sizeof(a[1]));//可参考解析(5)
printf("%d\n", sizeof(&a));//可参考解析(6)
printf("%d\n", sizeof(*&a));//可参考解析(7)
printf("%d\n", sizeof(&a + 1));//可参考解析(8)
printf("%d\n", sizeof(&a[0]));//可参考解析(9)
printf("%d\n", sizeof(&a[0] + 1));//可参考解析(10)
return 0;
}
解析(1):这里数组名单独放在sizeof内部,表示整个数组。数组4各元素,每个元素4个字节,所以是16字节
解析(2):a+0,这里数组名表示首元素地址,首元素地址+0,本质是地址。是地址就是4/8字节,地址的大小取决于平台,32位平台下,地址是4字节。64位平台下,地址是8字节。
解析(3):这里a表示首元素地址,对首元素地址进行*解引用操作,拿到的是第一个元素。所以是4个字节。
解析(4):a+1表示首元素地址+1,就是第二个元素的地址,是地址就是4/8字节。
解析(5):a[1]可以把它理解成 (a+1),a[1]本质上是数组第二个元素。整型数组每个元素的类型是整型,所以是4字节。
解析(6):sizeof(&a),这里&a取出的是整个数组地址。类型是整型数组指针int([5]);,指针的大小就是4/8字节。
解析(7): *&a,可以将 *和&进行抵消,也就是sizeof(a),数组名单独放在sizeof()内部,表示整个数组。也可以理解成先取出了整个数组的地址,在解引用访问整个数组,此时该表达式的类型就是int [4] ,大小就是16字节。
解析(8):&a+1,取出了整个数组的地址,+1跳过整个数组,单数本质还是数组的地址,是地址就是4/8字节。
解析(9):&a[0],取出的是第一个元素的地址,是地址就是4/8字节。
解析(10):&a[0]+1,取出的是第二个元素的地址,是地址就是4/8字节。
2.字符数组
#include<stdio.h>
#include<string.h>
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr));//分析请见解析(1)
printf("%d\n", sizeof(arr+0));//分析请见解析(2)
printf("%d\n", sizeof(*arr));//分析请见解析(3)
printf("%d\n", sizeof(arr[1]));//分析请见解析(4)
printf("%d\n", sizeof(&arr));//分析请见解析(5)
printf("%d\n", sizeof(&arr+1));//分析请见解析(6)
printf("%d\n", sizeof(&arr[0]+1));//分析请见解析(7)
printf("%d\n", strlen(arr));//分析请见解析(8)
printf("%d\n", strlen(arr+0));//分析请见解析(9)
printf("%d\n", strlen(*arr));//分析请见解析(10)
printf("%d\n", strlen(arr[1]));//分析请见解析(11)
printf("%d\n", strlen(&arr));//分析请见解析(12)
printf("%d\n", strlen(&arr+1));//分析请见解析(13)
printf("%d\n", strlen(&arr[0]+1));//分析请见解析(14)
解析(1):sizeof(arr),此时数组名单独放在内部,表示的是整个数组的大小。大小是6个字节。arr 表示整个元素的大小是因为,它的本质是*&arr。&arr的类型是char (*)[6],对这个指针变量解引用操作得到的便是整个数组,即char[6]。所以是6个字节。
解析(2):arr+0 表示的是首元素的地址,此时数组名表示首元素地址,首元素地址+0还是首元素地址。是地址就是4/8字节。
解析(3):arr,此时数组名表示首元素地址,操作后访问的是数组第一个元素,即’a’。所以大小是1字节。
解析(4):arr[1],此时数组名表示首元素地址。对首元素地址进行下标访问操作,访问的是数组第二个元素,即’b’。char的大小是1字节。也可以将arr[1] 理解为 (arr+1)。
解析(5):&arr,取出的使整个数组的地址,类型是char ()[6] 的数组指针。是指针就是4/8字节。
解析(6):&arr+1,此时&arr取出了整个数组的地址,+1后跳过整个数组的地址。类型依旧还是char ()[6] 。是指针大小就是4/8字节。
解析(7):&arr[0]+1,首先,arr和[0]结合,表示数组第一个元素,对其进行取地址操作,取出的是第一个元素的地址。+1后便是第二个元素的地址,是地址就是4/8字节。
解析(8):此时的arr是数组首元素的地址。由于字符数组内没有strlen的停止标识’\0’。故长度不可知,是随机值。
解析(9):arr+0表示的是首元素的地址向后偏移0个字节,故还是首元素地址。由于字符数组内没有strlen的停止标识’\0’。长度不可知,是随机值。
解析(10):arr,arr表示首元素地址,对其进行解引用操作,得到’a’,而’a’ 在内存中的值是ascii值97,将97作为参数传给strlen函数会造成访问权限冲突,所以这行代码是错误的。
解析(11):arr[1],表示的是第二个元素’b’,'b’的ascii值为98,将98作为参数传给strlen函数会造成访问权限冲突,所以这行代码是错误的。
解析(12):&arr,&arr的类型是char()[6],而库函数strlen参数部分为(const char str);,故类型不匹配,不建议这样传参。但是,编译器还是能跑过去,依旧存在’\0’位置不可预知,结果是随机值。
解析(13):&arr+1,依旧是一个字符数组指针,同上述所述,结果依旧是随机值。
解析(14):&arr[0]+1,此时的参数是第二个元素的地址。因为\0’标志位置不可知,所以结果是随机值。
char arr[] = "abcdef";
printf("%d\n", sizeof(arr));//分析请见解析(1)
printf("%d\n", sizeof(arr+0));//分析请见解析(2)
printf("%d\n", sizeof(*arr));//分析请见解析(3)
printf("%d\n", sizeof(arr[1]));//分析请见解析(4)
printf("%d\n", sizeof(&arr));//分析请见解析(5)
printf("%d\n", sizeof(&arr+1));//分析请见解析(6)
printf("%d\n", sizeof(&arr[0]+1));//分析请见解析(7)
printf("%d\n", strlen(arr));//分析请见解析(8)
printf("%d\n", strlen(arr+0));//分析请见解析(9)
printf("%d\n", strlen(*arr));//分析请见解析(10)
printf("%d\n", strlen(arr[1]));//分析请见解析(11)
printf("%d\n", strlen(&arr));//分析请见解析(12)
printf("%d\n", strlen(&arr+1));//分析请见解析(13)
printf("%d\n", strlen(&arr[0]+1));//分析请见解析(14)
解析(1):arr,此时的数组名单独放在sizeof内部,表示整个数组。“字符串内容”,需要加上’\0’的大小。所以大小是7字节。
解析(2):arr+0,此时数组名没有单独放在sizeof内部表示首元素的地址,+0依旧是首元素地址。是地址就是4/8字节。
解析(3):*arr,此时数组名表是首元素地址,对首元素进行解引用操作后,访问的是首个元素,即’a’,大小1字节。
解析(4):arr[1],对首元素地址进行下标访问操作,访问的数组第二个元素。因为数组下标从0开始。所以大小是1字节。
解析(5):&arr表示取出整个数组的地址。是地址就是4/8字节。
解析(6):&arr+1,&arr表示取出整个数组的地址,+1跳过整个数组的地址。是地址就是4/8字节。
解析(7):&arr[0]+1,arr首先和[0]结合,表示第一个元素,&第一个元素的地址,+1就是第二个元素的地址。是地址就是4/8字节。
解析(8):arr,没有放在sizeof内部也没有&,此时arr数组名表示首元素的地址。此时,strlen统计的是’\0’字符前的字符个数。长度为6。
解析(9):arr+0,此时arr表示首元素的地址,+0向后偏移0个元素大小,还是首元素地址。strlen统计的长度为6。
解析(10):*arr,解引用操作访问首元素地址,访问的是首元素’a’。字符’a’的ASCII值为97,将97作为地址传给strlen,会造成访问权限冲突,所以这段代码是错误的。
解析(11):arr[1],arr表示首元素地址,通过[]下标访问操作符,访问数组第二个元素,即’b’,‘b’作为参数传递给strlen,传过去的是’b’的ASCII值98,此操作会造成访问权限的冲突,故此代码错误。
解析(12):&arr,取出的是arr字符串数组的地址,类型是char()[7],而strlen所需的参数是char *的指针。类型不匹配,而在一些类型检查比较严谨编译器环境系,这段代码是无法运行的。但是在作者使用的VS2019环境下,编译器会将类型强制转化成char *,从而得到的结果是6.
解析(13):&arr+1,&arr取出了整个字符串数组的地址,+1跳过了整个数组,指向的是数组后内存空间的地址。而该内存空间的内容是不可预知的。所以strlen统计的长是随机值。
解析(14):&arr[0]+1,arr首先和[]结合,表示的是数组第一个元素。取出第一个元素的地址,+1后表示的是第二个元素的地址,即’b’的地址。此时统计字符串长度,从’b’开始统计,长度为5。
int main()
{
char* p = "abcdef";
printf("%d\n", sizeof(p));//分析请见解析(1)
printf("%d\n", sizeof(p+0));//分析请见解析(2)
printf("%d\n", sizeof(*p));//分析请见解析(3)
printf("%d\n", sizeof(p[1]));//分析请见解析(4)
printf("%d\n", sizeof(&p));//分析请见解析(5)
printf("%d\n", sizeof(&p+1));//分析请见解析(6)
printf("%d\n", sizeof(&p[0]+1));//分析请见解析(7)
printf("%d\n", strlen(p));//分析请见解析(8)
printf("%d\n", strlen(p+0));//分析请见解析(9)
printf("%d\n", strlen(*p));//分析请见解析(10)
printf("%d\n", strlen(p[1]));//分析请见解析(11)
printf("%d\n", strlen(&p));//分析请见解析(12)
printf("%d\n", strlen(&p+1));//分析请见解析(13)
printf("%d\n", strlen(&p[0]+1));//分析请见解析(14)
return 0;
}
解析(1):p是一个char* 指针变量,大小是4/8字节。
解析(2):p是一个char* 指针变量,+0向后偏移一个char* 指针,本质还是指针,是指针就是4/8字节大小。
解析(3):p是存放常量字符串首字符地址的指针,对其进行解引用访问操作后,访问的是常量字符串的首字符’b’,它的大小是1字节。
解析(4):p[1],其实就是对p进行下标访问操作,访问的是p+1地址处的内容,即*(p+1),计算的是’b’的大小,是1字节。
解析(5):&p,取出的是一级指针变量p的地址,类型是二级字符指针,是指针就是4/8字节大小。
解析(6):&p+1,取出p的地址,+1后指向p地址后的一个指针大小的地址,是地址就是4/8字节。
解析(7):&p[0]+1,首先p和[]结合,表示首字符,对其进行取地址操作后,得到的是首字符地址,+1得到第二个字符地址,是地址就是4/8字节大小。
解析(8):p指向的是常量字符串首字符的地址,所以strlen统计的是常量字符串’\0’前的字符个数,长度为6。
解析(9):p+0,表示首元素的地址向后偏移0个字符指针,还是首元素地址,长度是6。
解析(10):*p,p表示首元素地址,*p表示首元素,将首元素’a’作为strlen参数,会造成访问权限冲突,故这是一行错误的代码。
解析(11):p[1],对首元素地址进行下标访问操作,访问的是第二个字符。也就是’b’,,将’b作为strlen参数,会造成访问权限冲突,故这是一行错误的代码。
解析(12):&p,取出的字符指针变量p的地址,故’\0’的位置不可预知。所以长度是随机值。
解析(13):&p+1,&p取出的字符指针变量p的地址,+1后跳过一个指针,指向&P后面的一块内容。而该地址及其后面的内容不可预知,所以长度依旧是随机值。
解析(14):&p[0]+1,,p首先和[]结合,表示的是数组第一个元素。取出第一个元素的地址,+1后表示的是第二个元素的地址,即’b’的地址。此时统计字符串长度,从’b’开始统计,长度为5。
3.二维数组
int mian()
{
int a[3][4] = { 0 };
printf("%d\n", sizeof(a));//分析请见解析(1)
printf("%d\n", sizeof(a[0][0]));//分析请见解析(2)
printf("%d\n", sizeof(a[0]));//分析请见解析(3)
printf("%d\n", sizeof(a[0] + 1));//分析请见解析(4)
printf("%d\n", sizeof(*(a[0] + 1)));//分析请见解析(5)
printf("%d\n", sizeof(a + 1));//分析请见解析(6)
printf("%d\n", sizeof(*(a + 1)));//分析请见解析(7)
printf("%d\n", sizeof(&a[0] + 1));//分析请见解析(8)
printf("%d\n", sizeof(*(&a[0] + 1)));//分析请见解析(9)
printf("%d\n", sizeof(*a));//分析请见解析(10)
printf("%d\n", sizeof(a[3]));//分析请见解析(11)
return 0;
}
解析(1):二维数组数组名单独放在sizeof内部,表示整个数组,大小是48字节。
解析(2):a[0][0] 表示第一行第一个元素,大小是4字节。
解析(3):a[0],数组名表示第一行的地址,类型是int()[4],对其进行下标访问操作,访问的是第一行数组,类型是int[4],所以计算的大小是16字节。
解析(4):a[0] + 1,a[0]是第一行数组名,但是数组名没有单独放在sizeof内部,是第一行的地址,+1跳过一行,表示是第二行的地址,是地址就是4/8字节。
解析(5):(a[0] + 1),首先根据上述分析,a[0] + 1是第二行的地址。对其进行解引用操作后,得到的是第二行的数组名,所以大小是16字节。
解析(6):a + 1,此时数组名表示第一行的地址,+1后表示第二行的地址。是地址就是4/8字节。
解析(7):(a + 1),a+1表示第二行的地址,对其解引用后,表示整个第二行,大小是16字节。
解析(8):&a[0] + 1,a表示第一行的地址,和[]结合后表示整个第一行,对其&拿到的是第一行的地址,+1 表示第二行的地址。是地址就是4/8字节。
解析(9):(&a[0] + 1),上述分析&a[0] + 1为第二行的地址,对其进行解引用操作后表示的是整个第二行,大小是16字节。
解析(10):*a,此时a表示第一行的地址,对其进行解引用操作,拿到的是整个第一行,大小是16字节。
解析(11):a[3],上述分析a[3]表示整个第4行,但是a[3][4]没有第4行,但是这不影响sizeof计算大小,因为sizeof计算的是类型的大小,所以是16字节。
4.经典指针试题
试题一
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int *ptr = (int *)(&a + 1);
printf( "%d,%d", *(a + 1), *(ptr - 1));
return 0;
}
//程序的结果是什么?
解析:首先,定义一维整型数组a[5],接着,又定义了整型指针ptr 存放&a+1的地址。&a 表示的是整个数组的地址,类型是int(*)[5] ,+1后跳过整个数组,指向的位置是5后面一个指针的地址。将其进行强制类型转化后赋给ptr。ptr-1指向的是a+4的地址,解引用后访问的是a[4]的内容也就是5,a没有单独放在sizeof内部,表示的是首元素地址,+1后解引用操作访问的是第二个元素也就是2。故程序运行的结果是2,5。
试题二
//由于还没学习结构体,这里告知结构体的大小是20个字节
struct Test
{
int Num;
char* pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1);
return 0;
}
解析:
printf(“%p\n”, p + 0x1);,首先已知结构体大小是占20字节空间,那么结构体指针+1,跳过一个结构体,也就是跳过20字节。20的转化十六进制数就是14,当前作者使用的是32位平台,一个地址是4字节。故0x00100000+0x14,结果是0x00100014。
printf(“%p\n”, (unsigned long)p + 0x1);,这里将p强制类型转化成无符号长整型,整型+1 就是+1,所以结果是0x00100001。
printf(“%p\n”, (unsigned int*)p + 0x1);,这里将p强制类型转化成无符号整型指针,无符号整型指针+1 跳过4个字节,故结果是0x00100004
试题三
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;
}
解析:首先定义一维整型数组a[4],元素有{1,2,3,4}。&a取出的是整个数组的地址,+1跳过整个数组,将结果强制类型转化成整型指针变量并赋给整型指针ptr1。此时ptr1指向的空间是a+4,er ptr[-1] == a+4-1 ==a+3,对其解引用操作,访问的是a[3]也就是4。a表示首元素地址,将其强制转化成整型,整型+1 就是+1。将结果强制转化成整型指针赋给整型指针ptr2,由于当前环境下是小端存储,所以跳过的一个字节内容是01,将ptr解引用操作后,其指向的内容就是是0x20000000。
试题四
#include <stdio.h>
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
int* p;
p = a[0];
printf("%d", p[0]);
return 0;
}
解析:首先二位数组初始化内容十三个逗号表达式,故int a[3][2] = {1,3,5}; 而p指针存放的是整个第一行的地址。即a[0],这里求得是p[0]的值,其实求得是a[0][0]的值,也就是首行首元素1。
试题五
int main()
{
int a[5][5];
int(*p)[4];
p = a;
printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
return 0;
}
解析:如上图所示,两指针相减的值是-4,而-4通过%p格式打印,打印的是-4的补码,以十六进制格式打印也就是FFFFFFFC,%d格式打印依旧还是-4。
试题六
int main()
{
int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int* ptr1 = (int*)(&aa + 1);
int* ptr2 = (int*)(*(aa + 1));
printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));
return 0;
}
解析:&aa+1,&aa,数组名和&结合表示整个数组。取出的是整个数组的地址,+1跳过整个数组。指向的是aa数组后的40字节的空间。也就是10后面一个整型指针大小的空间。*(ptr1-1)得到的是10。 (aa+1), 此时数组名表示第一行的地址,aa+1表示第二行的地址,解引用后得到的是第二行首元素的地址,也就是6的地址。(ptr2-1)得到就是5。
试题7
#include <stdio.h>
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
解析:这里定义了字符指针数组a[],这里"work" “at” "alibaba"分别是三个常量字符串。定义二级指针变量pa并赋给它a数组名,这里pa指向a首字符的地址。pa指针++跳过一个元素,即指向第二个字符数组首字符’a’地址。
试题八
int main()
{
char *c[] = {"ENTER","NEW","POINT","FIRST"};
char**cp[] = {c+3,c+2,c+1,c};
char***cpp = cp;
printf("%s\n", **++cpp);
printf("%s\n", *--*++cpp+3);
printf("%s\n", *cpp[-2]+3);
printf("%s\n", cpp[-1][-1]+1);
return 0;
}