不得不看,深度理解 你所熟悉的数组和指针的类型!让大多数面试官也对你的分析过程亮眼
#if 1
#include <stdio.h>
void base_demo1()
{
int *pTest = 0;
printf("pTest = 0x%p \n", pTest);
pTest++;
printf("pTest = 0x%p \n", pTest);
printf("sizeof(int*)= %ld \n", sizeof(int*));
printf("sizeof(int)= %ld \n", sizeof(int));
printf("-- 结论:int *p; p++; ==> p加了sizeof(*p)--\n\n");
//结论:int *p; p++; 等效于==> p = p+sizeof(*p);
}
// 看到一个函数带这么多参数,是不是要疯了,不要介意这个,我们只是测试学习用而已。平时写函数,也不会写这么多参数。
// 调用时:func(array, array, (int**)(&array), &p, &p_3, str, (char**)(&str), &array);
void func(int array[10], int* istr, int** pistr, int** pistr_2, int** pistr_3, char* str, char** pcstr, int(*p_array)[10])
{
printf("sizeof(array)= %ld \n", sizeof(array));
printf("sizeof(p_array)= %ld \n", sizeof(p_array));
// 运行结果,(64位平台下测试)都是8字节, 可见,数组 或 数组指针 作为形参,实际传递的都是指针。
printf("sizeof(istr)= %ld \n", sizeof(istr));
/*
数组元素A[b] 表示什么意思。这表示需要访问:
1,以A的地址为最初内存起点,
2.以A的类型对应内存上元素的大小乘以b的值,该值为偏移量。 注意:如果A是int*,则对应的内存上元素的大小就是int类型的大小,而不是int*类型的大小哦! 。
通过 最初内存起点+偏移量 的方式来定位出要访问的最终内存起点。
另外,还需要获取将要访问的内存长度:将要获取数据的内存长度参考A的类型。
老调重谈,示例A:
int a[10]={0}; 怎么计算a[2]的值呢?
按照上述方法,以a这个地址作为初始最初内存起点,
a的类型是int*, 对应的元素肯定是int类型,那么得到下面X、Y结论:
X:偏移量就是 sizeof(int)×2 , 即偏移量为4×2=8.
注意,假设这里是sizeof(int*)x2,那么在ubuntu 64位系统内,sizeof(int*)是8,再乘以2得16。而一个int元素占4字节,很明显,得到的肯定不是a[2]!
Y: 待访问的内存长度是 sizeof(int) ,就4字节。这个应该好理解:待访问的内存长度肯定应该等于一个数组元素实际所占的内存大小。
所以a[2]访问到的是以a+sizeof(int)×2为最终内存起点,访问4字节内存长度。 然后将得到的这4字节数据按照大小端组合,才得到最终数值。
我们为什么要老调子重谈?是为了寻找基本的规律,以便套用这个规律来套用理解我们稍微一眼看去略显陌生的东西!规律就是规律,同样的事务基本遵循相同的规律。
(何况计算机这东西是非常遵循规律来运转的)
回归到本话题,
此处pistr是int** 类型, 套用上述我们得出的规律,pistr[2]就表示从pistr+sizeof(int*)×2地址开始处的,
长度为sizeof(int*)的这段内存上的数据整合到的值。
在64为系统上,sizeof(int*)的值为8,则偏移量为8*2=16字节。
所以pistr[2]访问到的值是局部数组元素array[4](array[4]相对array起始地址的偏移量也为16字节)的地址处的数据,
这是要访问的最终的内存起点。
再求待访问的长度,即sizeof(int*),也就是8字节。
然后鉴于我使用的是%d来打印, 所以只会打印4字节内容,也就是说,实际访问到的是8字节,而打印出来的是这8字节数据的一部分而已!
如果理解不了上面为什么使用sizeof(int*)而不是sizeof(int)来计算待访问的内存起始地址和待访问的内存长度,还可以再看一个佐证,提供了另一种思路。
(本质上,下面的分析和 上述的示例A是一样的,但是也可以分析分析,多多益善)。
pistr[2] 访问的方式也等价于 *(pistr+2) ,这个大家肯定都不会怀疑,学过C语言数组,一般都会学到这个。
64位系统上,int*类型是8字节, int类型是4字节。正因为64位系统上,int* 和 int 的类型大小有差异,于是我们在64位系统上进行实验。
(没差异,就不容易发现区别了嘛!所以我们做这个实验不基于32位ubuntu!)
示例B:
int test[3]={1,2,3}; 这表示3个元素顺序排列,每4字节排布一个元素,这就是一个数组 {数组相邻元素的内存地址的差值,和任意数组元素占用内存大小,是相等的}。
基于常识, 访问*(test+1) 得到的一定等于2。
这里test是int* 类型,对应的内存上元素的类型是int类型,*(test+1)表示的是从test+sizeof(int)×1为待访问的内存起始地址处,
待访问的长度是int类型大小(对test类型再解引用,得到int类型),即这段内存上的4字节数据按照大小端所组合得到的最终数据。
我们也可以得到规律,表达式*(test+Z)的释义是:以test+sizeof(*(test类型))×Z为待访问的内存起始地址,以sizeof(*(test类型))未待访问的内存长度。
*/
printf("sizeof(pistr)= %ld , pistr[2]=%d, pistr_2[0]=0x%p, pistr_3[1]=0x%p\n", \
sizeof(pistr), pistr[2], pistr_2[0], pistr_3[1]);
printf("sizeof(pistr)= %ld \n", sizeof(*pistr));
printf("sizeof(str)= %ld \n", sizeof(str));
}
int main()
{
base_demo1();
int array[10] = { 10,11,12,13,14,15 };
char* str = "hello";
int*p = array;
#if 1
// PART1
printf("p = 0x%p \n", p);
printf("&p = 0x%p \n", &p);
printf("array = 0x%p, &array = 0x%p \n", array, &array);
//结论:p值不同于&p值,因为p是个变量,所以变量的地址和变量的值是两个概念。
// 但是array值与&array值相同,数组名是个符号,其值等于数组首元素地址首地址,对数组名这个符号再次取地址,得到的值不变。
printf("\n\n");
#endif
#if 1
// PART2
int**p_3 = &array;
printf("int**p_3 = 0x%p \n", p_3);
func(array, array, (int**)(&array), &p, &p_3, str, (char**)(&str), &array);
printf("\n");
printf("sizeof(array)= %ld \n", sizeof(array));
printf("sizeof(str)= %ld \n", sizeof(str));
//小结: 符号array是数组,特定绑定一块内存,此属性不可更改。 故sizeof(array)的是符号array所绑定的数组内存的大小。
// str是一个执行hello字符串的指针,该指针是灵活多变的。 故sizeof(str)的大小是一个指针变量的大小,4(32位平台)或8字节(64位平台)。
printf("\n\n");
#endif
#if 1
// PART3
char data = 'c';
str = &data;
printf("str[0] = %c \n", str[0]);
char* const str_2 = "QQQQQQQQQQQQQQ";
printf("sizeof(str_2)= %ld \n", sizeof(str_2));
// 现象: sizeof(数组名)等于数组大小, 而sizeof(指针常量)等于4字节(32位平台)或8字节(64位平台)。
// 此实验说明虽然数组名的实现 和 指针常量(例如char* const p)很类似, 但在编译器眼里,仍然是有区别的,
// 知道这个区别就行,下次和别人聊天,不要把数组名完全等价为指针常量。
#endif
return 0;
}
#endif
运行结果:
root@llllw-virtual-machine:/home/lllllw/桌面/C_Text# ./ab
pTest = 0x(nil)
pTest = 0x0x4
sizeof(int*)= 8
sizeof(int)= 4
-- 结论:int *p; p++; ==> p加了sizeof(*p)--
p = 0x0x7fff425e3bf0
&p = 0x0x7fff425e3bd8
array = 0x0x7fff425e3bf0, &array = 0x0x7fff425e3bf0
int**p_3 = 0x0x7fff425e3bf0
sizeof(array)= 8
sizeof(p_array)= 8
sizeof(istr)= 8
sizeof(pistr)= 8 , pistr[2]=14, pistr_2[0]=0x0x7fff425e3bf0, pistr_3[1]=0x0x40096d # 也就这段比较难理解一点。
sizeof(pistr)= 8
sizeof(str)= 8
sizeof(array)= 40
sizeof(str)= 8
str[0] = c
sizeof(str_2)= 8
root@lllllw-virtual-machine:/home/lllllw/桌面/C_Text#
截止这里,本文并未直接给出最难的一句代码,即func函数内部:
printf("sizeof(pistr)= %ld , pistr[2]=%d, pistr_2[0]=0x%p, pistr_3[1]=0x%p\n", \
sizeof(pistr), pistr[2], pistr_2[0], pistr_3[1]);
这句代码的运行答案,但是鉴于之前系统的分析推理,相信不会有什么难处。
什么?貌似还有点点疑惑?不管你会不会,
我这种人是送佛送到西的,同时鉴于之前的函数代码有点多了,我专门针对这个提取出一个小demo,咱们再接着来透彻分析。
#include <stdio.h>
int array[10] = { 0xa0,0xb1,0xc2,0xd3,0x55667788,0xeeff,0x1,0x2,0x3,0x4 };
void func(int** pistr)
{
printf("pistr[2]=%d\n", pistr[2]); // 使用%d,这样只会打印4字节内容
printf("pistr[2]=%ld\n", pistr[2]);
printf("pistr[2]=%lx\n", pistr[2]);
// 使用 %lx:unsinged long int (长整形) 在ubuntu64位系统上,这样就会打印出8字节内容。
// 将前4字节的0x55667788和后4字节的0xeeff组合起来就得到了。
}
int main()
{
func((int**)(&array)); // way 1
printf("\n");
func((int**)array); // way 2 . 与way 1等效。 数组名再取地址,其值和数组名的值一样。
return 0;
}
运行:
t@llllw-virtual-machine:/home/llllw/桌面/C_Text# ./ab
pistr[2]=1432778632 // 等价于 ox5566 7788
pistr[2]=262780416849800
pistr[2]=eeff55667788
pistr[2]=1432778632
pistr[2]=262780416849800
pistr[2]=eeff55667788
root@llllw-virtual-machine:/home/llllw/桌面/C_Text#
分析:
小结: 本博客通过把一个一级指针(数组名作为指针常量),转为二级指针来使用,引发上述实验和相关的分析。 侧重论述的,可归根结底为 深度理解指针的类型。
ps: 追加,后记 本帖中涉及了对数组名的类型的判断。我们通过一个小demo来感受下。
尝试探索数组名的类型:
再来一个类似题目巩固下,常见于用作笔试题哦:
int a[5] = {1,2,3,4,5};
int main(){
printf("a = %d \n\n", a);
unsigned int addr1 = (int*)(&a+1) -1;
// 下面的偏移量都是整个数组大小
printf("(&a+1) = %d \n", (&a+1));
printf("(&a+1) -1 = %d \n", (&a+1) -1);
printf("((&a+1) -1 )+1 = %d \n", ((&a+1) -1 )+1);
printf("(&a+1) -1 +1 = %d \n\n", (&a+1) -1 +1);
// (int*)(&a+1) -1 ,最后面减1,指的应该是减去1个int*指针所指向的类型,即int型大小,4
printf("(int*)(&a+1) -1 = %d \n", (int*)(&a+1) -1);
unsigned int addr2 = &(a[4]);
if(addr1 == addr2){
printf("hello \n");
}
printf("sizeof(int*) = %ld \n", sizeof(int*));
printf("sizeof(int) = %ld \n", sizeof(int));
printf("sizeof(unsigned int) = %ld \n", sizeof(unsigned int));
}
gcc -test.c -m64 以64位方式编译、运行
.
/************* 社会的有色眼光是:博士生、研究生、本科生、车间工人; 重点大学高材生、普通院校、二流院校、野鸡大学; 年薪百万、五十万、五万; 这些都只是帽子,可以失败千百次,但我和社会都觉得,人只要成功一次,就能换一顶帽子,只是社会看不见你之前的失败的帽子。 当然,换帽子决不是最终目的,走好自己的路就行。 杭州.大话西游 *******/