C语言:指针详解-程序员宅基地

技术标签: c语言  

1、C语言指针是什么?

在C语言中,指针是一个非常重要的概念,简单的说:指针是存储空间的地址,计算机的存储空间由一个个的字节(8位)组成,指针就是这些字节的编号,通过这个编号,就可以访问到特定的存储空间。但是,指针如果简单的来说,就没办法简单的理解,接下来,我们将前面的描述换一种方式进行描述。

首先,我们明确一点,在计算机内,所有在存储空间内的数据都是或多或少的 0/1 位流组成,再往上点,可以说所有的数据都是以字节中的 0/1 序列形式存在,无论是代码数据、音视频、文档,在存储空间内并没有本质区别,都是 0/1 字节序列,那么,怎样让相同的数据具有不同的表现形式?其中的关键就是对数据的解释规则

同样一个苹果,在中国它写成:苹果,在英国写成:apple,变的不是这个水果,而是对它进行解释的规则,利用不同的规则对它进行解释会展现出不同的表现形式。

每个人都可以定义自己的规则来解释同一个数据,我们平时所说的标准、规范,不过也就是一些应用更为广泛、更具解释效率同时也是大家约定俗成的一套数据解释规则而已。C语言,也是一套解释规则,这套规则用来帮助我们更有效的与计算机进行交流,除了怎么对数据进行解释,另外一个关键就是数据本身是什么,由此可以得到两个关键的东西:数据本身是什么,以及怎么解释这个数据

2、C语言指针初体验

思考下列代码:

#include <stdio.h>

int cnt; 
int* cnt_ptr;

int main(int argc, char* argv)
{
    
    cnt = 2155905152;
    cnt_ptr = &cnt;

    printf("cnt = %d, cnt_ptr = %p\n",cnt,cnt_ptr);
}

首先我们看int cnt,按照C语言的规则,也就是语法,这句话表示向计算机申请一个变量,这个变量叫cnt(存储空间别名:别名是为了便于人类自己记忆),同时,我对它的访问需要按照int的规则进行,如果在本机上,int数据类型有四个字节,那么这句话的最终意思就是需要计算机为我准备四个字节的存储空间,同时将存储空间的别名叫做cnt,以后我叫到cnt,计算机就知道我叫的是这四个字节的空间,同时,对这四个字节空间中的 0/1 字节序列按照int的解释规则进行解释;

看到这里我知道你应该很困惑,如果实在搞不清楚,你只需要简单的理解:C语言语法全是规则,自己定义的变量全是别名;

接下来看下一句:int* cnt,规则是:int*,这告诉计算机我们申请的这个变量是拿来存储地址的,而且存储的还是一个int数据类型的地址;

cnt = 2155905152: 叫到了cnt存储空间,=规则告诉计算机把右边的这个数据2155905152放到前面这个叫cnt的地址空间里面去;

cnt_ptr = &cnt :叫到了cnt_ptr存储空间,&在C语言中表示获取它后面存储空间的空间首地址(第一个字节的空间编号),=规则告诉计算机把cnt空间的空间地址给放入到叫cnt_ptr的存储空间里面去;

到此为止,叫cnt的存储空间里面放的是:2155905152,叫cnt_ptr的存储空间里面放的是cnt存储空间四个字节中第一个字节的地址空间编号;

编译运行:

	gcc -o ctest ctest.c
	./ctest

输出结果:
在这里插入图片描述
诶?为什么cnt的输出是负数?我赶紧查了查四字节int的数据范围:-2147483648 到 2147483647,原来我们给它的赋值超出了它最大值;你还会发现,你的第二个地址输出和我也不一致,甚至运行两次的结果都不一样,这不必惊讶,只是操作系统耍的一点点小心机而已,致辞,我们才终于有了一个小例子,离理解还差得远,那么,再探指针!

3、再次体验C语言指针

代码如下:

#include <stdio.h>
#include <stdint.h>

uint32_t cnt;
int* cnt_ptr;

int main(int argc, char* argv)
{
    
    cnt = 2155905152;
    cnt_ptr = &cnt;

    printf("cnt = %d, cnt_ptr = %p\n",cnt,cnt_ptr);
}

和上一段代码唯一的不同是这次我们想要以4字节无符号数的方式对cnt里面的数据进行解释,也就是说我们修改了这段内存中数据的解释规则,再次运行查看结果:
在这里插入图片描述
为什么还是负数?仔细一看,原来在输出的时候还有一次数据解释规则的应用:%d表示输出的是整形,对无符号数的输出需要用到%u,修改输出语句为:

printf("cnt = %u, cnt_ptr = %p\n",cnt,cnt_ptr);

