不得不看,深度理解 你所熟悉的数组和指针的类型!让大多数面试官也对你的分析过程亮眼

#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位方式编译、运行

 

 

 

.

posted @ 2020-09-25 00:51  一匹夫  阅读(232)  评论(0编辑  收藏  举报