文章目录
注:以下分析均在32位环境下运行
1.小题
第一题:
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));
答案:
分析:
1.a是数组名,表示首元素地址。但在sizeof内部单独出现(不单独出现仍然表示首元素地址,这里的单独出现指的是不进行运算) 的时候表示整个数组,因此整个数组大小是16.
2.a没有单独出现在sizeof内部,因此(a+0)此时代表首元素地址。因此答案是4
3.a是首元素地址,*a是第一个元素1,1是整型,因此大小是4
4.a没有单独出现在sizeof内部,此时表示首元素地址。a+1表示跳过了一个整型,此时a+1是第二个元素的地址,因此大小是4.
5.a[1]等价于*(a+1),相当于在例(4)的基础上解引用,代表第二个元素,大小是4.
6.&a代表整个数组的地址(区分开首元素地址),&a的类型是数组指针,是int(*)[4]类型的。是指针的话大小也是4
对数组指针不了解的看:
https://blog.csdn.net/m0_51641706/article/details/119968733
7.解引用和取地址符号抵消了,剩下a,单独出现在sizeof内部。表示整个数组,因此大小是16
8.&a表示整个数组的地址,由于类型是int(*)[4],对&a+1则表示跳过整个数组。越界了,虽然越界,但sizeof里并不会真的去访问这段空间,它只看sizeof里面的操作数是什么类型的。此时仍然是数组指针。因此大小仍然是4.
对于sizeof不会访问对应空间可以看这两个例子
sizeof(int) ---------4
sizeof(i++) ------------i并不会加1
9.代表首元素地址,大小是4
10.代表第二个元素地址,大小是4
第二题
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));
答案:
仔细看这里并不是全部的答案,10和11两条语句是会使程序崩溃的。(运行时把它注释了)
分析:
1.arr单独出现在sizeof内表示整个数组,大小是6
2.arr没有单独出现在sizeof内部,arr表示的是首元素地址,大小是4
3.*arr表示第一个元素字符a,大小是1
4.和3同理
5.&arr表示整个数组的地址,是一个数组指针,大小是4
6.&arr+1表示一个数组指针,大小是4.(和第一题的(8)同理)
7.&arr[0]表示第一个元素地址,加一表示第二个元素地址。大小是4.
从第八题开始是strlen,别看错了。
8.strlen的原理是找NUL,由于arr里面没有,因此它会一直往数组后越界访问,直到它找到NUL. 因此长度是随机值
9.和8同理,也是随机值
10.第十题是有点让人困惑的。表达式的意思是strlen(‘a’),这是什么意思呢?计算机认为这是把a的ASCII码值当作地址传入strlen了。由于操作系统不允许访问地址为97的空间,因此程序会崩溃。
11.和10同理,相当于访问地址为98的空间,这是不允许的。程序会崩溃
12.&arr是整个数组的地址,类型是char(*)[6],在传入strlen时会被强制转换为char*类型。&arr的地址和arr是一样的,因此答案应该和第8小题一样,也是随机值。
13.&arr+1是跳过整个数组,然而它仍是一个地址,strlen会从那个地址继续往后找NUL,长度仍是随机值。有趣的是,它比第12小题的随机值少6。
14.相当于从第二个元素开始出发找NUL,长度仍然是随机值。有趣的是它比第12小题的随机值少1.
第三题
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));
答案:有两条语句会导致程序崩溃哦。这是注释后的运行结果
**分析:**1.大小是7,别忘了这种初始化会自动在末尾加上NUL
2.arr没有单独出现在sizeof内部,因此a这里是首元素的地址,大小是4
3.对arr解引用是字符a,因此大小是1
4.和小题3同理,大小是1
5.&arr是整个数组的地址,类型是char(*)[7],是地址大小就是4
6.和5同理,大小是4
7.是第二个元素的地址,大小是4
下面的是strlen了
8.终于到常规的strlen的使用了,长度是6
9.和小题8同理,长度是6
10.*a是第一个字符’a’,相当于strlen(97),程序会崩溃
11.和第10小题同理
12.&arr的地址和arr相同,虽然类型是数组指针但在传入strlen的时候,会强制转换成char*。长度也是6
13.&arr+1相当于跳过一整个数组,因此NUL的位置就是未知的了。长度是随机值
14.相当于第二个元素的地址,因此长度是5
第四题
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));
答案:
分析:
1.注意看,这里p是字符指针了。因此大小是4
2.p+1是第二个字符的地址,大小是4
3.*p是一个字符,大小是1
4.p[0]是一个字符,大小是1
5.&p的类型是char**,仍然是指针,大小是4
6.&p+1的类型仍然是char**,虽然越界了然无所谓,sizeof不会真的访问那段空间,大小是4
7.第二个字符地址,大小是4
下面是strlen了
8.长度是6,这个超简单
9.长度是5,这个也简单对吧
10.相当于strlen(97),老套路了。不解释了。
11.和小题10同理
12这道题有点不同,&p的类型是char**,它里面存的是p的地址,可它自己的地址可是未知的。因此长度是随机值。
13.char**+1的地址也是未知的,长度是随机值
14.长度是5
有没有发现,只要原理懂了其实套路都是一样的!
但接下来这道题可能就稍微难一点了。
第四题
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]));
答案:
分析:
1.a是二维数组的数组名,单独出现在sizeof内部,代表的是整个数组。因此大小是12×4等于48
2.a[0][0]是第一个元素,大小是4
3.a[0]是第一行的数组名。它单独出现在sizeof内部,因此代表整个第一行数组,大小是4×4等于16
注:为什么a[0]是第一行数组名?
我们要知道数组名本身的意义是首元素的地址。a[0]等价于*(a+0)
相当于对二维数组数组名a解引用了,而二维数组数组名代表的含义是
第一行数组的地址,因此解引用后代表的是第一行第一列元素的地址,这
正是一维数组数组名的含义。
由此类推a[1],a[2],a[3]...都是二维数组里面的一维数组名。
分别是第二行,第三行的,...
4.a[0]是第一行数组名,但它没有单独出现在sizeof内,因此它代表的是第一行第一列的元素的地址,加1后变成第一行第二列的元素的地址。因此大小是4
5.在小题4的基础上解引用得到了第一行第二列的元素,因此大小是4
6.a是二维数组数组名,但没有单独出现在sizeof内部,因此a代表第一行数组的地址,+1得到第二行数组的地址。类型是int(*)[4]的数组指针。大小是4
7.在小题6的基础上解引用得到了第二行首元素的地址,即第二行的数组名。它单独出现在sizeof内部。因此大小是16
8.a[0]是第一行首元素地址,&a[0]是第一行地址,&a[0]+1是第二行数组地址,类型是数组指针,大小是4
9.在8的基础上解引用,得到了第二行数组的首元素地址,它是第二行数组的数组名,因此大小是16、
10.a是第一行数组的地址,*a是第一行首元素地址,它单独出现在sizeof内部,因此大小是16.
11.a[3]仍然是第四行数组名,虽然它越界了。但我们知道sizeof并不会去访问这段空间,它只在乎里面的操作数的类型是什么。因此大小是16
2.大题(最后一题最难)
第一题
int a[5] = { 1, 2, 3, 4, 5 };
int* ptr = (int*)(&a + 1);
printf("%d,%d", *(a + 1), *(ptr - 1));
答案:2,5
分析:
由图就很容易看出来了,不难
第二题
//已知该结构体大小为20字节
struct Test
{
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
int main()
{
p = 0x100000;
printf("%x\n", p + 0x1);
printf("%x\n", (unsigned long)p + 0x1);
printf("%x\n", (unsigned int*)p + 0x1);
return 0;
}
答案:
分析:
1.p是一个结构体指针,对它加一即跳过一个对应结构体大小的空间。因此p+0x1等于100014(这是16进制的表达方式,其实就是加了20个字节)
2.把p强制转换成unsigned long类型,由于这不是指针了,就变成普通的加法,答案是100001
3.强制转换成int*,加一跳过四个字节。因此为100004
第三题
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;
答案:
4
2000000
分析:
1.下标是负数其实是访问该地址前面的元素。
ptr1相当于跳过了一个数组,访问ptr[-1]相等于*(ptr-1),答案就是4.
2.ptr2有趣很多。它先把a强制转换成int,再加1,相当于对地址加1。1在内存中以大端形式存储。此时ptr2指向的值如图:
由于ptr2是int*类型,因此解引用的作用范围是4个字节,*ptr2访问的内容如下:
从内存中拿出来后变成02 00 00 00
注:这里写的都是16进制
第四题
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] = { {0,1},{2,3},{4,5} };
而这里用的是逗号表达式。实际上这个二维数组被初始化成了如下的样子:
根据这个图,答案也很明显了。就是输出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;
}
分析:
p是数组指针,数组为4个元素。因此p+1是跳过4个元素。
而a是二维数组数组名,加1跳过5个元素。
而
p[4][2] 等价于*(*(p+4)+2),实际上计算机就是这么算的.
因此我们可以知道它们两对应的位置在哪。如图:
由于指针减指针得到的是两个指针之间的元素个数。(注:不是它们两相差的字节数)因此答案是4。
多提一句:对int类型的数组指针解引用后可以理解成类型int*。原先对数组指针加1是跳过一个数组,解引用后加1就是跳过一个整型
第六题:
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是二维数组名,类型是int(*)[5],&aa的类型是int(*)[2][5],对它加1代表跳过一整个二维数组。由于此时的类型仍然是int(*)[2][5],需要强制转换成int*才能赋值给ptr1.
aa+1再解引用的类型本来就是int*了,这里的强制转换其实没必要。
答案是5,10
第七题:
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
分析:
字符指针数组里每一个元素并不是字符串(C语言也没有字符串),而是每个字符串的第一个字符的地址。
pa++,代表了pa现在指向a的下一个字节的地址。
因此解引用之后得到的是at字符串的首字符地址。所以打印出来是at。
第八题:(压轴题)
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++指向c+2,而c+2指向POINT,因此第一个打印出来的是POINT
第二条式子(易错)
cpp++指向c+1(由于第一条式子cpp已经自增1了)
解引用后得到c+1.c+1再减减得到c(重点,易错点)
注:这里不是让c+1变成c+2。而是让c+1变成c
c指向的是ENTER。
此时再解引用得到ENTER的首字符地址。
再加3得到ER的首字符地址。因此输出ER。
第三条式子
CPP此时指向的是cp的第三个元素
cpp[-2]等效于*(cpp-2),得到了c+3。
解引用得到了FIRST的首字符地址
再加3,得到了ST
第四条式子
cpp[-1][-1]+1等效于*(*(cpp-1)-1)+1
cpp-1指向c+2的地址,解引用得到c+2.
c+2减1得到c+1再解引用得到NEW的首字符地址
NEW再加1得到EW的首字符地址
因此输出EW