C 语言第 17 天学习笔记:从二级指针到内存布局的进阶指南

C语言进阶:从二级指针到内存布局的深度解析

一、指针进阶:二级指针的应用与特性

1.1 二级指针的定义与基本用法

二级指针(多重指针)用于存储一级指针的地址,需要两次次解引用才能访问原始数据。在实际开发中,二级指针是最常见的多重指针类型。

c 复制代码
int a = 10; // 普通变量,原始数据
int *p = &a; // 一级指针,指向a,一次解引用可获取a的值
printf("%d\n", *p); // 输出:10

int **w = &p; // 二级指针,指向p,两次解引用可获取a的值
printf("%d\n", **w); // 输出:10

int ***k = &w; // 三级指针,指向w,三次解引用可获取a的值
printf("%d\n", ***k); // 输出:10

1.2 二级指针的语法与特点

语法格式

c 复制代码
数据类型 **指针变量名 = 指针数组的数组名 | 一级指针的地址

核心特点

  1. 与指针数组的等效性

    二级指针与指针数组在某些场景下可以等效使用,但与二维数组不等效。

    c 复制代码
    // 指针数组示例
    int arr[] = {11, 22, 33};
    int *arr_[] = {&arr[0], &arr[1], &arr[2]};
    
    // 二级指针接收指针数组
    char *str[3] = {"abc", "aaa034", "12a12"};
    char **p = str; // p存储数组首地址,*p访问列地址,**p访问列元素
  2. 与二维数组的差异

    二维数组名是数组指针类型(如int (*)[3]),直接赋值给二级指针会导致类型不匹配:

    c 复制代码
    int arr[2][3] = {{1, 3, 5}, {11, 33, 55}};
    int (*p)[3] = arr; // 正确:数组指针指向二维数组
    int **k = arr; // 错误:类型不兼容(int(*)[3] 与 int**)

1.3 二级指针的解引用操作

字符型二级指针可直接遍历字符串数组,操作类似一维数组:

c 复制代码
#include <stdio.h>

void fun2() {
    char *arr[] = {"orange", "apple", "grape", "banana", "kiwi"};
    int len = sizeof(arr) / sizeof(arr[0]);

    char **p = arr; // 二级指针等价于指针数组

    for (int i = 0; i < len; i++) {
        printf("%s\n", *(p + i)); // 指针法访问
    }
}

其他类型二级指针需要两次解引用访问数据,常用于操作指针数组:

c 复制代码
#include <stdio.h>

int main() {
    int arr1[] = {11, 22, 33, 44, 55, 66};
    int *arr[] = {&arr1[0], &arr1[1], &arr1[2], &arr1[3], &arr1[4], &arr1[5]};
    int **p = arr; // 二级指针接收指针数组

    for(int i = 0; i < sizeof(arr)/sizeof(arr[0]); i++){
        printf("%-6d", **(p+i)); // 指针法访问
    }
}

二、main函数原型解析

main函数作为程序入口,有多种定义格式,其中标准写法为:

c 复制代码
int main(int argc, char *argv[]) { ... }
// 或等价形式
int main(int argc, char **argv) { ... }

2.1 参数含义与用法

-** argc :存储命令行参数的个数,默认值为1(程序名本身)
-
argv **:存储所有参数的字符串形式,是一个字符型指针数组

示例代码

c 复制代码
#include <stdio.h>

int main(int argc, char **argv) {
    printf("参数个数: %d\n", argc);
    
    for(int i = 0; i < argc; i++){
        printf("参数%d: %s\n", i, argv[i]);
    }
    return 0;
}

三、常量指针与指针常量

3.1 常量指针(指向常量的指针)

定义:指向常量数据的指针,指针指向的数据不可修改,但指针本身的指向可以改变。

语法

c 复制代码
const 数据类型 *变量名;
// 或
const 数据类型* 变量名;

示例

c 复制代码
int a = 10, b = 20;
const int *p = &a;
// *p = 30; // 错误:不能修改指向的数据
p = &b; // 正确:可以改变指向

3.2 指针常量(指针本身是常量)

定义:指针本身是常量,指向固定地址,但指向的数据可以修改。

语法

c 复制代码
数据类型* const 变量名;
// 或
数据类型 *const 变量名;

示例

c 复制代码
int a = 10, b = 20;
int* const p = &a;
*p = 30; // 正确:可以修改指向的数据
// p = &b; // 错误:不能改变指向

3.3 常量指针常量

定义:指针指向和指向的数据都不可改变。

语法

c 复制代码
const 数据类型* const 变量名;

示例

c 复制代码
int a = 10, b = 20;
const int* const p = &a;
// *p = 30; // 错误
// p = &b; // 错误

