C 语言笔记: const 指针 + 堆内存申请

引言

理解 C 语言中const修饰的两种指针(常指针、常目标指针)的核心区别 ------ 指针指向和指向地址数值的修改权限,同时搞懂malloccalloc申请堆内存的原理、内存解释方式、指针偏移规则,以及两者在内存分配形式上的差异(精细字节分配 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),malloccalloc是最常用的堆内存申请函数,头文件均为#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)

总结

关键点回顾

  1. const 型指针:
    • 常指针(int * const p):指针指向不可改,指向地址的数值可改,必须初始化
    • 常目标指针(const int *p):指向地址的数值不可改,指针指向可改,无需强制初始化;
  2. 堆内存申请:
    • malloc:申请指定字节数的堆内存,未初始化,指针类型决定内存解释方式(如int*按 4 字节偏移);
    • calloc:分块申请堆内存(块数 × 每块大小),自动初始化 0,适合需要清零的场景;
    • 两者申请的内存均需用free释放,释放后指针需置NULL,避免内存泄漏和野指针。
相关推荐
wgslucky2 小时前
sm2 js加密,java服务器端解密
java·开发语言·javascript
dyyx1112 小时前
C++编译期数据结构
开发语言·c++·算法
曼巴UE52 小时前
UE C++ 组件 非构造函数创建的技巧
开发语言·c++
掘根2 小时前
【jsonRpc项目】服务端的RpcRouter模块
开发语言·qt
小白学大数据2 小时前
链家二手房数据爬取、聚类分析与可视化展示实践
开发语言·爬虫·python
杜子不疼.2 小时前
【Linux】基础IO(四):用户缓冲区深度解析
linux·运维·服务器·开发语言
梦想的旅途22 小时前
企业微信API自动化高效开发的实战指南
开发语言·python
2301_790300962 小时前
C++中的观察者模式实战
开发语言·c++·算法