引言
理解 C 语言中const修饰的两种指针(常指针、常目标指针)的核心区别 ------ 指针指向和指向地址数值的修改权限,同时搞懂malloc和calloc申请堆内存的原理、内存解释方式、指针偏移规则,以及两者在内存分配形式上的差异(精细字节分配 vs 分块组合分配)。本文会基于你的笔记,拆解核心规则、补充实战示例,帮你彻底吃透这些关键知识点。
一、const 型指针:常指针 vs 常目标指针
指针有两个核心功能:① 存储地址("指向" 某块内存);② 通过解引用修改指向地址的数值。const修饰指针的本质是限制其中一个功能,分为两种核心类型:
1. 常指针(const 修饰指针变量本身)
-
语法 :
类型 * const 指针名 = 初始地址;(const在*右侧,直接修饰指针变量); -
核心规则:不能修改指针的 "指向"(功能①被限制),但可以修改指针指向地址的数值(功能②可用);
-
关键要求:必须在定义时初始化 ------ 因为指针指向一旦确定就无法修改,未初始化的话指针无有效指向,后续也无法赋值;
-
实战示例:
#include <stdio.h>
int main() {
int a = 10, b = 20;
// 常指针:定义时必须初始化,指向a的地址
int * const p = &a;// ✅ 允许:修改指向地址的数值(功能②可用) *p = 100; printf("a的值:%d\n", a); // 输出100 // ❌ 禁止:修改指针的指向(功能①被限制),编译报错 // p = &b; return 0;}
2. 常目标指针(const 修饰指针指向的目标)
-
语法 :
const 类型 * 指针名;或类型 const * 指针名;(const在*左侧,修饰指针指向的数值); -
核心规则:不能修改指针指向地址的数值(功能②被限制),但可以自由修改指针的 "指向"(功能①可用);
-
关键要求:无需强制初始化(因为指针指向可随时修改);
-
实战示例:
#include <stdio.h>
int main() {
int a = 10, b = 20;
// 常目标指针:无需初始化,定义后可改指向
const int *p;// ✅ 允许:修改指针的指向(功能①可用) p = &a; printf("a的地址:%p\n", p); // 输出a的地址 p = &b; printf("b的地址:%p\n", p); // 输出b的地址 // ❌ 禁止:修改指向地址的数值(功能②被限制),编译报错 // *p = 200; return 0;}
核心对比表(易混点梳理)
| 指针类型 | 语法示例 | 能否修改指针指向 | 能否修改指向地址的数值 | 初始化要求 |
|---|---|---|---|---|
| 普通指针 | int *p; |
能 | 能 | 无需 |
| 常指针 | int * const p = &a; |
不能 | 能 | 必须初始化 |
| 常目标指针 | const int *p; |
能 | 不能 | 无需 |
二、堆内存申请:malloc vs calloc
C 语言中局部变量存在栈内存(大小有限、函数结束后自动释放),而堆内存需要手动申请 / 释放 (free),malloc和calloc是最常用的堆内存申请函数,头文件均为#include <stdlib.h>。
1. malloc:申请指定字节数的堆内存
-
语法 :
void *malloc(size_t size);size:申请的字节数;- 返回值:成功返回堆内存首地址(
void*类型,需根据指针类型隐式 / 显式转换),失败返回NULL(必须检查!);
-
核心解释 :
int *p = malloc(100);:向系统申请 100 字节的堆内存,void*隐式转为int*;指针类型决定内存的解释方式 ------int*表示按 4 字节(int 型大小)来存储 / 读取数据;- 指针
p指向堆内存的首地址,int*指针的偏移单位是 4 字节(p+1偏移 4 字节,p+2偏移 8 字节); - 可通过指针偏移向内存存数据(需避免越界);
-
实战示例:
#include <stdio.h>
#include <stdlib.h>int main() {
// 申请100字节堆内存(可存25个int型数据:100/4=25)
int *p = malloc(100);
if (p == NULL) { // 必须检查malloc是否成功(避免空指针操作)
perror("malloc failed"); // 打印错误原因
return 1;
}// 向堆内存存数据:偏移指针赋值 for (int i = 0; i < 25; i++) { *(p + i) = i + 1; // p+i偏移4*i字节,赋值1~25 } // 打印前5个数据验证 for (int i = 0; i < 5; i++) { printf("p[%d] = %d\n", i, *(p + i)); // 输出1、2、3、4、5 } free(p); // 释放堆内存(避免内存泄漏) p = NULL; // 置空指针,防止野指针 return 0;}
2. calloc:分块申请堆内存(自动初始化 0)
-
语法 :
void *calloc(size_t nmemb, size_t size);nmemb:内存块的数量;size:每块内存的字节数;- 返回值:成功返回首地址(
void*),失败返回NULL;
-
核心解释 (你的笔记重点):
calloc(25, 4);:申请 25 块、每块 4 字节的内存,总计25×4=100字节(和malloc(100)总大小相同);- 核心区别:
calloc会将申请的内存全部初始化为 0 ,而malloc申请的内存是 "脏数据"(随机值); - 适合场景:需要初始化的数组、结构体(无需手动
memset清零);
-
实战示例:
#include <stdio.h>
#include <stdlib.h>int main() {
// 申请25块,每块4字节(总计100字节),自动初始化0
int *q = calloc(25, 4);
if (q == NULL) {
perror("calloc failed");
return 1;
}// 打印前5个数据(未赋值,默认0) for (int i = 0; i < 5; i++) { printf("q[%d] = %d\n", i, *(q + i)); // 输出0、0、0、0、0 } // 赋值后验证 *(q + 0) = 10; printf("q[0] = %d\n", *q); // 输出10 free(q); // 释放内存 q = NULL; return 0;}
malloc vs calloc 核心对比
| 特性 | malloc | calloc |
|---|---|---|
| 参数形式 | 直接指定总字节数 | 块数 + 每块字节数 |
| 内存初始化 | 未初始化(脏数据) | 自动初始化为 0 |
| 适用场景 | 无需初始化的内存 | 需要清零的数组 / 结构体 |
| 总大小计算 | malloc(100) |
calloc(25,4)(25×4=100) |
总结
关键点回顾
- const 型指针:
- 常指针(
int * const p):指针指向不可改,指向地址的数值可改,必须初始化; - 常目标指针(
const int *p):指向地址的数值不可改,指针指向可改,无需强制初始化;
- 常指针(
- 堆内存申请:
malloc:申请指定字节数的堆内存,未初始化,指针类型决定内存解释方式(如int*按 4 字节偏移);calloc:分块申请堆内存(块数 × 每块大小),自动初始化 0,适合需要清零的场景;- 两者申请的内存均需用
free释放,释放后指针需置NULL,避免内存泄漏和野指针。