3.4 总结对比

类型 语法 指向可变 数据可变
常量指针 const int *p ✔️
指针常量 int *const p ✔️
常量指针常量 const int *const p

四、野指针、空指针与空悬指针

4.1 野指针

定义:指向无效内存区域的指针(未初始化、已释放或越界访问)。

产生场景

  1. 指针未初始化

    c 复制代码
    int *p; // 野指针
    printf("%d\n", *p); // 危险操作
  2. 指针指向已释放的内存

    c 复制代码
    int *p = malloc(sizeof(int));
    free(p);
    printf("%d\n", *p); // 危险操作
  3. 返回局部变量的地址

    c 复制代码
    int* fun() {
        int sum = 0;
        return &sum; // 危险:返回局部变量地址
    }

避免方法

  • 初始化指针为NULL
  • 释放内存后立即置为NULL
  • 避免返回局部变量地址
  • 使用前检查指针有效性

4.2 空指针

定义:值为NULL的指针,指向地址0x00000000(系统保留,不可访问)。

作用:明确表示指针当前不指向有效内存,用于指针初始化。

c 复制代码
int *p = NULL; // 初始化为空指针

4.3 空悬指针

定义:指针指向的内存已被释放,但未重新赋值,是野指针的一种特例。

c 复制代码
char *p = malloc(100);
free(p); // 释放内存后,p成为空悬指针

五、void与void*的区别

5.1 void类型

表示"无类型/空类型",用于函数返回类型或参数:

c 复制代码
void func(void); // 无返回值,无参数

5.2 void*类型(通用指针)

可指向任意类型数据,但需要强制类型转换后才能解引用:

c 复制代码
void* ptr = malloc(4); // 分配4字节内存

// 存储int类型
int *p = (int*)ptr;
*p = 10;

// 存储float类型
float* p1 = (float*)ptr;
*p1 = 12.5f;

注意void*只能与具体类型指针(int*double*等)之间进行转换。

六、C语言内存管理

6.1 进程内存布局

每个C语言进程拥有结构相同的虚拟内存,包含以下区域:

-** 栈(stack):存储环境变量、命令行参数、局部变量
-
堆(heap):动态内存区域,可由开发者管理
-
数据段 :包含.bss段(未初始化静态数据)、.data段(已初始化静态数据)、.rodata段(常量数据)
-
代码段 **:包含.text段(用户代码)、.init段(系统初始化代码)

6.2 各内存区域特性

栈内存

  • 空间有限,自动分配和释放
  • 函数调用时栈向下增长,函数退出时栈向上缩减

静态数据

  • 包括全局变量和static修饰的局部变量
  • 程序启动时分配,程序退出时释放
  • 未初始化时自动初始化为0

堆内存

  • 大小受限于物理内存
  • 从下往上增长
  • 需要开发者手动申请和释放
  • 相关API:malloc()calloc()realloc()free()

示例

c 复制代码
// 堆内存操作示例
int *p = malloc(sizeof(int)); // 申请内存
*p = 100; // 存储数据
free(p); // 释放内存
p = NULL; // 避免空悬指针

// 连续内存申请
double *k = calloc(3, sizeof(double)); // 已清零
k[0] = 0.618;
free(k);

七、总结

本文深入探讨了C语言中的二级指针、main函数原型、常量指针与指针常量、各类特殊指针以及内存布局等进阶知识。掌握这些概念对于理解C语言的内存管理机制、编写高效安全的代码具有重要意义。特别是内存管理部分,需要开发者手动管理堆内存,这既是C语言的灵活性所在,也是容易出现bug的地方,需要格外注意。

相关推荐
Asu520228 分钟前
思途spring学习0807
java·开发语言·spring boot·学习
胤祥矢量商铺2 小时前
菜鸟笔记007 [...c(e), ...d(i)]数组的新用法
c语言·开发语言·javascript·笔记·illustrator插件
泽虞2 小时前
C语言深度语法掌握笔记:底层机制,高级概念
java·c语言·笔记
董莉影2 小时前
学习嵌入式第二十二天
数据结构·学习·算法·链表
尘心不灭3 小时前
Spring Boot 项目代码笔记
spring boot·笔记·后端
橘色的喵3 小时前
嵌入式C语言编程:策略模式、状态模式和状态机的应用
c语言·状态模式·策略模式·状态机
人生游戏牛马NPC1号4 小时前
学习 Android (十六) 学习 OpenCV (一)
android·opencv·学习
爱吃生蚝的于勒4 小时前
一文学会c++继承 组合
java·c语言·开发语言·数据结构·c++·算法·蓝桥杯
ouliten4 小时前
cuda编程笔记(13)--使用CUB库实现基本功能
笔记·cuda