10   学习指针

1  指针核心定义与本质

1.1 指针与指针变量

        1、指针即地址,指针变量是存放地址的变量,其大小与操作系统位数相关:64 位系统中占 8 字节,32 位系统中占 4 字节。

        2、指针的核心功能是通过地址间接访问目标变量,实现灵活的内存操作。

1.2 指针类型的关键作用

        1、承载地址信息与内存解析规则,基类型必须与指向数据类型严格一致。

        2、决定内存访问步长:char*偏移 1 字节,int*偏移 4 字节,double*偏移 8 字节。

1.3 核心要求

        指针变量本身及指向的内存空间必须确定,严禁使用野指针(未初始化)或悬垂指针(指向已释放空间)。

2  指针的基本操作规则

2.1 地址获取与传递

        1、通过&(取地址运算符)可获取变量在内存空间中的首地址,只有左值(可被赋值的变量)能进行&操作,常量、表达式和寄存器变量(register修饰)不能。

        2、例如int Num = 0; &Num可得到Num在内存中 4 字节空间的首地址,其类型为int *(由基类型int升级而来)。

        3、在函数传参中,使用&传递变量地址,能实现在被调函数中修改主调函数的变量值,突破值传递 “单向传递” 的限制(典型应用:函数返回多个值)。

2.2 指针访问与解引用

        1、通过*运算符可获得指针指向的空间或对应空间中的值,*连接的内容必须为指针类型(否则编译报错)。

        2、若直接使用*对应的表达式,其值为指针指向空间中的值,类型为指针类型降级后的基类型,如int *p; *p的类型为int

        3、变量有两种访问形式:直接访问(通过变量名,如Num = 5)和间接访问(通过指针,如*p = 5)。

3  字符串函数与指针应用(基于指针实现的库函数)

        字符串操作函数的底层实现依赖指针对字符数组的遍历与修改,核心是通过char*指针访问内存中的字符序列(以'\0'结尾)。

