当前位置: 首页 > news >正文

深度理解指针与内存

指针的概念

指针是一个特殊的变量,它存储的是某块内存空间的地址值

地址的概念

每个数据都有自己的地址,每个地址存放一字节的数据。用下面的例子举例

int a[4]={1,2,3,4};

假设数组a的首地址是0x1000,则第一个元素 1 (元素 1 是int型,占4个字节)的存储方式如下:

内存地址字节内容(十六进制)说明
0x10000x01最低有效字节(LSB)
0x10010x00
0x10020x00
0x10030x00最高有效字节(MSB)

大部分计算机系统都采用小端存储模式,即数据低位字节放在低地址处,高位字节放在高地址处

数组中所有的元素在内存中的存储方式如下:

地址内容(值)说明
0x10001a[0](首元素)
0x10042a[1]
0x10083a[2]
0x100C4a[3]

数组名 a 其实就是数组首元素的地址(即 &a[0]),它本身不是一个变量,只是表示数组的起始地址

如果对 a 取地址(&a)得到的地址值与 a 相同,但类型不同:

  1. a 的类型是 int* (指向 int 的指针)

  2. &a 的类型是 int(*)[4](指向长度为4的数组的指针)

int *p = a;

指针变量 p 指向数组首元素,相当于 指针p 的值为 0x1000,就是数组的起始地址

指针的大小

32位的系统上,地址长度就是32位,也就是4个字节。所以一个指针变量就是4个字节。 char *p; sizeof(p) = 4;

取地址& 解引用*

&a 的结果是一个指针,指针的类型就是 a 的类型加上 * ,指针指向的类型是 a 的类型,指针指向的地址是 a 的地址。

*p 的结果是 指针p 所指向的内容,这个内容可以是一个数值、字符串、数组、指针。

注意:

指针的类型决定:

1.指针算术运算的步长,即 p+1 移动的字节数。

2.解引用时,访问多少字节的数据

例如:

int a[10]={0x0101,1,2,3,4,5,6,7,8,9};

int *p=a;
printf("%d %d",*p,*(p+4)); //打印257 4
printf("%d",*(char*)p); //打印1,(char*)类型强转只是告诉编译器:
                        //1.如何解释该地址处的数据(按一字节读取),从低位数据开始读
                        //2.指针算术的步长
char *p=a;
printf("%d %d",*p,*(p+4)); //打印1 1

指针 与 数组

char *p = "abcde";

p:指针指向字符串开头,也就是第一个字节a的地址,sizeof(p)=4

p+1:指针指向了第二个字节b的地址,sizeof(p+1)=4

*p:指针所指向的内容,也就是第一个字节a,sizeof(*p)=1 p[0]:同上

&p:对指针变量 p 取地址,sizeof(&p)=4

&p+1:指针变量 p 的地址的下一个地址,然后将地址往后移动4个字节的大小(这里+1的步长是sizeof(char*)=4字节),sizeof(&p+1)=4

&p[0]+1:先取第一个字节a的地址,然后将地址向后移动1个字节的大小(这里+1的步长是sizeof(char)=1字节),得到p[1]的地址,也就是字符b的地址。sizeof(&p[0]+1)=4

int arr[5] = {1,2,3,4,5};

arr:表示整个数组,sizeof(arr)=5

arr+0:表示首元素的地址,sizeof(arr+0)=4

*arr:arr本质上是指向数组开头的指针,也就是指向第一个元素,*arr则是对第一个元素解引用,所以*arr=1,sizeof(*arr)=1

arr[1]:arr的第二个元素

&arr:对数组取地址,sizeof(&arr)=4

&arr+1:数组arr的地址的下一个地址,然后将地址往后移动4个字节的大小(这里+1的步长是sizeof(int*)=4字节),sizeof(&arr+1)=4

&arr[0]+1:取出第一个元素的地址,然后将地址向后移动4个字节的大小(这里+1的步长是sizeof(int)=4字节),sizeof(&arr)=4

内存的三种分配方式

