news 2026/10/24 2:22:46

指针核心知识——从内存地址到多级指针

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
指针核心知识——从内存地址到多级指针

目录

  • 一:内存
  • 二:计算机常见单位
  • 三:内存单元
  • 四:指针变量和地址
  • 五:指针运算
  • 六:传值调用与传址调用
  • 七:二级指针
  • 八:总结

引言

本文面向刚接触 C 语言的初学者,聚焦一个常见困惑:变量在内存中究竟如何存放,指针又为何能"指向"数据。文章从内存与地址的基本概念讲起,逐步拆解指针变量、指针类型、指针运算、传值/传址调用与二级指针,帮你建立一套可落地的指针认知框架,读懂并写出正确的指针代码。

TL;DR

  • 内存被划分为以字节为单位的单元,每个单元有唯一编号,这个编号就是地址,也就是指针。
  • 指针变量是专门存放地址的变量,int* pa = &a; 中 * 表示它是指针,int 表示它指向的数据类型。
  • 指针类型决定解引用的权限(一次操作几个字节)和+/-整数时的步长。
  • 指针大小与类型无关,只与平台有关:x86 为 4 字节,x64 为 8 字节。
  • 想在函数内部修改外部变量,必须用传址调用(传地址),这是指针最实用的场景之一。

一:内存

计算机上CPU(中央处理器)在处理数据时,需要的数据是在内存中读取的,处理后的数据也会放回内存中

  • 高效内存管理:
    • 把内存划分为一个个内存单元,每个内存单元的大小取1个字节,然后给每一个内存单元一个编号(类似于房间编号)

二:计算机常见单位

一个比特位可以存储一个二进制的位1或位0

bit(比特位)计算机中的最小的数据/信息单位
Byte(字节)1 Byte = 8 bit
KB1 KB = 1024Byte
MB1 MB = 1024 KB
GB1 GB = 1024 MB
TB1 TB = 1024 GB
PB1 PB = 1024 TB

1024 = 2^10

三:内存单元

  • 计算机中,把内存分为若干个有1个字节大小的内存单元
    • 每个内存单元相当于每个学生宿舍
    • 一个内存单元 = 1 Byte = 8 bit = 8人间
    • 一个bit = 一个室友
    • 每个内存单元都有一个编号 = 宿舍门牌号
      • 有了这个内存单元的编号,CPU就可以快速找到一个内存空间
  • 生活中我们把门牌号称为地址;在计算机内存中把内存单元的编号也称为地址
    • 内存单元的编号 = 地址 = 指针
  • 补充知识:
    • 实际上我们编写的运行在x86架构下的计算机上的C语言程序,不会直接访问物理内存
    • 程序运行时为变量分配的地址,在程序调试过程中,我们看到的地址都是虚拟地址;虚拟地址不是物理地址,虚拟地址是虚拟地址空间中的地址
      • 虚拟地址空间:操作系统(eg:Windows地址)为每个程序提供的一个假想的,独占的,连续的内存空间
      • 程序中的虚拟地址,会由CPU的内存管理单元(MMU)映射到物理内存中,其实程序最终还是会访问物理内存的,这个由虚拟地址换成物理地址的过程地址转换/地址映射
    • 能直接访问物理内存的情况(非常特殊的领域):
      • 1.嵌入式系统:没有MMU(叫单片机或MCU,如ARM Cortex—M系列,Arduino),C程序直接使用物理地址,此时,编译器和程序员需要亲自管理整个内存布局
      • 2.操作系统内核:内核拥有最高权限,可直接访问物理地址

四:指针变量和地址

1.取地址操作符(&)

&:取出内存中数据(变量、数组等)的地址。
& 取出的地址是该数据中第一个字节(地址较小的字节)的地址。

2.指针变量

指针变量是用来专门存放地址(指针)的一个变量

通过&拿到的地址是一个数值,这个数值有时也需要存起来,方便后续使用,那么这个地址就要存放在指针变量中

指针变量是个单独的变量类型,具体写法就是在基本变量类型后加——*

I.指针类型的拆解

inta=0;int*pa=&a;//type * pointer_name = &variable_name//指针指向的对象类型 *说明该变量是指针变量 指针名字 要存的地址

II.指针变量的创建与初始化

//eg:int*pa=&a;//创建与初始化
  • 指针变量也是一种变量,这种变量就是专门用来存地址的,存放在指针变量里的值都会被理解成地址