函数 功能 核心实现逻辑 关键点
strlen 计算字符串长度 指针遍历至'\0',返回指针差值 const char*保证只读,不包含'\0'
strcpy 复制字符串 逐个字符复制(含'\0' 需保证目标空间足够,禁止地址重叠
strcat 拼接字符串 先移动指针至目标末尾,再复制源字符串 目标需足够大,源和目标均需'\0'结尾
strcmp 比较字符串 按 ASCII 值逐个比较,遇不同字符返回差值 非长度比较,一旦不同立即返回

3.1 strlen:计算字符串长度

        1、功能:返回字符串中'\0'前的字符个数(不包含'\0')。

        2、底层实现:

size_t strlen(const char *str) 
{
    const char *p = str; // 用指针遍历字符串
    while (*p != '\0') 
    { // 遍历至结束符
        p++;
    }
    return p - str; // 指针差值即长度(字符个数)
}

        3、关键点:const char*保证不修改原字符串;通过指针自增遍历,通过指针相减计算长度。

3.2 strcpy:字符串复制

        1、功能:将源字符串(src)复制到目标空间(dest),包括'\0',返回目标地址。

        2、底层实现:

char *strcpy(char *dest, const char *src) 
{
    char *p = dest; // 保存目标首地址(用于返回)
    while ((*p++ = *src++) != '\0'); // 逐个复制字符,包括'\0'
    return dest;
}

        3、注意事项

                需保证dest空间足够大,否则会导致缓冲区溢出。

                源字符串必须以'\0'结尾,否则会复制随机数据。

                禁止源地址与目标地址重叠(如strcpy(a, a+1)会导致未定义行为)。

3.3 strcat:字符串拼接

        1、功能:将源字符串(src)追加到目标字符串(dest)的末尾(覆盖dest原有的'\0'),返回目标地址。

        2、底层实现:

char *strcat(char *dest, const char *src) 
{
    char *p = dest;
    // 1. 移动指针到dest的末尾('\0'位置)
    while (*p != '\0') 
    {
        p++;
    }
    // 2. 复制src到dest末尾,同strcpy逻辑
    while ((*p++ = *src++) != '\0');
    return dest;
}

        3、注意事项:

        dest必须有足够空间容纳拼接后的字符串。

        destsrc都必须以'\0'结尾。

3.4 strcmp:字符串比较

        1、功能:按 ASCII 值比较两个字符串,返回差值(0表示相等,正数表示str1大于str2,负数表示str1小于str2)。

        2、底层实现:

int strcmp(const char *str1, const char *str2) 
{
    // 遍历字符,直到遇到不同字符或'\0'
    while (*str1 != '\0' && *str2 != '\0' && *str1 == *str2) 
    {
        str1++;
        str2++;
    }
    // 返回对应字符的ASCII差值
    return (unsigned char)*str1 - (unsigned char)*str2;
}

        3、关键点:

                比较的是字符的 ASCII 值,而非字符串长度。

                一旦遇到不同字符立即返回差值,不继续比较后续字符。

4  指针偏移与内存访问

        1、指针偏移大小由基类型大小决定:char *偏移 1 字节,int *偏移 4 字节,double *偏移 8 字节,结构体指针偏移整个结构体大小。

        2、两个同类型指针相减的结果为地址间相差的数据类型元素个数(非字节数),例如int *p1 = a; int *p2 = a+3; p2-p1结果为 3(表示相差 3 个int元素)。

        3、指针不能与非指针类型运算,也不能跨类型相减(编译报错)。

5  野指针与空指针

        1、野指针成因:未经初始化的指针(如int *p;)、指向已释放空间的指针(如free(p);后未置空)、越界访问的指针(如数组越界)。

        2、空指针:指向内存地址0x0的指针,用NULL(本质为(void*)0)表示,该空间为系统保留的只读区域,对空指针解引用(*p = 10)会导致程序崩溃。

        3、预防野指针:未使用的指针初始化为NULL;释放内存后立即置空(free(p); p = NULL;);避免指针越界访问。

6  指针赋值与修改

        1、对指针变量本身赋值(如p = q;):改变指针的指向,使其指向新的地址。

        2、对指针解引用赋值(如*p = *q;):改变指针指向空间的值,指针指向不变。

        3、示例:int a=1, b=2; int *p=&a, *q=&b; p=q;p指向b*p结果为 2;*p=*q;a的值变为 2,p仍指向a

7  动态内存分配与管理

7.1 核心函数

函数 功能 特点
malloc(size_t size) 申请size字节的连续内存 未初始化,内容为随机值;返回void*,需强转;失败返回NULL
calloc(size_t n, size_t size) 申请nsize字节的连续内存 自动初始化为 0;效率略低于malloc
realloc(void *ptr, size_t size) 调整已分配内存的大小 可能在原地址扩展或重新分配;失败返回NULL,原内存不变
free(void *ptr) 释放动态分配的内存 仅释放ptr指向的空间,不改变指针值;不能重复释放或释放非动态内存

7.2 内存管理规则

        1、动态内存必须手动释放,否则导致内存泄漏(程序运行中内存占用持续增长)。

        2、释放后指针需置空(p = NULL;),避免成为野指针。

        3、申请内存后必须检查返回值是否为NULL(防止内存不足导致崩溃):

int *p = (int*)malloc(10*sizeof(int));
if (p == NULL) { /* 内存申请失败处理 */ }

        4、避免 “内存碎片”:频繁申请和释放小块内存会导致内存碎片,可通过内存池优化。

8  指向函数的指针与指针函数

类型 定义 特点
指针函数 返回指针的函数(类型* 函数名(参数) 不可返回局部变量地址,可返回动态内存、全局变量地址
函数指针 指向函数的指针(返回类型 (*指针名)(参数列表) 需匹配函数返回类型、参数类型和个数,用于回调函数(如qsort比较器)

8.1 指针函数

        1、定义:返回值为指针的函数,格式为类型 *函数名(参数列表)

        2、注意:不能返回局部变量的地址(局部变量在函数结束后释放,返回后成为野指针),可返回动态分配内存、全局变量或静态变量的地址。

        3、示例:

int *createArray(int n) 
{
    int *arr = (int*)malloc(n*sizeof(int));
    return arr; // 返回动态内存地址,需外部释放
}

8.2 函数指针

        1、定义:指向函数的指针,格式为返回类型 (*指针名)(参数类型列表),需严格匹配函数的返回类型、参数类型和个数。

        2、函数名本质是函数入口地址,可直接赋值给函数指针(无需&)。

        3、应用:实现回调函数(如排序函数qsort的比较器)、函数接口封装,降低模块耦合性。

        4、示例:

int add(int a, int b) 
{
    return a + b; 
}
int (*funcPtr)(int, int) = add; // 函数指针指向add
int result = funcPtr(3, 4); // 调用函数,结果为7

9  指针与数组的关系

9.1数组名的特殊性

        1、数组名是指向首元素的指针常量(不可修改指向),如int a[5]; a等价于&a[0],类型为int *

        2、例外情况:

    sizeof(数组名):获得数组总字节数(如int a[5]; sizeof(a) = 20)。

    &数组名:类型为指向整个数组的指针(如int (*)[5]),偏移量为整个数组大小。

               数组名作为sizeof参数或取地址时,不退化为首元素指针。

9.2 数组作为函数参数

        1、三种传递形式等价:int fun(int a[5]);int fun(int a[]);int fun(int *a);,函数内均按指针处理,丢失数组长度信息。

        2、必须显式传递数组长度:int fun(int *a, int len),避免越界访问。

9.3 字符数组与字符串

        1、字符串本质是'\0'结尾的字符数组,传参时可直接传递数组名(即char *指针)。

        2、遍历字符串:while (*p != '\0') { printf("%c", *p++); }

        3、字符串常量存储在只读区,不能通过指针修改(如char *p = "hello"; *p = 'H';会崩溃)。

10  const 指针

10.1 三种形式及特性

定义 含义 指针值是否可改 指向空间是否可改 必须初始化
const int *p; 或 int const *p; const 修饰*p
int *const p; const 修饰p
const int *const p; 或 int const *const p; const 修饰p*p

10.2 应用场景

    1、const int *p:保护指向的数据不被修改(如函数参数传递只读数据)。

    2、int *const p:确保指针始终指向同一变量(如硬件寄存器地址)。

    3、const int *const p:既固定指针指向,又保护数据(如常量配置参数)。

11  指针数组与数组指针

11.1 概念区分

类型 本质 定义形式 内存占用(64 位) 示例
指针数组 数组,元素为指针 int *a[5]; 5×8=40 字节 char *strs[] = {"apple", "banana"};
数组指针 指针,指向数组 int (*a)[5]; 8 字节 int (*p)[3] = &arr;arrint[3]数组)

11.2数组指针与二维数组

        1、二维数组名是指向第一行的数组指针,类型为int (*)[列数],如int a[2][3];a的类型为int (*)[3]

        2、访问元素的方式:a[i][j]*(a[i] + j)*(*(a + i) + j)(*(a + i))[j]

        3、遍历二维数组:

int a[2][3] = {{1,2,3}, {4,5,6}};
int (*p)[3] = a; // p指向第一行
for (int i=0; i<2; i++) 
{
    for (int j=0; j<3; j++) 
    {
        printf("%d ", *(*(p+i) + j));
    }
}

12  二级指针

12.1 定义与本质

        1、二级指针是指向一级指针变量的指针,格式为类型 **p,64 位系统中占 8 字节,用于存储一级指针的地址。

        2、示例:int a=10; int *p=&a; int **q=&p;q是二级指针,*q等价于p**q等价于a

12.2 使用场景

        1、函数内部修改外部指针的指向:

void allocMemory(int **p, int size) 
{
    *p = (int*)malloc(size); // 修改外部指针p的指向
}
int *arr;
allocMemory(&arr, 10*sizeof(int)); // 传递一级指针的地址

        2、指针数组传参:指针数组的数组名是二级指针,如char *strs[] = {"a", "b"};传参时类型为char **

        3、动态二维数组:int **arr = (int**)malloc(3*sizeof(int*));用于创建行长度可变的二维数组。

13  指针作为函数参数

13.1 传递方式对比

传递方式 特点 适用场景
值传递 形参是实参的副本,修改形参不影响实参 函数仅使用参数值,不修改原变量
地址传递(一级指针) 形参指向实参地址,可修改实参的值 函数需修改原变量的值(如交换两个变量)
二级指针传递 形参指向一级指针的地址,可修改一级指针的指向 函数需为外部指针分配内存或改变其指向

13.2典型应用:交换两个变量

void swap(int *a, int *b) 
{
    int temp = *a;
    *a = *b;
    *b = temp;
}
int x=1, y=2;
swap(&x, &y); // 调用后x=2, y=1

14  学习总结

1、野指针操作:对未初始化、已释放或越界的指针解引用,导致程序崩溃或数据损坏。
解决:初始化指针为NULL,释放后立即置空,避免越界。

2、类型不匹配:用char*指针访问int变量(如char *p = (char*)&a;)可能导致数据截断或解析错误。
        解决:严格保证指针类型与指向数据类型一致。

3、内存泄漏:动态分配的内存未释放,长期运行导致系统内存耗尽。
        解决:遵循 “谁申请谁释放” 原则,使用智能指针(C++)或内存池管理。

4、重复释放内存:对同一指针多次调用free,导致内存管理混乱。

        解决:释放后立即置空,释放前检查是否为NULL

5、字符串函数使用错误

        (1)使用strcpy时目标空间不足,导致缓冲区溢出(如char dest[3]; strcpy(dest, "hello");)。
        解决:使用strncpy限制复制长度,或确保目标空间足够。

        (2)对非'\0'结尾的字符序列使用strlen,导致遍历越界(如未初始化的字符数组)。
        解决:确保字符串以'\0'结尾,或手动指定长度。

6、const 指针违规操作:对const int *p尝试修改指向空间的值(*p = 5),编译报错。
        解决:明确const修饰的对象,避免违规修改。

7、混淆指针数组与数组指针:错误定义(如int (*a)[5]写成int *a[5])导致内存访问错误。
        解决:记住优先级:[]高于*,数组指针需加括号。

8、返回局部变量地址:函数返回栈上变量的地址(如int *func() { int a=1; return &a; }),返回后地址失效。
        解决:返回全局变量、静态变量或动态分配内存的地址。

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