zl程序教程

您现在的位置是:首页 >  后端

当前栏目

指针和数组笔试题解析

数组 解析 指针 笔试
2023-06-13 09:17:43 时间

文章目录

一、一维数组

下面代码的输出结果是什么:(32位平台下)

int main()
{
    int a[] = {1,2,3,4};
    printf("%d\n",sizeof(a));
    printf("%d\n",sizeof(a+0));
    printf("%d\n",sizeof(*a));
    printf("%d\n",sizeof(a+1));
    printf("%d\n",sizeof(a[1]));
    printf("%d\n",sizeof(&a));
    printf("%d\n",sizeof(*&a));
    printf("%d\n",sizeof(&a+1));
    printf("%d\n",sizeof(&a[0]));
    printf("%d\n",sizeof(&a[0]+1));
    return 0;
}

解析:

int a[] = {1,2,3,4};    
printf("%d\n",sizeof(a));   //16
//a作为数组名单独放在sizeof内部,表示整个数组的大小:4*4=16
printf("%d\n",sizeof(a+0));   //4/8
//a没有单独放在sizeof内部,也没有&,所以代表首元素即a[0]的地址,地址的大小是4/8个字节
printf("%d\n",sizeof(*a));   //4
//a没有单独放在sizeof内部,也没有&,所以代表首元素即a[0]的地址,首元素的地址解引用得到首元素,int 4个字节
printf("%d\n",sizeof(a+1));   //4/8
//a没有单独放在sizeof内部,也没有&,所以代表首元素即a[0]的地址,a+1代表第二个元素的地址,4/8个字节
printf("%d\n",sizeof(a[1]));	//4
//a[1]代表数组第二个元素,整形占4个字节
printf("%d\n",sizeof(&a));	//4/8
//&a取出的是整个数组的地址,地址占4/8个字节
printf("%d\n",sizeof(*&a));	//16
//&a取出整个数组的地址,然后再对其进行解引用,得到整个数组
printf("%d\n",sizeof(&a+1));	//4/8
//&a取出整个数组的地址,&a+1跳过一个数组(跳过一个数组指针)的大小,指向数组末尾,地址大小是4/8个字节
printf("%d\n",sizeof(&a[0]));	//4/8
//&a[0]取出的是第一个元素的地址,地址大小为4/8个字节
printf("%d\n",sizeof(&a[0]+1));	//4/8
//&a[0]取出的是第一个元素的地址,&a[0]+1表示第二个元素的地址,地址大小为4/8个字节

总结:

1、数组名的意义

  • 数组名单独放在sizeof内部时表示整个数组;
  • &数组名取出的是整个数组的地址;
  • 其余情况数组名均表示首元素的地址;

2、指针类型的意义

  • 指针的类型决定了指针加减整数时跳过字节/元素的个数;
  • 指针的类型决定了指针解引用时的权限,即访问字节的个数(步长);

二、字符数组

1、题型一

下面代码的输出结果是什么:(32位平台下)

int main()
{
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", sizeof(arr));
	printf("%d\n", sizeof(arr + 0));
	printf("%d\n", sizeof(*arr));
	printf("%d\n", sizeof(arr[1]));
	printf("%d\n", sizeof(&arr));
	printf("%d\n", sizeof(&arr + 1));
	printf("%d\n", sizeof(&arr[0] + 1));
    
	printf("%d\n", strlen(arr));
	printf("%d\n", strlen(arr + 0));
	printf("%d\n", strlen(*arr));
	printf("%d\n", strlen(arr[1]));
	printf("%d\n", strlen(&arr));
	printf("%d\n", strlen(&arr + 1));
	printf("%d\n", strlen(&arr[0] + 1));
	return 0;
}

解析:

char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", sizeof(arr));	//6
//arr单独放在sizeof内部,代表整个数组,大小为6个字节
printf("%d\n", sizeof(arr + 0));	//4/8
//arr代表首元素的地址,4/8个字节
printf("%d\n", sizeof(*arr));	//1
//arr代表首元素地址,对其解引用代表第一个元素,大小为1个字节
printf("%d\n", sizeof(arr[1]));    //1
//arr[1]代表数组第二个元素,1个字节
printf("%d\n", sizeof(&arr));	//4/8
//&arr取出整个数组地址,4/8个字节
printf("%d\n", sizeof(&arr + 1));	//4/8
//&arr取出整个数组的地址,&arr+1跳过一个数组,指向数组后面紧挨的空间,4/8个字节
printf("%d\n", sizeof(&arr[0] + 1));	///4/8
//&arr[0]代表首元素地址,+1代表第二个元素地址,4/8个字节

