51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

内存和指针

  1. 内存 {#1-内存} =============

关于内存我们都耳熟能详,对于程序员而言,可以从两个维度去理解这个概念 --- 物理存储器和存储地址空间

  • 内存是计算机系统中用于存储数据和指令的地方。它是计算机的关键组件之一,用于临时存储和处理正在运行的程序所需的数据。

    • 主板上装插的内存条
    • 显示卡上的显示RAM芯片
    • 各种适配卡上的RAM芯片和ROM芯片
  • 计算机的内存通常被划分为不同的存储单元,每个存储单元都有一个唯一的地址,用于标识其在内存中的位置。每个存储单元都可以存储一定数量的数据。

    • 编码:对每个物理存储单元(一个字节)分配一个号码
    • 寻址:可以根据分配的号码找到相应的存储单元,完成数据的读写

我们可以将内存抽象成一个很大的一维字符数组,编码就是对内存的每一个字节分配一个唯一的32位或64位的编号(与32位或者64位处理器相关),这个内存编号我们称之为内存地址,例如:一个内存地址可能是0x00007FF6D9A5C000。

内存中的每一个数据根据类型的不同,分配的内存大小也不尽相同:

  • char:占1个字节分配1个地址

  • int、float:占4个字节分配4个地址

  • double、long long:占8个字节分配8个地址

  1. 指针 {#2-指针} =============

指针是计算机编程中一个重要的概念,它是一种特殊的数据类型,用于存储变量的内存地址。简单来说,指针指向了一个变量在计算机内存中的存储位置

每个变量在内存中都有一个地址,在编程中,通过定义指针变量,我们可以存储一个变量的地址,这就使得我们可以通过间接的方式操作和修改变量,而不需要访问原始的变量名。

2.1 指针变量的定义和使用 {#2-1-指针变量的定义和使用}

指针也是一种数据类型,指针变量也是一种变量,指针变量指向谁,就把谁的地址赋值给指针变量。

首先,要声明一个指针变量,需要使用星号"*"来表示该变量是一个指针。例如,以下示例声明了一个指向整数的指针变量 ptr:

|-------------------|----------------------------------------------------------------------------| | 1 2 3 4 5 | // 关于 * 的位置没有要求,根据个人喜好书写即可 int *ptr; int* ptr; int * ptr; int*ptr; |

可以将指针初始化为某个特定变量的地址,也可以在后续的代码中将指针指向某个变量的地址。要将指针指向一个变量的内存地址,需要使用取地址符号"&",例如:

|-------------|-----------------------------------------------------| | 1 2 | int num = 10; ptr = # // 将ptr指向num的内存地址 |

要访问指针指向的变量的值,需要使用星号"*"来解引用指针。解引用操作符告诉编译器去访问指针所指向的内存地址,并获取那个地址处存储的值。例如:

|-----------|---------------------------------------------| | 1 | printf("%d", *ptr); // 输出指针所指向的变量的值 |

还可以通过指针来修改变量的值。通过解引用指针并使用赋值操作符,可以将新的值存储到指针指向的内存地址处。例如:

|-----------|----------------------------------------| | 1 | *ptr = 20; // 将指针所指向的变量的值修改为20 |

下面是关于指针定义和使用的一段完整示例代码:

|------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #include <stdio.h> int main() { int a = 9; char b = 97; printf("%p, %p\n", &a, &b); //打印a, b的地址 // int *代表是一种数据类型,int*指针类型,p才是变量名 // 定义了一个指针类型的变量,可以指向一个int类型变量的地址 int* p; //将a的地址赋值给变量p,p也是一个变量,值是一个内存地址编号 p = &a; printf("%d\n", *p);//p指向了a的地址,*p就是a的值 char* p1 = &b; printf("%c\n", *p1);//*p1指向了b的地址,*p1就是b的值 return 0; } |

程序输出的结果如下:

|-----------------|----------------------------------------------------------------| | 1 2 3 4 | 0000008E46D9F914, 0000008E46D9F934 9 a a = 100, b = 66 |

注意事项:&可以取得一个变量在内存中的地址。但是,不能取寄存器变量地址,因为寄存器变量不在内存里,而在CPU里面,所以是没有地址的。

2.2 指针大小 {#2-2-指针大小}

指针是一个变量,它存储了内存地址的值。在C语言中,使用运算符sizeof()可以计算指针的大小,指针的大小指的是指针变量指向的存储地址的大小,得到的值为 4 或 8:

  • 在32位平台,所有的指针(地址)都是32位(4字节)
  • 在64位平台,所有的指针(地址)都是64位(8字节)

|------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #include <stdio.h> int main() { int* p1; int** p2; char* p3; char** p4; printf("sizeof(p1) = %zd\n", sizeof(p1)); printf("sizeof(p2) = %zd\n", sizeof(p2)); printf("sizeof(p3) = %zd\n", sizeof(p3)); printf("sizeof(p4) = %zd\n", sizeof(p4)); printf("sizeof(double *) = %zd\n", sizeof(double*)); return 0; } |

在32为平台测试的结果如下:

|-------------------|------------------------------------------------------------------------------------------| | 1 2 3 4 5 | sizeof(p1) = 4 sizeof(p2) = 4 sizeof(p3) = 4 sizeof(p4) = 4 sizeof(double *) = 4 |

在64为平台测试的结果如下:

|-------------------|------------------------------------------------------------------------------------------| | 1 2 3 4 5 | sizeof(p1) = 8 sizeof(p2) = 8 sizeof(p3) = 8 sizeof(p4) = 8 sizeof(double *) = 8 |

2.3 野指针和空指针 {#2-3-野指针和空指针}

指针变量也是变量,是变量就可以任意赋值,不要越界即可(32位为4字节,64位为8字节),但是,任意数值赋值给指针变量没有意义,因为这样的指针就成了野指针,此指针指向的区域是未知(操作系统不允许操作此指针指向的内存区域)。所以,野指针不会直接引发错误,操作野指针指向的内存区域才会出问题

|------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #include <stdio.h> int main() { int a = 100; int* p; // 给指针变量p赋值,p为野指针 p = a; // 给指针变量p赋值,p为野指针 p = 0x12345678; // 对野指针指向的未知区域进行写操作,内存出问题,error *p = 1000; return 0; } |

在上面的代码中演示的野指针有两种存在形式:

  • 第6行定义了指针变量但是没有初始化,其指向一块随机的内存地址
  • 第8行和第10行都是让指针指向了一块内存地址,这块地址属于哪个进程(程序)以及现在有没有被使用都是未知的

野指针和有效指针变量保存的都是数值,为了标志此指针变量没有指向任何变量(空闲可用),C语言中,可以把NULL赋值给此指针,这样就标志此指针为空指针,没有任何指针。

|-----------|--------------------------| | 1 | int* ptr = NULL; |

NULL是一个值为0的宏常量:

|-----------|----------------------------------| | 1 | #define NULL ((void *)0) |

2.4 万能指针 void* {#2-4-万能指针-void}

void* 是一个特殊的指针类型,用来表示一个指向未知类型的指针。它可以存储任何类型的地址,但无法直接解引用或操作其指向的数据。

它可以用于在没有明确类型信息的情况下表示指针。例如,当你需要在函数中传递一个指针,但不确定指针所指向的数据类型时,可以使用 void* 作为参数类型。

使用 void* 类型时,需要注意的是,在使用 void* 指针进行操作之前,必须将其转换为适当的指针类型,以便进行正确的解引用和操作。这是因为 void* 指针在不确定指向的具体类型时无法进行类型推断。

|---------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #include <stdio.h> int main() { void* p = NULL; int a = 10; // 指向变量时,最好转换为void * p = (void*)&a; //使用指针变量指向的内存时,转换为int * *((int*)p) = 11; printf("a = %d\n", a); return 0; } |

总而言之,void* 是一种特殊的指针类型,可以用于表示指向未知类型的指针。它在某些情况下非常有用,但需要小心使用,以避免类型不匹配或错误的操作。

2.5 const 与指针 {#2-5-const-与指针}

在定义指针的时候可以添加const关键字, 根据const关键字的位置可以用其修饰指针本身也可以用来修饰指针指向的值:

  1. 常量指针:const 关键字在 * 左边,常量指针的本质是指针,表示指针所指向的地址可变,但是地址中的数据不能被修改。例如:

    |-------------|-------------------------------------------------------------------------------------------| | 1 2 | const int* ptr; // ptr 是一个指向 int 类型常量的指针 int const *ptr; // 等价于 const int* ptr; |

    在这个例子中,ptr 是一个指向 int 类型常量的指针。这意味着不能通过 ptr 修改它所指向的整数值,但该指针指向的地址是可以改变的

  2. 指针常量: const 关键字在 * 右边,表示指针指向的地址不能被修改,但是地址中的值可以被修改。例如:

    |---------------|---------------------------------------------------------------------------------------------| | 1 2 3 | int value = 10; // ptr 是一个常量指针,指向 int 类型的变量 value, 不能被重新赋值 int* const ptr = &value; |

    在这个例子中,ptr 是一个指针常量,指向了变量 value。这意味着不能通过 ptr 修改指针的值,即不能将 ptr 指向其它地址,但可以通过 *ptr 来修改所指向的变量的值。

    注意:指针常量在定义时要赋初值。

下面有几句口诀,方便大家记忆常量指针和指针常量:

  • const (*号)左边放,我是指针变量指向常量 - 常量指针
  • const (*号)右边放,我是指针常量指向变量 - 指针常量
  • const (*号)两边放,我是指针常量指向常量 - 常量指针常量

指针变量能改指向,指针常量不能转向,要是全部变成常量,锁死了,我不能转向,你也甭想变样!

|------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | #include <stdio.h> int main() { // 常量指针 int value1 = 100; int value2 = 200; const int* ptr = &value1; // 初始化 ptr = &value2; // ok, 可以修改指针指向的内存地址 *ptr = 300; // error, 不能修改指针指向的地址中的值 // 指针常量 int value = 10; int* const ptr1 = &value; // 初始化 *ptr1 = 20; // ok, 可以修改指针指向的地址中的值 ptr1 = &value1; // error, 不能修改指针指向的内存地址 // 指向常量的指针常量 const int* const ptr2 = &value2; *ptr2 = 99; // error, 不能修改指针指向的地址中的值 ptr2 = &value; // error, 不能修改指针指向的内存地址 return 0; } |

const 修饰的指针变量可以帮助确保数据的不可变性和程序的安全性,特别是在函数参数传递、返回值和常量数据的处理方面有广泛的应用。在使用 const 修饰的指针时,需要注意遵守 const 修饰符的规则,以便正确使用和理解指针的行为。

赞(0)
未经允许不得转载:工具盒子 » 内存和指针