C-pointer Learning

发布时间 2023-05-21 17:16:59作者: duuuuu17

基础

指针类型

静态/全局内存

指在内存空间中的全局/静态数据区的指针变量

自动内存

即局部作用域的指针,只有在函数被调用时才创建。

动态内存

在堆区动态创建的指针变量,在不使用时需要即是释放该部分内存空间。

特殊指针

NULL指针

在指针变量中,初始化时,可以使用NULL或数字0赋值,表示空指针。

我们还需只要的是,仅声明的指针不是空指针,仅声明的指针其值为随机值,空指针时指定了指针为空。

常作为条件表达式

用于判断指针是否为空

在ASCII码中的NULL

是指“NULL”字符串或者"\0"结束符。

void指针

是通用指针,用于存放任何数据类型的引用。任何指针都可以赋给void型指针。

void指针的大小根据编译器或系统的机器字长相等。

注意

在使用void指针时,我们需要正确转换void指针,因为随意使用void指针会出错。

特点

  1. void指针与char指针有相同的形式和内存对齐方式。

  2. void指针与别的指针不会相等,不过两个赋值为NULL的void指针是相等的。

常量指针和指针常量

怎么看指针类型,代码从右往左看就能明白。

int a = 1;const int b= 3;

指针类型 code 指针是否可修改 指针引用的数据是否可被修改 是否必须初始化
指向非常量的指针 int *ptr = &a;
指向常量的指针 const int *ptr = &b;
指向非常量的常量指针 int *const ptr = &a;
指向常量的常量指针 const int *const ptr = &b;
指向常量的常量指针的指针 const int*const *ptr = &b;

code

    int a = 5;
    const int b = 5;
    //指向非常量的指针
    int *ptr1 = &a;
    //指向常量的指针
    const int *ptr2 = &b;
    //指向非常量的常量指针
    int *const ptr3 = &a;
    //指向常量的常量指针
    const int *const ptr4 = &b;
    //指向常量的常量指针的指针
    const int *const *ptr5 = &ptr4;

阅读指针声明

一般来说,我们应该从右往左读指针。

const int *ptr;
从右往左依次读该指针变量:
    *ptr 指针变量
int *ptr int型的指针变量
const int *ptr ,指向整数常量的指针变量

打印指针的值

一般来说,指针保存的值是地址,我们使用十六进制格式输出在屏幕上。

在printf中,有以下格式

格式说明符 含义
%d 值输出为十进制数
%o 值输出为八进制数
%x(%p:十六进制数为大写) 值输出为十六进制数

指针预定义类型

size_t

用于安全地表示长度,为sizeof()和strlen()的返回值类型,以字节为单位。

该数据类型为无符号整型,是C语言中任何对象能表示的最大长度。

ptrdiff_t

用于处理指针算术运算

intptr_t和uintptr_t

用于存储指针地址,此类型的地址长度与机器字长相等,用于将地址变为整型存储。

uintptr_t是intptr_t的无符号版。对于大部分附变量地址操作,常用intptr_t而非uintptr_t,是后者无法赋一个有符号型的变量地址。

需要注意的是:在对于不同的变量赋予对应类型指针即可,不应该误用intptr_t类型的指针。intptr_t指针使用时需要强转类型,某些类型使用它会丢信息。

#include<stdio.h>
#include<stdint.h>
intptr_t num = 5;
intptr_t *intptrt = &num;
printf("intptrt's size:%d\n",sizeof(intptrt));
printf("intptrt's value:%d\n",*intptrt);

指针运算

常见的指针*,&,++,--,->(用于访问指针引用的结构的字段),==(!=),<(>,>=,>=),以及指针类型转换。

指针的加减比较运算

加运算

将指针+整数,表示的是地址上的+数据类型大小*整数。用于移动指针。

注意

void指针不可以用加运算

减运算

指针之间的减运算其结果是两者之间相差的元素个数,常用于变量为连续空间。

比较运算

在连续空间,如数组中的元素,我们使用指针可以比较其两者,间接得出两者的前后的位置。

动态分配

除了数组和结构体的成员变量在动态分配时,会是连续空间。其余在分配时不一定连续分配内存块。

分配内存时,会根据指针的指向的数据类型进行对齐。

需要注意的问题:指针未定义不应使用、不需要的内存块需要及时释放、动态分配时内可能会分配失败(返回NULL值)、动态开辟空间的size_t应正确。

动态分配函数

函数 描述
malloc 从堆上分配内存
realloc 在之前分配的内存块的基础上,将内存重新分配大小
calloc 从堆上分配内存并初始化内存内容为0
free 释放内存块

需要注意的是:静态和全局指针在声明时,是无法用malloc初始化的,而是通过赋值方式。

static int *ptr;
ptr = malloc(sizeof(int));

C中只有指针传递和值传递。

realloc

void *realloc(void *ptr, size_t size);

realloc 接受两个参数:ptrsizeptr 是一个指向已分配内存的指针,size 表示需要重新分配的新大小(以字节为单位)。

realloc 的功能是重新分配 ptr 指向的内存,使其大小变为 size 字节。有以下几种情况可能发生:

  • 如果 ptrNULL,则 realloc 的行为类似于 malloc,它会分配一个新的大小为 size 字节的内存块,并返回一个指向分配内存的指针。
  • 如果 size 是 0,且 ptr 不是 NULL,则 realloc 的行为类似于 free,它会释放 ptr 所指向的内存,并返回 NULL
  • 如果 ptrsize 都有效,realloc 会尝试重新分配 ptr 指向的内存块,使其大小变为 size 字节。如果内存分配成功,则返回一个指向新分配内存的指针,该指针可能与原来的 ptr 相同,也可能是一个新的地址。如果内存分配失败,则返回 NULL,原来的内存块保持不变。

