内存地址:
字节: 字节是一个内存容量的单位,一个字节有8个位 1byte = 8bit
地址: 系统为了方便管理内存,给每一个字节的内存都编了一个号码,这个编号就称为内存地址
基地址(起始地址):
单字节的数据:对于单字节的数据而言, 它所占用的内存的地址值就称为它的基地址
多字节的数据:对于多自己数据而言, 它最小的那个地址值称为给变量的基地址
取地址:
每一个变量都有一个自己的地址(基地址)可以使用&(去地址符)来获得他的地址。
例子:
int i ; float f ;
char c ;
printf("&c:%p\n" , &c);
printf("&i:%p\n" , &i);
printf("&f:%p\n" , &f);
指针的基础:
概念: 指针其实也是一个变量而已,只不过这个变量用来存储的是一个地址
如何定义:
int * ptr ; // 申请一片内存名字叫ptr , * 说明该内存用来存放地址值的因此该内存的大小已经确定, // int 用来说明该指针应该用来存放一个整形数据的地址, // 确定以后使用该指针PTR 来访问该内存的时候 要把该内存看做是一个整形数据 char * ptr1 ;
如何赋值:
int a = 0x12345678; int * p = &a ; // &a 把a的地址作为右值赋值给p所代表的内存 char *p1 = &a ; // 把一个整形数据的地址赋值给一个char指针p1 , // 如果想通过p1来访问a 的内存时 ,只能访问一个字节
指针的引用:
*p = 0x1023 ;// 通过指针p可以直接修改a内存 中的4个字节的数据 *p1 = 0x45;//通过指针p1只可以直接修改a内存 中的第一个字节的数据
指针的尺寸:
- 指针的尺寸与指针的类型没有任何关系
- 指针用来存放是一个内存的地址, 因此他的大小就应该是该计算机中内存地址的大小
注意:
int a = 0x12345678; int * p = &a ; char * p1 = &a ; printf("p:%p\n *p:%#x\n" , p , *p ); // 0x12345678 printf("p1:%p\n *p1:%#x\n" , p1 , *p1 ); // 0x78 因为 p1他是一个char类型的指针因此通过p1 // 来访问内存将会访问该内存的第一个字节
野指针:
概念: 野指针指的是一个指针她所指定地址是未知或非法。
危害:
-
-
- 情节不严重的情况下你有可能可以正常使用,但是一般情况下会出现段错误,导致程序崩溃
- 情节严重的,有可能导致系统崩溃
-
产生原因:
-
-
- 定义的时候没有给他初始化
- 他所指向的内存被释放了,系统已经回收了这个内存
- 指针越界
-
如何解决:
-
-
- 定义的时候记得给他初始化
- 被释放之后的指针应该不在使用
- 确定好内存的大小不要越界
-
空指针:
在某一些情况下我们定义了一个指针但是暂时不知道该指向哪里,等待后面的程序运行或分配地址,又不想让野指针出现危害系统的安全,可以让该指针先指向一个相对安全的位置。一般会让它指向0x000000000 这个地址。
概念:
空指针是一个保存了零地址的一个指针,也就零地址指针。
示例:
// 定义指针的时候 , 让它先指向NULL 确保系统不会被崩溃 int * p = NULL ; int * p1 = malloc(10); // 向系统动态申请了10个字节的内存 free(p1) ; // 释放掉刚才申请的10个字节的内存 。 p1 就指向了一个非法地址(野指针) p1 = NULL ; // 让p1 不要指向原本的地址, 指向空NULL
指针运算:
概念: 指针的运算就是在当前指针的地址上增加或减小N个字节。
指针的加法: p+1 则表示在p目前指向地址值上增加1个单位(指针的类型)
指针的减法: p-1 则表示在p目前指向地址值上下减1个单位(指针的类型)
int a = 0x12345678; int *p = &a ; char * p1 = &a ; printf(" p-1:%p , p:%p , p+1:%p\n" ,p-1 , p , p +1 ); // p-1 地址距离 p 有4个地址值之差 , p 距离p+1 也有4个字节之差 printf("p1:%#x\n" , *p1); // 0x78 p1指针指向的是 整型a 的入口地址 printf("p1:%#x\n" , *(p1+2)); // p1 + 2 则地址值增加 两个字节 因此指向了 0x34的位置
总结:
指针的加减运算实际上是在当前指针指向的地址基础上增加或者减少指针类型大小的字节。
指针的索引,注意指针的类型如果指针是一个整型的指针,那么通过该指针来访访问内存的时候或获取4个字节,并以整型的形式来解析它所存放的二进制编码。
数组与指针:
数组名字在很多情况下它就是表示数组的地址或者数组首元素的地址。
#include <stdio.h> int main(int argc, char const *argv[]) { int arr[5] = {5,4,3,2,1} ; int * ptr1 = arr ; //arr[3] === > *(arr+3) int * ptr2 = &arr ; // &arr 是一个指针,他指向的是整个数组 printf("arr:%p , ptr1:%p , ptr2:%p\n" , arr, ptr1 , ptr2); printf("ptr1+1:%p\n" , ptr1 + 1); printf("ptr2+1:%p\n" , ptr2 + 1); printf("&arr+1:%p\n" , &arr + 1); // &arr 表示整个数组+1 就加一个数组的大小 // 如何通过 ptr1来访问数组的值 printf("*ptr1:%d\n" , *ptr1 ); printf("*(ptr1+1):%d\n" , *(ptr1+1) ); // 如何定义一个用来指向数组的指针 int (*ptr3) [5] = &arr ; printf("ptr3+1:%p\n" , ptr3+1 ); // 因为该指针为数组指针,所以+1 则加了一个数组的大小 // 如何通过数组指针来访问数组里面的元素 printf("*ptr3:%p\n" , *ptr3 ); // 解引用一次得到的是整个数组的首地址 printf("**ptr3:%d\n" , **ptr3 ); // 再解一次,在首地址的基础上解析4个字节的数据 // 字符数组 char arr2 [] = {"Hello GZ2172"} ; * ptr4 = arr2 ; // 字符指针ptr4 指向数组 arr2 的首元素的首地址 *(ptr4+6) += 32 ; // ptr4+6 原本指向首元素H首地址,由于是char 类型+6则往后偏移6个字节 printf("ptr4:%s\n" , ptr4); // %s 需要的参数是一个字符串的地址,ptr4刚好就是一个字符串Hello ... 的地址 printf("*(ptr4+4):%c\n" , *(ptr4+4)); return 0; }