深度理解指针与内存

指针的概念

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

地址的概念

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

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

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

|--------|------------|-------------|
| 内存地址 | 字节内容(十六进制) | 说明 |
| 0x1000 | 0x01 | 最低有效字节(LSB) |
| 0x1001 | 0x00 | |
| 0x1002 | 0x00 | |
| 0x1003 | 0x00 | 最高有效字节(MSB) |

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

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

|--------|-------|-------------|
| 地址 | 内容(值) | 说明 |
| 0x1000 | 1 | a[0](首元素) |
| 0x1004 | 2 | a[1] |
| 0x1008 | 3 | a[2] |
| 0x100C | 4 | a[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.解引用时,访问多少字节的数据

例如:

cpp 复制代码
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 | 必须与分配函数成对使用 |

相关推荐
CodeWithMe36 分钟前
【C/C++】跟我一起学_C++同步机制效率对比与优化策略
c语言·c++
Susea&1 小时前
趣味编程:四叶草
c语言·开发语言·c++·技术美术
xueyinan2 小时前
小刚说C语言刷题—1058 - 求出100至999范围内的所有水仙花数
c语言
航Hang*2 小时前
C PRIMER PLUS——第6-2节:二维数组与多维数组
c语言·开发语言·经验分享·程序人生·算法·学习方法·visual studio
Dovis(誓平步青云)3 小时前
精讲C++四大核心特性:内联函数加速原理、auto智能推导、范围for循环与空指针进阶
c语言·开发语言·c++·笔记·算法·学习方法
jz_ddk3 小时前
[学习]RTKLib详解:convkml.c、convrnx.c与geoid.c
c语言·开发语言·学习
可乐鸡翅好好吃9 小时前
not a genuine st device abort connection的问题
c语言·stm32·单片机·keil
C++实习生12 小时前
powerbuilder9.0中文版
c语言·c++
keepDXRcuriosity15 小时前
动态规划详解及 C/C++ 示例
c语言·c++·动态规划
YuforiaCode17 小时前
第十一届蓝桥杯 2020 C/C++组 蛇形填数
c语言·c++·蓝桥杯