C语言重点——指针篇(一文让你完全搞懂指针)| 从内存理解指针 | 指针完全解析
有干货、更有故事,微信搜索【编程指北】关注这个不一样的程序员,等你来撩~
注:这篇文章好好看完一定会让你掌握好指针的本质
C语言最核心的知识就是指针,所以,这一篇的文章主题是「指针与内存模型」
说到指针,就不可能脱离开内存,学会指针的人分为两种,一种是不了解内存模型,另外一种则是了解。
不了解的对指针的理解就停留在“指针就是变量的地址”这句话,会比较害怕使用指针,特别是各种高级操作。
而了解内存模型的则可以把指针用得炉火纯青,各种 byte 随意操作,让人直呼 666。
一、内存本质
编程的本质其实就是操控数据,数据存放在内存中。
因此,如果能更好地理解内存的模型,以及 C 如何管理内存,就能对程序的工作原理洞若观火,从而使编程能力更上一层楼。
大家真的别认为这是空话,我大一整年都不敢用 C 写上千行的程序也很抗拒写 C。
因为一旦上千行,经常出现各种莫名其妙的内存错误,一不小心就发生了 coredump… 而且还无从排查,分析不出原因。
相比之下,那时候最喜欢 Java,在 Java 里随便怎么写都不会发生类似的异常,顶多偶尔来个 NullPointerException,也是比较好排查的。
直到后来对内存和指针有了更加深刻的认识,才慢慢会用 C 写上千行的项目,也很少会再有内存问题了。(过于自信
「指针存储的是变量的内存地址」这句话应该任何讲 C 语言的书都会提到吧。
所以,要想彻底理解指针,首先要理解 C 语言中变量的存储本质,也就是内存。
1.1 内存编址
计算机的内存是一块用于存储数据的空间,由一系列连续的存储单元组成,就像下面这样,
每一个单元格都表示 1 个 Bit,一个 bit 在 EE 专业的同学看来就是高低电位,而在 CS 同学看来就是 0、1 两种状态。
由于 1 个 bit 只能表示两个状态,所以大佬们规定 8个 bit 为一组,命名为 byte。
并且将 byte 作为内存寻址的最小单元,也就是给每个 byte 一个编号,这个编号就叫内存的地址。
这就相当于,我们给小区里的每个单元、每个住户都分配一个门牌号: 301、302、403、404、501…
在生活中,我们需要保证门牌号唯一,这样就能通过门牌号很精准的定位到一家人。
同样,在计算机中,我们也要保证给每一个 byte 的编号都是唯一的,这样才能够保证每个编号都能访问到唯一确定的 byte。
1.2 内存地址空间
上面我们说给内存中每个 byte 唯一的编号,那么这个编号的范围就决定了计算机可寻址内存的范围。
所有编号连起来就叫做内存的地址空间,这和大家平时常说的电脑是 32 位还是 64 位有关。
早期 Intel 8086、8088 的 CPU 就是只支持 16 位地址空间,寄存器和地址总线都是 16 位,这意味着最多对 2^16 = 64 Kb
的内存编号寻址。
这点内存空间显然不够用,后来,80286 在 8086 的基础上将地址总线和地址寄存器扩展到了20 位,也被叫做 A20 地址总线。
当时在写 mini os 的时候,还需要通过 BIOS 中断去启动 A20 地址总线的开关。
但是,现在的计算机一般都是 32 位起步了,32 位意味着可寻址的内存范围是 2^32 byte = 4GB
。
所以,如果你的电脑是 32 位的,那么你装超过 4G 的内存条也是无法充分利用起来的。
好了,这就是内存和内存编址。
1.3 变量的本质
有了内存,接下来我们需要考虑,int、double 这些变量是如何存储在 0、1 单元格的。
在 C 语言中我们会这样定义变量:
int a = 999;
char c = 'c';
- 1
- 2
当你写下一个变量定义的时候,实际上是向内存申请了一块空间来存放你的变量。
我们都知道 int 类型占 4 个字节,并且在计算机中数字都是用补码(不了解补码的记得去百度)表示的。
999
换算成补码就是:0000 0011 1110 0111
这里有 4 个byte,所以需要四个单元格来存储:
有没有注意到,我们把高位的字节放在了低地址的地方。
那能不能反过来呢?
当然,这就引出了大端和小端。
像上面这种将高位字节放在内存低地址的方式叫做大端
反之,将低位字节放在内存低地址的方式就叫做小端:
上面只说明了 int 型的变量如何存储在内存,而 float、char 等类型实际上也是一样的,都需要先转换为补码。
对于多字节的变量类型,还需要按照大端或者小端的格式,依次将字节写入到内存单元。
记住上面这两张图,这就是编程语言中所有变量的在内存中的样子,不管是 int、char、指针、数组、结构体、对象… 都是这样放在内存的。
二、指针是什么东西?
2.1 变量放在哪?
上面我说,定义一个变量实际就是向计算机申请了一块内存来存放。
那如果我们要想知道变量到底放在哪了呢?
可以通过运算符&
来取得变量实际的地址,这个值就是变量所占内存块的起始地址。
(PS: 实际上这个地址是虚拟地址,并不是真正物理内存上的地址
我们可以把这个地址打印出来:
printf("%x", &a);
- 1
大概会是像这样的一串数字:0x7ffcad3b8f3c
2.2 指针本质
上面说,我们可以通过&
符号获取变量的内存地址,那获取之后如何来表示这是一个地址,而不是一个普通的值呢?
也就是在 C 语言中如何表示地址这个概念呢?
对,就是指针,你可以这样:
int *pa = &a;
- 1
pa 中存储的就是变量 a
的地址,也叫做指向 a
的指针。
在这里我想谈几个看起来有点无聊的话题:
为什么我们需要指针?直接用变量名不行吗?
当然可以,但是变量名是有局限的。
变量名的本质是什么?
是变量地址的符号化,变量是为了让我们编程时更加方便,对人友好,可计算机可不认识什么变量 a
,它只知道地址和指令。
所以当你去查看 C 语言编译后的汇编代码,就会发现变量名消失了,取而代之的是一串串抽象的地址。
你可以认为,编译器会自动维护一个映射,将我们程序中的变量名转换为变量所对应的地址,然后再对这个地址去进行读写。
也就是有这样一个映射表存在,将变量名自动转化为地址:
a | 0x7ffcad3b8f3c
c | 0x7ffcad3b8f2c
h | 0x7ffcad3b8f4c
....
相关文章:
- 快速入门Docker
- 你的眼中满是“变量”,可“变量”眼中是无相(Python)(Java与Python学习通法)
- Kafka 实战 (1):消息中间件原理与概念
- 2021Java面经:java封装的概念
- kafka的基本概念和工作流程分析
- 盒子科技笔试Java_丰巢科技 Java高级笔试面试题 PDF 下载
- 丰巢快递柜收费,究竟挑动了我们哪根神经?
- 2019年丰巢科技Java面试题
- 丰巢科技面试题(2019年JAVA)
- 丰巢科技-Java高级
- 丰巢面试经验
- 哈利波特(分院帽)
- ChartDirector 6.3(C ++版)教程分享——图标饼图
- Jquery实现京东tab切图
- 国内网络安全公司、社区简介
- arduino 驱动_Arduino驱动的My Little Pony捐款箱
- 刘群:基于深度学习的自然语言处理,边界在哪里?
- 用C++编写出《哈利波特》的分院帽程序,不要错过哦~
- ai人工智能_对人工智能的追求
- 一个续写故事达到人类水平的AI,OpenAI大规模无监督语言模型GPT-2...
- Task 4: Contextual Word Embeddings
- 2021年亚太地区15个最佳职场榜单;人均养老消费预期将超百万元;高净值人群财富传承方式依然首选保险 | 美通社头条...
- 买一台 iPhone X,还是创建一家未来的独角兽?
- 买一台 iPhone X,还是创建一家未来的独角兽?
- [预训练语言模型专题] 银色独角兽GPT家族
- VUE头像轮播组件
- 达摩院用128张GPU烧出“中文版GPT-3”,我试了下,这文风不是开往幼儿园的车…...
- 九天·毕昇 试玩体验
- 详解变分自编码器VAE(Variational Auto-Encoder)
- 简单认识一点C语言
C语言重点——指针篇(一文让你完全搞懂指针)| 从内存理解指针 | 指针完全解析相关推荐
- lambda表达式java项目常用_一文带你彻底搞懂Lambda表达式
1. 为什么使用Lambda表达式 Lambda是一个匿名函数,我们可以把Lambda表达式理解为是一段可以传递的代码(将代码像数据一样进行传递).可以写出更简洁.更灵活的代码.作为一种更紧凑的代码风 ...
- 一文带你彻底搞懂i++和++i的区别,谁的效率更高?
作者简介:Codebowl靓仔,学妹的工具人,C++开发误入数据开发,梦想30岁退休的靓仔就是我啦. i++和++i对于初学者来说,一直是一个特别容易搞混的内容,相信很多人现在也没有完全搞清(作者初学 ...
- 一文让你彻底搞懂AQS(通俗易懂的AQS)
一文让你彻底搞懂AQS(通俗易懂的AQS) 一.什么是AQS AQS是一个用来构建锁和同步器的框架,使用AQS能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的ReentrantLock,Se ...
- 一文带你彻底搞懂C++中一些常见指针(形如*p)的用法
提高指针效率以及程序可读性 为什么要后置运算符? 有C语言编程经验的人可能会感到疑问,为什么在C++里面我们推荐优先使用运算符的前置版本,就是把运算符号放在变量的前面,前置版本的递增避免了不必要的工作 ...
- java中对递归的限制是什么_什么是递归,通过这篇文章,让你彻底搞懂递归
美丽开始于你决定做自己的那一刻. 啥叫递归 tips:文章有点长,可以慢慢看,如果来不及看,也可以先收藏以后有时间在看. 聊递归之前先看一下什么叫递归. 递归,就是在运行的过程中调用自己. 构成递归需 ...
- 开发人员必学!这篇入门你必须了解!搞懂这些直接来阿里入职
前言 Netty 是一款基于 Java 的网络编程框架,能为应用程序管理复杂的网络编程.多线程处理以及并发.Netty 隐藏了样板和底层代码,让业务逻辑保持分离,更加易于复用.使用 Netty 可以得 ...
- 一文带你轻松搞懂事务隔离级别(图文详解)
本文由 SnailClimb 和读者 BugSpeak 共同完成. 事务隔离级别(图文详解) 什么是事务? 事务是逻辑上的一组操作,要么都执行,要么都不执行. 事务最经典也经常被拿出来说例子就是转账了 ...
- 一文带你你搞懂索引如何优化!!!
前言 索引的相信大家都听说过,但是真正会用的又有几人?平时工作中写SQL真的会考虑到这条SQL如何能够用上索引,如何能够提升执行效率? 此篇文章详细的讲述了索引优化的几个原则,只要在工作中能够随时应用 ...
- 一文让你彻底搞懂浏览器的渲染流程
hello,大家好.上次为大家介绍了重排和重绘的一些内容,其中涉及到的浏览器渲染流程自己就没有在上篇博文详细介绍了.今天我们就来好好唠唠浏览器的整个渲染流程. 首先,我们知道,一个页面通常由三个部分组 ...
最新文章
- h5仿微信聊天(高仿版)、微信聊天表情|对话框|编辑器
- 医院计算机网络安全宣教,医院网络安全及解决方案
- JS 原型链 prototypt 和隐式原型 _proto_
- notepad++ 远程连接阿里云服务器
- foreach循环符合就不往下走了_柴油发电机组冷却液循环故障解决方法
- 《Python Cookbook 3rd》笔记(3.10):矩阵与线性代数运算
- 使用case语句的3个诀窍
- Java对象的serialVersionUID在序列化和反序列化的用途
- wget整站抓取、网站抓取功能
- Flink catalog简单使用
- EasyRecovery14永久免费版密钥电脑硬盘恢复教程
- 计算年龄:DATEDIF函数
- Mac python 安装信息安全,Pycrypto 出现,C compiler cannot create executablesC编辑器不能创建可执行文件
- 如何规避适配风险?以《乱世王者》为例,探秘手游兼容性测试之路
- [RTOS]rtthread,freeRTOS,uCOS等系统简单对比
- App测试分类总结及方法
- 开始闭关修炼 冥思微软之大未来
- Docker 的使用和部署(Daocloud)
- 视频教程-FPS游戏逆向与安全+UE4引擎基础详解-其他
- 【自学笔记】天地图添加标注