结果如下:
在这里插入图片描述
终于,我们输出了正常的数据,我们可以发现,不同的数据解释规则可以把相同的数据解释为不同的表现形式,至此,你是否有了切身体会?理解了数据本身和数据解释规则,接下来我们继续改造这段代码,进入指针的深入学习。

改造代码如下:

#include <stdio.h>
#include <stdint.h>

uint32_t cnt;
uint8_t* cnt_ptr;
uint32_t* tmp_ptr;

int main(int argc, char* argv)
{
    
    cnt = 2155905152;
    cnt_ptr = &cnt;
    tmp_ptr = &cnt;
    printf("cnt = %u, cnt_ptr = %p, tmp_ptr = %p\n",cnt,cnt_ptr,tmp_ptr);
}

相比于上一段代码,cnt_ptr的规则被换成了uint8_t*,这个规则表示cnt_ptr将被解释为一个一字节无符号数存储空间的首地址,这个是它的解释规则,那它的数据本身是什么?cnt_ptr = &cnt告诉我们它的数据本身是cnt存储空间的第一个字节的空间编号,同时,我们还声明了一个tmp_ptr,其本身数据也是cnt存储空间的第一个字节的空间编号,那真的是这样的吗?

编译运行:

在这里插入图片描述
确实如此,它俩的本身数据就是一样的,那不同的解释规则有什么不同呢?

改造代码如下:

#include <stdio.h>
#include <stdint.h>

uint32_t cnt;
uint8_t* cnt_ptr;
uint32_t* tmp_ptr;

int main(int argc, char* argv)
{
    
    cnt = 2155905152;
    cnt_ptr = &cnt;
    tmp_ptr = &cnt;
    printf("cnt = %u, cnt_ptr = %p, tmp_ptr = %p\n",cnt,cnt_ptr,tmp_ptr);
    for (int i = 0; i < 4; i++)
    {
    
        printf("i = %d *cnt_ptr = %u\n",i,*cnt_ptr);
        cnt_ptr ++; 
    }
}

在C语言中*cnt_ptr规则是:先拿到cnt_ptr这个存储空间里面的值(cnt变量的第一个字节的空间地址),把拿到的值解释为地址空间编号,再去这个地址空间编号所对应的空间里面拿出数据来,怎么拿?cnt_ptruint8_t*类型,那就一个字节一个字节的拿,cnt变量有四个字节,那么就可以拿四次才能把cnt的四个存储空间拿完,编译运行如下:
在这里插入图片描述
我们拿了四次,每次都是128,其二进制是:10000000,每个字节里面的数据都是这个,四个字节就是:2155905152,这是计算机按照我们给出的规则一个一个字节拿的结果。

tmp_ptrcnt_ptr的值一致,是否也可以这样操作呢?可以,但是它只能拿一次,因为它一次拿四个字节,一下就拿完了。

到这,你是否对指针有了初步的认识?是否理解到了数据本身和数据解释规则的重要性,要是还没有弄懂,我们可以试着拆解C语言里面的规则,灵活的组合各种C语言规则以实现自己需要的目的,这很灵活,但是这也是C语言指针的魅力;

4、拆解C语言二级指针

你也许通过,二级指针是指向指针的指针,那么你也许会写下如下的代码:

#include <stdio.h>
#include <stdint.h>

uint32_t cnt;
uint32_t* cnt_ptr;
uint32_t** cnt_ptr_ptr;

int main(int argc, char* argv)
{
    
    cnt = 2155905152;
    cnt_ptr = &cnt;
    cnt_ptr_ptr = &cnt_ptr;
    printf("cnt = %u, cnt_ptr = %p, cnt_ptr_ptr = %p ",cnt,cnt_ptr,cnt_ptr_ptr);
    printf("*cnt_ptr = %u, **cnt_ptr_ptr = %u\n",*cnt_ptr,**cnt_ptr_ptr);
}

编译运行这个代码:
在这里插入图片描述
呵!二级指针,不过是C语言吓退初学者的小把戏而已,你只需要牢记:数据本身和数据解释规则;
上面的代码进行如下改造,使用C语言指针的规则拆解掉二级指针!修改代码如下:

#include <stdio.h>
#include <stdint.h>

uint32_t cnt;
uint32_t* cnt_ptr;
uint64_t cnt_ptr_ptr;

int main(int argc, char* argv)
{
    
    cnt = 2155905152;
    cnt_ptr = &cnt;
    cnt_ptr_ptr = &cnt_ptr;
    printf("cnt = %u, cnt_ptr = %p, cnt_ptr_ptr = %p ",cnt,cnt_ptr,cnt_ptr_ptr);
    printf("*cnt_ptr = %u, **cnt_ptr_ptr = %u\n",*cnt_ptr,*((uint32_t*)(*((uint64_t*)(cnt_ptr_ptr)))));
}