printf("%d\n", strlen(arr));	//随机值
//arr代表首元素地址,数组从第一个元素开始求字符串长度
//strlen求的是\0以前字符的个数,因为字符数组中并不包含\0,所以strlen会一直往后寻找,直到遇到\0,而我们不知道数组后面空间的内容,所以为随机值。
printf("%d\n", strlen(arr + 0));	//随机值 
//arr+0代表首元素地址,数组从第一个元素开始求字符串长度
//随机值原因同上
printf("%d\n", strlen(*arr));	//error
//strlen函数的参数是一个地址,这里我们把第一个元素即'a'传给strlen函数,那么strlen就会把'a'的ASCII码值(97)当成地址来用,但是97处的地址并不属于我们,从而造成野指针问题,报错
printf("%d\n", strlen(arr[1]));	//error
//原因和上面类似
printf("%d\n", strlen(&arr));	//随机值
//&arr虽然取出整个数组的地址,但仍然是指向首元素,所以和上面的strlen(arr)一样
printf("%d\n", strlen(&arr + 1));	//随机值
//&arr取出整个数组的地址,+1跳过整个数组,指向数组后面紧挨的空间,但我们不知道后面空间何时遇到\0,所以仍然是随机值
printf("%d\n", strlen(&arr[0] + 1));	//随机值
//&arr[0]+1代表第二个元素地址,仍然是随机值

2、题型二

下面代码的输出结果是什么:(32位平台下)

int main()
{
	char arr[] = "abcdef";
	printf("%d\n", sizeof(arr));
	printf("%d\n", sizeof(arr + 0));
	printf("%d\n", sizeof(*arr));
	printf("%d\n", sizeof(arr[1]));
	printf("%d\n", sizeof(&arr));
	printf("%d\n", sizeof(&arr + 1));
	printf("%d\n", sizeof(&arr[0] + 1));

	printf("%d\n", strlen(arr));
	printf("%d\n", strlen(arr + 0));
	printf("%d\n", strlen(*arr));
	printf("%d\n", strlen(arr[1]));
	printf("%d\n", strlen(&arr));
	printf("%d\n", strlen(&arr + 1));
	printf("%d\n", strlen(&arr[0] + 1));
	return 0;
}

解析:

char arr[] = "abcdef";
printf("%d\n", sizeof(arr));	//7
//arr代表整个数组,因为字符串末尾会自动补一个\0,所以arr数组有七个元素
printf("%d\n", sizeof(arr + 0));	//4/8
printf("%d\n", sizeof(*arr));	//1
printf("%d\n", sizeof(arr[1]));	//1
printf("%d\n", sizeof(&arr));	//4/8
printf("%d\n", sizeof(&arr + 1));	//4/8
printf("%d\n", sizeof(&arr[0] + 1));	//4/8

printf("%d\n", strlen(arr));	//6
//strlen遇到字符串末尾的\0停止,前面一共6个元素
printf("%d\n", strlen(arr + 0));	//6
printf("%d\n", strlen(*arr));	//error
printf("%d\n", strlen(arr[1]));    //error
printf("%d\n", strlen(&arr));	//6
printf("%d\n", strlen(&arr + 1));	//随机值
//&arr取出整个数组的地址,+1跳过整个数组,后面空间的内容未知,所以随机
printf("%d\n", strlen(&arr[0] + 1));	//5
//&arr[0]+1是第二个元素的地址,把它传给strlen,所以为5

3、题型三

下面代码的输出结果是什么:(32位平台下)

int main()
{
	char* p = "abcdef";
	printf("%d\n", sizeof(p));
	printf("%d\n", sizeof(p + 1));
	printf("%d\n", sizeof(*p));
	printf("%d\n", sizeof(p[0]));
	printf("%d\n", sizeof(&p));
	printf("%d\n", sizeof(&p + 1));
	printf("%d\n", sizeof(&p[0] + 1));

	printf("%d\n", strlen(p));
	printf("%d\n", strlen(p + 1));
	printf("%d\n", strlen(*p));
	printf("%d\n", strlen(p[0]));
	printf("%d\n", strlen(&p));
	printf("%d\n", strlen(&p + 1));
	printf("%d\n", strlen(&p[0] + 1));
	return 0;
}