#include<stdio.h>int*p=NULL
  • 若创建一个指针变量时,不知道给指针赋什么值或暂时不需要指向变量,可用NULL来初始化
    • NULL是C语言中定义的一个标识符常量,值是0,0也是地址,这个地址是无法使用的,读写该地址会报错
    • 一个指针变量被初始化为NULL,意思是这个指针变量暂时不指向任何有效的内存位置,等后续使用时的时候,可以给赋值新的地址
    • NULL的使用要包含头文件<stdio.h>
      • NULL —— 0
      • \0 —— 0
      • 0 —— 0
      • ‘0’ —— 48
  • 若指针变量未初始化
    • 1.全局:默认会初始化为0(NULL指针)
    • 2.局部:野指针(值是未知的),强制使用时,野指针会导致非法访问内存,程序崩溃

III.解引用操作符(*[单目])

只要拿到了地址(指针),就可以通过地址(指针)找到它指向的对象。要完成这类操作,就必须学习一个操作符——解引用操作符(间接访问操作符)*。

inta=;int*pa=&a;printf("%d\n",a);//10——aprintf("%d\n",*pa);//10——*pa——a//解应用,*pa相当于a*pa=100;//a = 100printf("%d\n",a);//100——aprintf("%d\n",*pa);//100——*pa——a

IV.指针变量大小

  • C语言标准并未规定指针变量的大小(长度)
    • x64下:都是8字节
    • x86下:都是4字节
      变量长度与类型无关
  • 指针变量的大小(指针类型的长度)是取决于数据类型的
    • 常见数据类型
模型\类型intlonglong longpointer(指针)操作系统
LP3316位32位64位32位Win16 API
ILP3232位32位64位32位Win32 API,Unix及Unix系统
LLP6432位32位64位64位Win64 API
LP6432位64位64位64位Unix及Unix系统
32位平台下地址是32个bit位,指针变量大小是4个字节 64位平台下地址是64个bit位,指针变量大小是8个字节
  • 注:指针变量的大小与类型无关,只要是指针类型的变量,在相同平台下,大小都是相同的
    • 指针变量是用来存放地址的
    • 地址的存放需多大空间,指针变量的长度就是多大

V.指针类型的意义

【1】指针解引用

指针类型决定了对指针解引用的时候有多大的权限(一次操作几个字节)

char*操作1个字节
short*操作2个字节
int*操作4个字节

转换不同类型的方法

inta=1;char*pa=(char*)&a;
【2】指针+,-整数
  • 指针+1,其实就是跳过1个指针指向的元素,所以指针+n,就是跳过n个指针指向的元素
  • 指针可以+n,也可以-n
intn=10;char*pc=(char*)&n;int*pi=&n;//&n = pc = pi//n = *pc = *pipc+1//= &n + 1pi+1//= &i + 4
  • 指针的类型决定了指针向前或向后走一步移动多大(距离),指针+,-整数,让指针可以在内存中向前或向后移动,为使用指针访问内存打好了基础
【3】void* 指针
  • void* 可理解为无具体类型的指针(泛型指针),这种类型的指针可以用来接收任意类型的地址(不会弹警告)
    • 局限性:void*类型的指针不能进行解引用和指针的+,-整数运算
  • 后续应用:
    • 一般void*类型的指针是使用在函数参数的部分,用来接收不同类型数据的地址,这样设计可以实现泛型编程的效果,使得一个函数来处理多种类型的数据

五:指针运算

指针+/-整数,指针-指针,指针关系运算

学习指针—>用指针管理内存(读内存中数据;把数据写进内存)

1.指针+/-整数

I.指针访问一维数组

因为数组在内存中是连续存放的,故只需要知道第一个元素的地址,就能顺藤摸瓜找到后面的所有元素

  • 以int数组为例
数组下标指针
10p
21p+1
32p+2
43p+3
54p+4

补充:指针与数组名的区别

初学时很容易把数组名当成指针,二者在「访问元素」上确实很像,但在类型和语义上并不等价。下表从五个维度做个对比(以 int arr[5]; int* p = arr; 为例):