需要注意的是,使用 realloc 进行内存调整时,原来的内存块的内容可能会被复制到新分配的内存块中。因此,建议在使用 realloc 后,检查返回的指针是否为 NULL,以确保内存分配成功。

检测realloc是否移动内存块

(常用)1.存放在临时的指针变量,检测该临时指针变量是否为空

2.使用memcmp(ptr, new_ptr, original_size) == 0,若两个内存块的内容相同则返回0.

calloc

void *calloc(size_t num, size_t size);
//例如:
int *pi = calloc(5,sizeof(int));
== int *pi = malloc(5*sizeof(int));
pi = memset(pi,0,5*sizeof(int));
//memset函数用于将制定长度的内存块的内容赋指定值
//memeset( *ptr, value, size);

calloc 接受两个参数:numsizenum 表示要分配的元素个数,size 表示每个元素的大小(以字节为单位)。calloc 会分配 num * size 字节的内存,并返回一个指向分配内存起始位置的指针。分配的内存会被初始化为零。

calloc 的主要用途是在动态内存分配时初始化一块连续的内存区域,例如创建数组或结构体对象等。与 malloc 不同,calloc 在分配内存后会将其初始化为零,因此可以确保新分配的内存不包含任何垃圾数据。

内存结构

栈(Stack):

  • 栈是一种自动分配和释放内存的数据结构,通常用于存储局部变量和函数调用的上下文信息。
  • 栈的内存分配和释放由编译器自动管理,栈上的数据遵循后进先出(LIFO)的原则。
  • 栈的大小有限,通常较小,由系统预先分配固定的空间,超出栈空间大小可能导致栈溢出。
  • 栈的访问速度较快,因为它使用指针进行操作,而且内存分配和释放非常高效。
  • 向上扩展

堆(Heap):

  • 堆是一种动态分配和释放内存的数据结构,用于存储动态创建的对象和数据结构。
  • 堆的内存分配和释放由开发人员手动管理,需要显式地调用相应的函数(如 malloc、free 或 new、delete)来分配和释放内存。
  • 堆的大小相对较大,可根据需求动态增长,但是需要注意及时释放不再使用的内存,避免内存泄漏。
  • 堆上的数据存储没有特定的顺序,可以随机访问,但是堆的访问速度相对较慢。
  • 向下扩展

区别:

栈上的数据具有自动分配和释放的特性,而堆上的数据需要手动管理内存的分配和释放。在使用堆上的内存时,需要注意及时释放不再使用的内存,以防止内存泄漏和资源浪费。

程序栈

程序栈由许多栈帧组成,栈帧由以下元素组成:

  • 返回地址:函数完成后要返回的程序内部地址
  • 局部数据存储:为局部变量分配的内存。
  • 参数存储:为函数参数分配的内存。
  • 栈指针和基指针:运⾏时系统⽤来管理栈的指针。
  • 栈帧的创建和销毁是根据函数调用的顺序进行的,遵循后进先出(LIFO)的原则

栈指针

栈指针(Stack Pointer)通常是一个寄存器,它指向栈的当前顶部或下一个可用的位置。栈指针会随着函数调用和返回的发生而移动。当调用一个函数时,栈指针会向下移动,为该函数的局部变量和参数分配空间;当函数返回时,栈指针会向上移动,释放这些空间

基指针

基指针(Base Pointer)通常是另一个寄存器,用于指向当前函数的栈帧的起始位置。栈帧是存储函数的局部变量、参数和返回地址的一块连续内存区域。基指针在函数执行过程中保持不变,用于访问函数的局部变量和参数。

常见的观点

基指针和栈指针的关系可以根据具体的编程语言和编译器而有所不同。一种常见的方式是,基指针指向栈帧的底部,栈指针指向栈帧的顶部或下一个可用位置。在函数调用过程中,基指针保持不变,栈指针根据需要移动。

栈帧

  • 栈帧针顶部

  • 栈指针位置

  • 基指针位置

  • 栈帧针底部

如何释放动态分配的指针变量

我们在使用free函数释放动态分配的内存块时,释放后不会将该指针变量的值存储为NULL;

创建自己的free函数

#define safefree(p) saferfree((void**)&(p))
//将传递进的指针变量分配的动态内存块释放
//*pp = 指针变量分配的动态内存块的地址
//&pp = &p,只是使用了双重指针保护指针变量,仅对指针变量分配的内存块进行释放
void saferfree(void **pp){
    if(pp != NULL && *pp != NULL){
        free(*pp);
        *pp = NULL;
    }
}

函数指针

声明函数指针

//声明 
返回值类型 (*fptr)([参数]);
//例:
//使用类型定义 声明函数指针
typedef  int (*fptroperator)(int,int);
//实际调用函数
int add(int a,int b){
    return a+b;
}
int sub(int a,int b){
    return a-b;
}
int mul(int a,int b){
    return a*b;
}
int div(int a,int b){
    return a/b;
}
//使用函数指针
int compute(fptroperator f,int a,int b){
    return f(a,b);
}

返回函数指针

//函数指针声明承接上部分
fptroperator select(char opcode){
    switch(opcode){
        case '+': return add;
        case '-': return sub;
    }
}
int evaluate(char opcode,int a,int b){
    fptroperator f = select(opcode);
    return f(a,b);
}

其他

函数指针数组

//声明函数指针数组
typedef int (*operation)(int,int);
operation opearations[128]={NULL};
== typedef int(*operation[size])(int,int) = {NULL};

函数指针比较

函数指针也可以比较(用于检查使用函数指针的变量是否指向同一个函数的栈帧)

函数指针转换

强转(将一个使用函数指针的变量转换为另一个)

C的异常处理有待学习。