先不解释,编译运行,结果如下:
在这里插入图片描述
经过我们的代码改造,在没有使用二级指针的前提下,实现了与使用二级指针的同样效果,这就是对复杂规则的拆解,但是也可以看见二级指针对后段代码的简化,拆解是为了理解,正常开发视情况而定,接下来我们利用数据本身和数据解释规则两大法宝来解析这两段代码;

注:前面我们一直提到地址数据本身,但是一直没有说地址本身数据的形式是什么?地址数据是地址空间的编号,那么编号的范围常常能限制机器可以寻找内存空间的范围,32位机器位宽为32位,为了效率,地址寻址肯定是最好一次就找到,那么地址空间的最大编号就是:2^32 = 4GB,对于64位机器,最大地址空间编号为:2^64,这个数可就太大了,一般都没有用到64位,一般48位就足够了,可以寻址:2^48 = 256TB,所以在32位上,地址数据本身是一个32位无符号数,64位上是64位无符号数;

首先,无论几级指针,其数据本身都是一个64位无符号数,不管这个指针是指向一个函数、数组、变量、常量还是执行一个指针变量,其数据本身不变,变的是数据的解释规则;

在第二段代码中:

cnt_ptr_ptr = &cnt_ptr;

cnt_ptr_ptr 是一个64位无符号数,这是它的解释规则,只是恰好它存储的数据是一个存储空间编号而已,难以理解的是下面这段代码:

*((uint32_t*)(*((uint64_t*)(cnt_ptr_ptr))))

按照括号对它进行展开:

前提1uint32_t* cnt_ptr;
推论11、cnt_ptr 本身数据是一个64位无符号数
	  2、这个无符号数被解释为一个32位无符号数所在存储空间的第一个字节的地址空间编号
前提2uint64_t cnt_ptr_ptr;
推论21、cnt_ptr_ptr 本身数据是cnt_ptr变量存储空间第一个字节的地址空间编号,是一个64位无符号数
	  2、cnt_ptr_ptr 被解释为一个64位无符号数
	  
由内到外拆解:
/* 强制把 cnt_ptr_ptr 里面存储的数据解释为一个64位无符号数所在存储空间的第一个字节的地址空间编号
* 结合推论2.1,这就是变量cnt_ptr的首地址
* 注:得到cnt_ptr 的地址
*/
A = (uint64_t*)(cnt_ptr_ptr) 
/*
* 按照A的规则,从变量cnt_ptr的首地址起取 uint64_t 得到的是变量cnt_ptr的本身数据
* 也就是变量cnt存储空间的首地址,其本身数据是一个 uint64_t 
* 注:取出 cnt_ptr 的值
*/
B = *(A)
/*
* 将B中存储的 uint64_t 数据强制解释为一个 uint32_t 变量存储空间的首地址
* 注:得到 cnt 的地址
*/
C = (uint32_t*)(B)
/*
* 按照 uint32_t 的规则从 C 本身数据所代表的空间地址编号处取四个字节
* 注:取出 cnt 的值
*/
D = *(C)

说的很绕,也许水平有限没能讲清楚,哈哈哈!

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_37121463/article/details/137206047

智能推荐

几个步骤将Ubuntu 20的apt的Ubuntu源更改为清华源_apt清华源-程序员宅基地

文章浏览阅读5.5k次,点赞6次,收藏7次。其中,focal代表Ubuntu 20.04的代号,根据不同版本的Ubuntu,需要相应更改。如Ubuntu 18.04的代号是Bionic Beaver。在打开的文件中,将默认的源地址替换为清华源地址。_apt清华源

location.href&&window.open_location.href 新窗口-程序员宅基地

文章浏览阅读7k次。href 属性是一个可读可写的字符串,可设置或返回当前显示的文档的完整 url。语法就是 location.href。_location.href 新窗口

Code Simplicity–The Science of Software Development 书摘-程序员宅基地

文章浏览阅读77次。Chapter1 IntroductionThat is the art and talent involved in programming—reducing complexity to simplicity.A “bad programmer” is just somebody who fails to reduce the complexity. So, a “good prog...

uni-app开发介绍_uniapp 开发app-程序员宅基地

文章浏览阅读2.8k次,点赞2次,收藏14次。在学习uni-app前,建议开发者先学习Vue.js框架,因为uni-app基于Vue.js框架开发。若您已经熟悉Vue.js,则可以开始学习uni-app了。uni-app使用HTML、CSS和JavaScript编写应用程序,您可以通过使用Vue.js的语法以及Uni-app提供的组件和API来构建应用程序。在学习uni-app时,建议同时了解应用开发的相关知识,如应用的生命周期、页面布局、事件绑定及其响应等。这些知识将帮助您更好地理解Uni-app的实现原理和应用开发流程。_uniapp 开发app