对比维度指针 p数组名 arr
本质一个独立的变量,自身占内存,里面存放的是地址数组首元素的地址,是数组这种类型的「名字」,不是独立变量
是否可赋值可以,p = p + 1;、p = &arr[2]; 都合法不可以,arr = …会编译报错,数组名是常量地址
sizeof 结果与平台有关:x86 为 4,x64 为 8整个数组的字节数:sizeof(arr) == 5 * sizeof(int)
+1 的含义指针向后移动一个元素(int* 即 4 字节)同样跳过 4 字节,但 &arr + 1 跳过的是整个数组
能否取地址可以,&p 得到指针变量自身的地址(二级指针)可以,&arr 的类型是 int(*)[5],指向整个数组

一句话概括:数组名在大多数表达式中会「退化」成指向首元素的指针,但它本身不是指针变量——它不能被赋值,sizeof 得到的是整个数组的大小。也正因为如此,函数形参写成 int arr[] 时,实际接收到的仍是一个指针,数组长度信息在传参时就丢失了。

II.模拟实现strlen函数

size_tstrlen1(char*s){intcount=0;while(*s){//原因:'\0'ASCII码值为0count++;s++;}returncount;}

2.指针 - 指针

指针1 + 整数n = 指针2(向后跳n个元素后的地址)
指针2 - 整数n = 指针1(向前跳n个元素后的地址)
指针2 - 指针1 = 整数n(指针1 - 指针2 = -n)

  • 指针 - 指针结果的绝对值是两个 指针间原数的个数
  • 两个指针相减运算的前提—>两个指针指向同一块内存,否则无意义(eg:arr)
  • 注:指针相减会自动除以sizeof(type)

练习:模拟strlen

size_tstrlen2(char*s){char*str=s;//起始位置char*end=s;//寻\0while(*end){end++;}returnend-str;}

3.指针的相关运算

地址的本质是数值,数值可以比较大小,所以也就满足关系运算
现在以数组为例(连续地址【arr】由低到高运算)

#include<stdio.h>intmain(){intarr[]={1,2,3,4,5,6,7,8,9,10};int*p=&arr[0];intsize=sizeof(arr)/sizeof(arr[0]);while(p<&arr[size]){//&arr[0~9] < &arr[10]printf("%d",*p);//虽然arr[10]越界了,但仍能指向(越界危险的原因)p++;//指向的是arr[9]的下一个int大小内存的地址}return0;}

六:传值调用与传址调用

一类必须要用到指针的就是传址调用——eg:用函数交换两个数的值

  • 传值调用:
    • 调用函数时,把实参的值复制给形参
    • 形参和实参是两块独立内存
    • 函数内修改形参,不影响外部实参
//正常写intmain(){inta=10;intb=20;intz=0;//赋值时会把变量覆盖,因此要引入临时变量z=a;a=b;b=z;return0;}//若想用函数voidswap(inta,intb){intz=0;z=a;//逻辑无问题,但对形参的修改不影响实参a=b;//注:形参与实参的地址也是不同的,形参有单独的地址b=z;}
  • 传址调用
    • 把实参的地址传给形参,形参通常是指针
    • 函数通过指针解引用 *p 可以修改外部实参
    • 严格来说,C 语言传地址的本质仍然是传值——传的是地址这个值
voidswap2(int*p1,int*p2){intz=0;z=*p1;*p1=*p2;*p2=z;}intmain(){inta=10;intb=20;swap2(&a,&b);return0;}
  • 结论:
    • 传值调用时,实参传递给形参时,形参会单独创建一份临时空间来接收实参,对形参的修改不影响实参
    • 传址调用时可以让函数和主调函数之间建立真正的联系,在函数内部可以修改主调函数中的变量
    • 若是函数中只是需要主调函数中的变量值来完成计算,就采用——传值调用
    • 若是函数内部要修改主调函数中的变量的值,就采用——传址调用
      • 数组传值的本质——传址调用

七:二级指针

二级指针——存指针变量的地址(变量的地址的地址)

inta=10;//普通变量int*pa=&a;//一级指针int**ppa=&pa;//二级指针int***pppa=&ppa//三级指针//以此类推

I.分析

inta=10;int*pa=&a;//指针指向的类型——int 表明是指针变量int**ppa=&pa;//指针指向的类型——int* 表明是指针变量

II.解引用应用

  • 指针相当于加密锁
  • 多级指针相当于多级加密锁
#include<stdio.h>intmain(){inta=10;int*pa=&a;int**ppa=&pa;int***pppa=&ppa;printf("%d\n",a);//打印10printf("%d\n",*pa);//打印10printf("%d\n",**ppa);//打印10printf("%d\n",***pppa);//打印10printf("%d\n",****ppppa);//打印10}

八:总结

  • 内存被划分为以字节为单位的单元,每个单元有唯一编号,这个编号就是地址,也就是指针;内存单元的编号 = 地址 = 指针。
  • & 取出的是变量所占内存中第一个字节(地址较小字节)的地址,把它存进变量,就得到了指针变量。
  • 指针类型决定两件事:解引用时一次能操作几个字节,以及 +/- 整数时一步跨过多少字节。
  • 指针变量的大小与所指类型无关,只与平台有关:x86 为 4 字节,x64 为 8 字节。
  • 指针运算建立在「数组在内存中连续存放」之上:p + n 跳过 n 个元素,同一块内存中的两个指针相减得到元素个数。
  • 传值调用只复制实参的值,函数内改形参不影响外部;想在函数内部修改外部变量,必须传址调用(传地址)。
  • 二级指针存放的是一级指针变量的地址,int** ppa = &pa; 中 * 的层数对应解引用的层数,多一级指针就多一次解引用。
  • 学习路径建议:先吃透取地址 & 与解引用 * 这对互逆操作,再动手练指针 +/- 整数与指针相减,最后理解传址调用与二级指针,把「用指针管理内存」这件事真正跑通。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/24 2:22:44

Flutter 3.47 鸿蒙三方库适配实战:从 0 到 1 处理 native_file_preview

Flutter 3.47 鸿蒙三方库适配实战&#xff1a;从 0 到 1 处理 native_file_preview 调用系统默认应用预览沙箱内的文件。 本文以 native_file_preview 1.1.0 为例&#xff0c;记录我如何保留原有 Dart API、补齐 Flutter OH 插件入口、在鸿蒙端接入真实系统能力&#xff0c;并…

作者头像 李华
网站建设 2026/10/24 2:22:38

DDD 领域驱动设计(04)· 战术设计(上):实体、值对象与聚合

DDD-04-战术设计-实体值对象与聚合 进入限界上下文内部&#xff0c;开始真正的建模。本篇讲三块基石&#xff1a;实体、值对象、聚合。其中聚合是 DDD 战术设计最核心的模式——它就是一致性的边界&#xff0c;也是事务的边界。而"聚合该画多大"&#xff0c;是我见过…

作者头像 李华
网站建设 2026/10/24 2:22:33

2026年AI岗位激增14倍,传统程序员如何抓住最后转型窗口期?

2026年AI岗位激增14倍&#xff0c;传统程序员入门级岗位断崖式下跌。就业市场分化严重&#xff0c;AI工程师薪资远超传统程序员。转型窗口期就在现在&#xff0c;学历门槛尚可&#xff0c;企业还在抢人。程序员需根据自身情况选择智能体开发、AI应用开发或算法工程师等方向&…

作者头像 李华
网站建设 2026/10/24 2:22:22

力扣刷题小集

LeetCode.347前k个高频元素思路&#xff08;1&#xff09;统计频次&#xff1a;用unordered_map遍历nums原数组&#xff0c;key存数组元素值&#xff0c;value存该元素出现次数。&#xff08;2&#xff09;维护大小为 k 的小顶堆(为什么选择小顶堆而不是大顶堆如下图)&#xff…

作者头像 李华
网站建设 2026/10/24 2:22:03

前端转全栈不再难!2026年必备技能,轻松拿回技术评审主动权

文章探讨了前端在技术评审中常感到的无力感&#xff0c;以及如何通过转型全栈来克服这种困境。文章指出&#xff0c;2026年的全栈开发门槛已大幅降低&#xff0c;得益于平台自动化和AI辅助&#xff0c;前端只需专注核心技能如HTTP、后端逻辑、数据库和接口设计&#xff0c;八到…

作者头像 李华
网站建设 2026/10/24 2:21:57

小白/程序员必看:收藏这份Agent评测指南,解锁大模型行动力!

本文探讨了传统大模型评测方法在Agent评估中的局限性&#xff0c;强调了评估Agent需关注行动过程而非仅看结果。美团Agent评测体系提出&#xff0c;应评估由模型、Prompt、工具等组成的复杂系统&#xff0c;通过轨迹评估和结果评估两类问题&#xff0c;确保Agent稳定上线。文章…

作者头像 李华