解析:

char* p = "abcdef";
printf("%d\n", sizeof(p));	//4/8
//"abcdef"是常量字符串,char* p = "abcdef" 的作用是把字符串中'a'的地址赋给P,所以4/8
printf("%d\n", sizeof(p + 1));	//4/8
//p+1代表字符'b'的地址,4/8
printf("%d\n", sizeof(*p));    //1
printf("%d\n", sizeof(p[0]));	//1
printf("%d\n", sizeof(&p));    //4/8
//&p取出的是p的地址,是一个二级指针,但地址大小仍然是4/8
printf("%d\n", sizeof(&p + 1));    //4/8
//&p+1代表p所在空间后面紧挨的空间
printf("%d\n", sizeof(&p[0] + 1));    //4/8
//&p[0] <==> &(*(p+0)) <==> p,代表'a'的地址

printf("%d\n", strlen(p));    //6
printf("%d\n", strlen(p + 1));    //5
printf("%d\n", strlen(*p));    //error
printf("%d\n", strlen(p[0]));    //error
printf("%d\n", strlen(&p));    //随机值
//&p取出的是p的地址,这里有两个东西是不确定的:
//1、不知道p的地址中是否含有四个字节大小的数字0,p的地址可能0x14ffff44,也可能是0x1400ff00
//2、不知道p的后面空间的内容
//所以strlen求出来的值是随机的
printf("%d\n", strlen(&p + 1));    //随机值
//&p+1跳过了p的地址,直接来到了p其后的空间,消除了上面的第一点不确定,但第二段仍然存在
printf("%d\n", strlen(&p[0] + 1));    //5

总结:

1、数组名的意义

  • 数组名单独放在sizeof内部时表示整个数组;
  • &数组名取出的是整个数组的地址;
  • 其余情况数组名均表示首元素的地址;

2、指针类型的意义

  • 指针的类型决定了指针加减整数时跳过字节/元素的个数;
  • 指针的类型决定了指针解引用时的权限,即访问字节的个数(步长);

3、strlen的用法

  • strlen会一直向后寻找,直到遇到 ‘\0’ 才停止计算(越界计数得到随机值);
  • strlen会把传递过来的数据当成地址来使用(错误传递参数导致野指针问题);

三、二维数组

下面代码的输出结果是什么:(32位平台下)

int main()
{
	int a[3][4] = { 0 };
	printf("%d\n", sizeof(a));
	printf("%d\n", sizeof(a[0][0]));
	printf("%d\n", sizeof(a[0]));
	printf("%d\n", sizeof(a[0] + 1));
	printf("%d\n", sizeof(*(a[0] + 1)));
	printf("%d\n", sizeof(a + 1));
	printf("%d\n", sizeof(*(a + 1)));
	printf("%d\n", sizeof(&a[0] + 1));
	printf("%d\n", sizeof(*(&a[0] + 1)));
	printf("%d\n", sizeof(*a));
	printf("%d\n", sizeof(a[3]));
	return 0;
}

解析:

int a[3][4] = { 0 };
	printf("%d\n", sizeof(a));	//48
	//a单独放在sizeof内部,表示整个二维数组的大小,3*4*4=48
	printf("%d\n", sizeof(a[0][0]));	//4
	//a[0][0]代表第一个元素,整形占4个字节
	printf("%d\n", sizeof(a[0]));	//16
	//二维数组的行名代表其对应一维数组的数组名
	//这里的a[0]代表二维数组第一行的数组名
	//一维数组的数组名单独放在sizeof内部,代表整个一维数组,4*4=16
	printf("%d\n", sizeof(a[0] + 1));	//4/8
	//a[0]代表第一行的数组名
	//a[0]不是单独放在sizeof内部,所以数组名代表首元素地址,即a[0][0]的地址
	//所以a[0]+1代表数组第二个元素a[0][1]的地址,4/8
	printf("%d\n", sizeof(*(a[0] + 1)));	//4
	//对数组第二个元素解的地址引用得到第二个元素,整形大小4个字节
	printf("%d\n", sizeof(a + 1));    //4/8
	//a代表首元素即第一行的地址,a+1跳过一行,指向第二行,第二行的地址4/8
	printf("%d\n", sizeof(*(a + 1)));	//16
	//第一行的地址+1指向第二行的地址,解引用得到第二行
	printf("%d\n", sizeof(&a[0] + 1));	//4/8
	//&a[0]取出第一行的地址,+1指向第二行的地址
	printf("%d\n", sizeof(*(&a[0] + 1)));	//16
	//对第二行的地址解引用得到整个第二行
	printf("%d\n", sizeof(*a));	//16
	//a代表首元素即第一行的地址,解引用得到第一行
	printf("%d\n", sizeof(a[3]));	//16
	//sizeof求数据大小时只关注数据的类型,不关注数据的内容
	//这里虽然这里a[3]越界了,但是根据a[3]的数据类型:int (*)[4] -- 数组指针,仍能求出a[3]的大小
	//a[3]代表一维数组的数组名单独放在sizeof内部代表整个数组的大小