数据结构-Hash(哈希)基本特征_hash数据结构-程序员宅基地

文章浏览阅读878次,点赞22次,收藏19次。HashMap的实现原理是先要找到要存放数组的下标,如果是null的就存进去,如果不是null的就先判断key值是否一样,如果一样就替换,如果不一样就以链表的形式存在链表中(从JDK8开始,根据元素数量选择使用链表还是红黑树存储。例如上面存放7,8,9时,7直接存入索引为0的位置。8本来应该存到索引为1的位置,但是已经满了,所以向后找,索引3的位置为null,所以8存到索引3的位置,同理9存到6的位置。元素时,会先将目标位置前后的空间搜索一下,将标记为null的位置回收掉,这样大部分不用的位置就收回来了。_hash数据结构

linux配置的jmeter环境变量_linux配置jmeter环境变量-程序员宅基地

在CentOS 7系统中配置非root用户的jmeter环境变量,通过编辑.bash_profile文件并添加export JMET命令来配置。确保配置生效后,可通过java -version命令查看java版本信息。参考链接:知乎和程序员宅基地。

随便推点

运行python报错,Warning! ***HDF5 library version mismatched error***_the hdf5 header files used to compile this applica-程序员宅基地

文章浏览阅读1.7w次,点赞4次,收藏20次。报错内容如下:Warning! ***HDF5 library version mismatched error***The HDF5 header files used to compile this application do not matchthe version used by the HDF5 library to which this application is link..._the hdf5 header files used to compile this application do not match

Solr 4.2.x 拼写检查组件-程序员宅基地

文章浏览阅读88次。2019独角兽企业重金招聘Python工程师标准>>> ..._solr拼写组件

linux命令之ls命令_使用“ls -l”命令列出的以下文件,属于链接文件的是( )。a-rw-rw-rw- 3 root-程序员宅基地

文章浏览阅读660次。首先ls命令是列出当前目录的内容,其次学之前先了解一下ls出来后的不同类型文件的标识. 表示隐藏文件 / 表示一个目录* 表示一个可执行文件@ 表示一个符号链接文件| 表示管道文件= 表示socket文件ls 文件当前目录,ls -a不隐藏以.字符开始的项目 ,ls -A列出除了.和..以..._使用“ls -l”命令列出的以下文件,属于链接文件的是( )。a-rw-rw-rw- 3 root

linux系列之常用运维命令整理笔录_linux运维命令-程序员宅基地

文章浏览阅读10w+次,点赞1.7k次,收藏1.1w次。本博客记录工作中需要的linux运维命令,大学时候开始接触linux,会一些基本操作,可是都没有整理起来,加上是做开发,不做运维,有些命令忘记了,所以现在整理成博客,当然vi,文件操作等就不介绍了,慢慢积累一些其它拓展的命令,博客不定时更新free -m其中:m表示兆,也可以用g,注意都要小写Men:表示物理内存统计total:表示物理内存总数(total=used+free)use......_linux运维命令

单基因gsea_零代码5分+的单基因综合分析-程序员宅基地

文章浏览阅读2k次,点赞2次,收藏26次。小伙伴们好呀!今天和大家分享的是2020年一月份发表在Front Bioeng Biotechnol(IF:5.122)的一篇文章,作者在主要针对头颈癌,从表达量入手,对PGRMC1高低表达组中的差异基因近行GO,GSEA等分析,并与头颈癌患者的临床信息相联系,系统地研究了PGRMC1在癌症中作为原癌基因的作用,发现它影响头颈癌的代谢活性并有预后价值。标题:Identification ..._singlegene.clincialcor.r代码

STM32F4上CCM内存的使用小结_ccmram-程序员宅基地

文章浏览阅读4.6k次,点赞9次,收藏58次。一、CCM内存介绍相较于F2,F4新加的一个特殊内部SRAM。64 KB CCM (内核耦合存储器)数据 RAM 不属于总线矩阵(请参见图 1 : STM32F405xx/07xx和 STM32F415xx/17xx 器件的系统架构)。只能通过 CPU 对其进行访问(dma等外设不能访问)。二、用法(基于MDK)1、自动分配法(不建议使用)设置完后,若重新编译,map文件里就会有这块SRAM的资源分配——由于IRAM1优先使用,而且一般SRAM1够用,就不会给它分配资源。2..._ccmram

推荐文章

热门文章

相关标签