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

    #if 1
    
    
    #include <stdio.h>
    
    void base_demo1()
    {
        int *pTest = 0; 
        printf("pTest = 0x%p 
    ", pTest); 
        pTest++;
            printf("pTest = 0x%p 
    ", pTest); 
        
    
        printf("sizeof(int*)= %ld 
    ", sizeof(int*)); 
        printf("sizeof(int)= %ld 
    ", sizeof(int)); 
    
        printf("-- 结论:int *p; p++; ==> p加了sizeof(*p)--
    
    "); 
    
            //结论: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 
    ",   sizeof(array)); 
        printf("sizeof(p_array)= %ld 
    ", sizeof(p_array)); 
            // 运行结果,(64位平台下测试)都是8字节, 可见,数组 或 数组指针 作为形参,实际传递的都是指针。
    
        printf("sizeof(istr)= %ld 
    ",  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 ", sizeof(pistr), pistr[2], pistr_2[0], pistr_3[1]); printf("sizeof(pistr)= %ld ", sizeof(*pistr)); printf("sizeof(str)= %ld ", 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 ", p); printf("&p = 0x%p ", &p); printf("array = 0x%p, &array = 0x%p ", array, &array); //结论:p值不同于&p值,因为p是个变量,所以变量的地址和变量的值是两个概念。 // 但是array值与&array值相同,数组名是个符号,其值等于数组首元素地址首地址,对数组名这个符号再次取地址,得到的值不变。 printf(" "); #endif #if 1 // PART2 int**p_3 = &array; printf("int**p_3 = 0x%p ", p_3); func(array, array, (int**)(&array), &p, &p_3, str, (char**)(&str), &array); printf(" "); printf("sizeof(array)= %ld ", sizeof(array)); printf("sizeof(str)= %ld ", sizeof(str)); //小结: 符号array是数组,特定绑定一块内存,此属性不可更改。 故sizeof(array)的是符号array所绑定的数组内存的大小。 // str是一个执行hello字符串的指针,该指针是灵活多变的。 故sizeof(str)的大小是一个指针变量的大小,4(32位平台)或8字节(64位平台)。 printf(" "); #endif #if 1 // PART3 char data = 'c'; str = &data; printf("str[0] = %c ", str[0]); char* const str_2 = "QQQQQQQQQQQQQQ"; printf("sizeof(str_2)= %ld ", 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
    ",   
                   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
    ",   pistr[2]); // 使用%d,这样只会打印4字节内容
      printf("pistr[2]=%ld
    ",  pistr[2]);
    
      printf("pistr[2]=%lx
    ",  pistr[2]); 
    // 使用 %lx:unsinged long int (长整形)  在ubuntu64位系统上,这样就会打印出8字节内容。
    // 将前4字节的0x55667788和后4字节的0xeeff组合起来就得到了。
    } int main() { func((int**)(&array)); // way 1 printf(" "); 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 
    
    ", a);
    
      unsigned int addr1 = (int*)(&a+1) -1;
    
      // 下面的偏移量都是整个数组大小
      printf("(&a+1) = %d 
    ", (&a+1));	
      printf("(&a+1) -1 = %d 
    ", (&a+1) -1);
      printf("((&a+1) -1 )+1 = %d 
    ", ((&a+1) -1 )+1);
      printf("(&a+1) -1 +1 = %d 
    
    ",   (&a+1) -1 +1);
    
      // (int*)(&a+1) -1 ,最后面减1,指的应该是减去1个int*指针所指向的类型,即int型大小,4
      printf("(int*)(&a+1) -1 = %d 
    ", (int*)(&a+1) -1);
    
      unsigned int addr2 = &(a[4]);
     
      if(addr1 == addr2){
           printf("hello 
    ");	
      }
    
      printf("sizeof(int*) = %ld 
    ", sizeof(int*));	
      printf("sizeof(int) = %ld 
    ", sizeof(int));
      printf("sizeof(unsigned int) = %ld 
    ", sizeof(unsigned int));
    }
    

    gcc -test.c -m64 以64位方式编译、运行

     

    .

    /************* 社会的有色眼光是:博士生、研究生、本科生、车间工人; 重点大学高材生、普通院校、二流院校、野鸡大学; 年薪百万、五十万、五万; 这些都只是帽子,可以失败千百次,但我和社会都觉得,人只要成功一次,就能换一顶帽子,只是社会看不见你之前的失败的帽子。 当然,换帽子决不是最终目的,走好自己的路就行。 杭州.大话西游 *******/
  • 相关阅读:
    Objective-C-----协议protocol,代码块block,分类category
    iOS-Core Data 详解
    TCP、UDP详解
    springboot+mybatisplus配置多个mapper路径
    django.core.exceptions.ImproperlyConfigured: Error loading MySQLdb module: No module named MySQLdb的解决方法
    Django笔记——Eclipse+pydev首个django例子helloworld
    eclipse html插件的下载和安装
    sqlserver往字符串里固定位置插入字符
    在文件夹中直接调用命令提示符
    HTML:如何将网页分为上下两个部分
  • 原文地址:https://www.cnblogs.com/happybirthdaytoyou/p/13727576.html
Copyright © 2011-2022 走看看