总结:

1、数组名的意义

  • 数组名单独放在sizeof内部时表示整个数组;
  • &数组名取出的是整个数组的地址;
  • 其余情况数组名均表示首元素的地址;

2、指针类型的意义

  • 指针的类型决定了指针加减整数时跳过字节/元素的个数;
  • 指针的类型决定了指针解引用时的权限,即访问字节的个数(步长);

3、二维数组相关

  • 二维数组的行号可以看作对应一维数组的数组名,如arr[3][4]中arr[0]代表第一行的一维数组的数组名;
  • 对于sizeof(arr[0]),由于arr[0]代表一维数组的数组名,且arr[0]单独放在sizeof内部,所以求的是这个一维数组的大小;

四、指针笔试题

1、笔试题一

下面程序的输出结果是什么:(32位平台下)

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取出整个数组的地址,+1跳过整个数组,然后强制转化为int*类型后赋给int*的变量ptr,此时ptr指向的位置如图所示; *(a+1):a代表首元素地址,+1指向第二个元素,解引用得到2; *(ptr-1):ptr指针的类型是int*,由于指针类型决定指针解引用访问的字节个数,所以ptr-1指向第五个元素,解引用得到5;

2、笔试题二

下面程序的输出结果是什么:(32位平台下)

//已知,结构体Test类型的变量大小是20个字节
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
struct Test
{
 int Num;
 char *pcName;
 short sDate;
 char cha[2];
 short sBa[4];
}*p = (struct Test*)0x100000;

int main()
{
 printf("%p\n", p + 0x1);
 printf("%p\n", (unsigned long)p + 0x1);
 printf("%p\n", (unsigned int*)p + 0x1);
 return 0;
}

解析:

p+0x1 <==> p+1,因为此结构体的大小为20个字节,由指针类型的意义可知,这里+1跳过20个字节(一个Test结构体的大小),十进制的20等价于十六进制的14,所以 0x100000 + 0x14 = 0x100014; p原本代表结构体Test的地址,但是这里将其强制类型转换为size_t,那么它就变为了普通的整数,整数+1就是+1,所以 0x100000 + 0x000001 = 0x100001; p原本代表结构体Test的地址,但是这里将其强制类型转换为unsigned int*,由于指针的类型决定了指针加减整数跳过的字节个数,所以这里+1跳过4个字节(一个整形的大小),所以 0x100000 + 0x100004 = 0x100004;

3、笔试题三

在小端机器下,下面程序的输出结果是什么:(32位平台下)

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*)(&a + 1):和第一题类似,&a的地址,然后+1跳过整个数组,此时a指向数组后面紧挨的空间(如图所示),然后强制转化为int*类型后赋给int*的变量ptr1; (int*)((int)a + 1):a代表首元素的地址,将a强转为int类型,+1之后强转赋给int*类型的的ptr2,因为这里的a被强转为整数,所以+1跳过一个字节,此时ptr2的指向如图所示; ptr1[-1] <==> *(ptr1-1):因为ptr1是是整形指针,所以ptr1-1的指向如图,解引用访问一个整形大小,得到a[3] = 4; *ptr2:如图,ptr2此时指向a[0]的第二个字节,因为ptr2是整形指针,所以解引用访问图中紫色区域,得到 0x02000000(数据以小端存储的模式存入,也会已小端存储的模式拿出);

对于大小端字节序的理解\概念有问题的同学,可以看看我之前的文章:深度剖析数据在内存中的存储