1.静态内存分配(全局变量、静态变量、常量)

特点:

  • 内存的分配和释放由编译器在编译阶段完成

  • 分配的内存在程序整个生命周期内存在(直到程序结束才释放)。

优点:

  • 无需手动管理,生命周期明确。

缺点:

  • 内存固定,无法动态调整大小。

  • 可能浪费内存(未使用的静态内存无法释放)。

2.栈内存分配(函数内的局部变量、函数参数)

特点:

  • 内存的分配和释放由编译器自动管理(通过函数调用栈)。

  • 分配的内存随函数调用结束自动释放(局部变量的生命周期)。

优点:

  • 分配速度快(仅移动栈指针)。

  • 无需手动管理,内存自动回收。

缺点:

  • 内存大小固定(栈空间有限,默认几MB)。

  • 大对象可能导致栈溢出(如大数组 int arr[1000000])。

3.堆内存分配(动态大小的数据结构(链表、数组)、需要长期存在或跨函数使用的数据)

特点:

  • 内存的分配和释放由程序员手动控制(通过 malloc, calloc, free 等函数)。

  • 内存生命周期完全由代码逻辑决定

优点:

  • 内存大小灵活(按需分配)。

  • 生命周期可控。

缺点:

  • 分配速度较慢(需查找可用内存块)。

  • 需手动管理,易导致内存泄漏(忘记 free)或野指针。

常用的动态内存分配函数:

  • malloc:用于分配指定大小的内存块,返回指向该内存块起始地址的指针。如果分配失败,返回空指针(NULL)。

  • calloc:与malloc类似,但它还会将分配的内存块初始化为零。函数原型:void* calloc(size_t num_elements, size_t element_size);。

  • realloc:用于重新分配已经分配的内存块大小,可以扩大或缩小内存块。函数原型:void* realloc(void* ptr, size_t new_size);。

  • free:用于释放动态分配的内存块,将该内存块返回给堆,以便其他程序可以使用。函数原型:void free(void* ptr);。

函数功能初始化特殊性
malloc分配未初始化内存基础分配函数
calloc分配并初始化为零全零适合数组初始化
realloc调整已分配内存的大小可能移动内存块
free释放内存N/A必须与分配函数成对使用

相关文章:

  • 使用数据库和缓存的时候,是如何解决数据不一致的问题的?
  • android edittext 防止输入多个小数点或负号
  • 开发环境搭建-05.后端环境搭建-前后端联调-通过断点调试熟悉项目代码特点
  • 每日一题----------枚举的注意事项和细节
  • C/C++蓝桥杯算法真题打卡(Day3)
  • 江科大51单片机笔记【11】AT24C02(I2C总线)
  • 算法·搜索
  • 数据集笔记 LTA Traffic Count
  • VS2019,VCPKG - 为VS2019添加VCPKG
  • LInux 文件系统
  • Spring Boot 缓存最佳实践:从基础到生产的完整指南
  • 实时读取另一个串口发来的返回数据
  • Android 低功率蓝牙之BluetoothGattDescriptor详解
  • 装饰器模式--RequestWrapper、请求流request无法被重复读取
  • 基于GeoTools的GIS专题图自适应边界及高宽等比例生成实践
  • 【JavaSE-8】面向对象
  • 运动控制卡--固高实用
  • 软件信息安全性测试流程有哪些?专业软件测评服务机构分享
  • MySQL自定义序列数的实现
  • 【AIGC系列】6:HunyuanVideo视频生成模型部署和代码分析
  • 美最高法允许政府撤销委内瑞拉移民临时保护身份,35万人或将被驱逐
  • 深圳南山法院回应“执行款未到账”:张核子公司申请的执行异议成立
  • 中信银行资产管理业务中心原副总裁罗金辉一审被控受贿超4437万
  • 这个东西每道菜里都有,却可能让你得一身病,做好这些能避免
  • 外交部发言人就第78届世界卫生大会拒绝涉台提案发表谈话
  • 探月工程鹊桥二号中继星取得阶段性进展