4、笔试题四

下面程序的输出结果是什么:(32位平台下)

int main()
{
    int a[3][2] = { (0, 1), (2, 3), (4, 5) };
    int *p;
    p = a[0];
    printf( "%d", p[0]);
 return 0;
}

解析:

这里有一个陷阱:二维数组初始化的内容是三个逗号表达式,所以其实a的初始化内容是 { 1,3,5 },其余自动初始化为0,所以p[0]为1;

5、笔试题五

下面程序的输出结果是什么:(32位平台下)

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;
}

解析:

p = a:a是二维数组的数组名,代表首元素的地址,而二维数组的首元素是第一行,所以a的类型是 int (*)[5],这里我们把a赋给p,p的类型是 int (*)[4],二者类型不同; 虽然p和a的类型不一样,但是由于二维数组和一维数组一样,在空间中都是连续存储的,所以这里并不会报错,只是会报一个警告; 所以&p[4][2]取出的是二维数组第18个元素的地址,&a[4][2]取出的是二维数组第22个元素的地址,地址相减得到的是相差元素的个数,即-4; 在内存中存储的是-4的补码即11111111 11111111 11111111 11111100,同时这里以%p(地址)的形式打印,由于地址是无符号数,所以直接取出-4的补码,即十六进制的FFFF FFFFC;

6、笔试题六

下面程序的输出结果是什么:(32位平台下)

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;
}

解析:

(int*) (&aa + 1):&aa取出整个二维数组的地址,+1跳过整个二维数组,然后强转为int*类型赋给ptr1; (int *) (*(aa + 1)):aa代表首元素地址,二维数组首元素是第一行,+1跳过一行得到整个第二行的地址,然后解引用得到第二行,也相当于得到第二行的数组名,而数组名又代表首元素地址,所以*最终得到的是aa[1][0]的地址,之后再强转赋给ptr2; *(ptr1 - 1), *(ptr2 - 1):ptr1 和 ptr2 都是整形指针,-1减去四个字节,ptr1指向aa[1][4],解引用得到10,ptr2指向aa[0][4],解引用得到5;

7、笔试题七

下面程序的输出结果是什么:(32位平台下)

int main()
{
	char* a[] = { "work","at","alibaba" };
	char** pa = a;
	pa++;
	printf("%s\n", *pa);
	return 0;
}

解析:

如图:

a是一个指针数组,其中a[0]存放的是字符’w’的地址,a[1]存放的是字符’a’的地址,a[2]存放的是字符’a’的地址; pa = a:a代表首元素的地址,而首元素是字符’w’的地址,所以pa是一个二级指针,指向a[0],pa++指向a[1]; *pa:对pa解引用得到a[1],a[1]中存放的是字符’a’的地址,以字符串的形式打印得到"at";

8、笔试题八

下面程序的输出结果是什么:(32位平台下)

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;
}

解析:

各变量的指向关系如图:

**++cpp:++cpp指向cp[1],解引用找到cp[1],再解引用找到字符’P’的地址,以字符串的形式打印得到’POINT’,此时由于自增操作符的副作用,cpp指向cp[1];

*-- * ++cpp + 3:++cpp指向cp[2],解引用找到cp[2]中的内容c+1,前置–使得cp[2]中的内容变为c,此时cp[2]不再指向字符’N’的地址,而是指向字符’E’的地址;解引用找到字符’E’的地址,+3找到字符串’ENTER’中第二个’E’的地址,以字符串的形式打印得到’ER’;此时由于自增操作符的副作用导致cpp指向cp[2],cp[2]指向字符’E’;

*cpp[-2] + 3 <==> *(*(cpp-2)) + 3:cpp-2指向cp[0],解引用找到cp[0]中的内容c+3,再解引用找到字符’F’的地址,+3找到字符串’FIRST’中字符’S’的地址,以字符串的形式打印得到’ST’;此时因为cpp-2并没有副作用,所以cpp仍然指向cp[2];

cpp[-1][-1] + 1 <==> *(*(cpp - 1) - 1) + 1:cpp-1指向cp[1],解引用找到cp[1]中的内容c+2,再-1变为c+1,解引用找到c+1处的内容,即字符’N’,+1找到字符串"NEW"中的字符’EW’;此时cpp仍然指向cp[2],cp[1]中的内容仍然为c+2;