计算机的内存长什么样子?
1、计算机中的内存就像一叠非常厚的“便签”,一张便签就相当于一个字节的内存,一个字节有8个二进制位
2、每一张“便签”都有自然排序的一个编号,计算机是根据便签的编号来访问、使用"便签"
3、CPU会有若干个金手指,每根金手指能感知高低电平,高电平转换成1,低电平转换成0,我们常说的32位CPU指的是CPU有32个金手指用于感知电平,并计算出“便签”的编号
便签的最小编号:
00000000 00000000 00000000 00000000 = 0
便签的最大编号:
11111111 11111111 11111111 11111111 = 4294967295
所以32位CPU最多能使用4Gb的内存
4、便签的编号就是内存的地址,是一种无符号的整数类型
什么是指针:
1、指针(pointer)是一种特殊的数据类型,使用它可以用于定义指针变量,简称指针
2、指针变量中存储的是内存的地址,是一种无符号的整数类型,
3、通过指针变量中记录的内存地址,我们可以读取对应的内存中所存储的数据、也可以向该内存写入数据
4、可以通过 %p 显示指针变量中存储的地址编号
如何使用指针:
定义指针变量
类型* 指针变量名;
int num;
char n;
double d;
int* nump; // 访问4字节
char* p; // 访问1字节
double* doublep; // 访问8字节
long* lp; // 访问4/8字节
1、一个指针变量冲只记录内存中某一个字节的地址,我们把它当做一块内存的首地址,当使用指针变量去访问内存时具体连续访问多少个字节,指针变量的类型来决定。
2、普通变量与指针变量的用法上有很大区别,为了避免混用,所以指针变量一般以p结尾,以示区分
3、指针变量不能连续定义,一个*只能定义一个指针变量
int n1,n2,n3; // n1 n2 n3都是int
int* p1,p2,p3; // int *p1,p2,p3 p1是int* p2 p3是int
int* p1,*p2,*p3; // p1 p2 p3都是int*
4、指针变量与普通一样,默认值是随机的(野指针),为了安全尽量给指针变量初始化,如果不知道该初始化为多少,可以先初始化为NULL(空指针)
int* p; // 野指针
int* p = NULL; // 空指针
给指针变量赋值:
指针变量 = 内存地址
所谓的给指针变量赋值,其实就是往指针变量中存储一个内存地址,如果该内存地址是非法的,当使用该指针变量去访问内存时会出现 段错误
// 存储堆内存地址
int* p = malloc(4);
// 存储指向num所在内存地址(stack\data\bss)
int num; // stack
int* p = #
注意:num变量的类型必须与p类型相同
指针变量解引用:
*指针变量名;
给指针变量赋值就是让指针指向某一个内存,对指针变量解引用就是根据指针变量中存储的内存编号,去访问该内存,具体连续访问多少个字节由指针变量定义时的类型决定
int num = 100;
// 定义指针变量
int* p = NULL;
// 给指针变量赋值
p = #
// 查看指针变量的值
printf("%p\n",p);
// 对指针变量解引用
printf("%d\n",*p + 10);
*p = 88;
printf("%d\n",num);
如果指针变量中存储的是非法的内存地址,当程序运行到该指针变量解引用时,会出现段错误
// 定义指针变量
int* p = NULL;
*p = 100; // 非法访问内存 会段错误
验证指针变量中存储的就是一个整数
#include <stdio.h>
void func(unsigned long addr) {
*(int*)addr = 88;
}
int main() {
int num = 10;
func(&num);
printf("num=%d\n",num);
}
为什么要使用指针:
1、函数之间需要共享变量
函数之间的命名空间是相互独立,并且是以赋值的方式进行单向值传递,所以无法通过普通类型形参传参来解决共享变量的问题
全局变量虽然可以在函数之间共享,但是过多地使用全局变量容易造成命名冲突和内存浪费
使用数组是可以共享,但是需要额外传递长度
因此,虽然函数之间的命名空间是相互独立的,但是所使用的是同一条内存,也就是说内存空间是同一个,所以使用指针可以解决函数之间共享变量的问题
#include <stdio.h>
void func(int* p) {
printf("func p=%p,*p=%d\n",p,*p);
*p = 88;
printf("func p=%p,*p=%d\n",p,*p);
}
int main() {
int num = 66;
func(&num);
printf("main &num=%p ",&num);
printf("main:%d\n",num);
}
当函数需要返回两个以上的数据时,光靠返回值满足不了,可以通过指针共享一个变量,借助该输出型参数,返回多个数据
// put_p输出型参数
int func(int* put_p) {
*put_p = 20;
return 10;
}
int main() {
int num = 0;
int ret1 = func(&num);
printf("ret1 = %d ret2=%d\n", ret1, num);
}
2、使用指针可以提高函数之间的传参效率
一个指针变量占内存 4 | 8 字节
函数之间传参是以内存拷贝的方式进行,当参数的内存字节数比较大(大于4字节时)的时候,传参的效率就会比较低下,此时使用指针传参可以提高传参效率
#include <stdio.h>
void func(long double* f) {
}
int main() {
long double f = 3.14;
for (int i = 0; i < 1000000000; ++i) {
func(&f);
}
}
3、使用堆内存时,必须与指针变量配合
堆内存无法像栈、数据段、bss段那样给内存取名字,通过标准库、操作系统提供的管理堆内存的接口函数,来操作堆内存时,是直接返回堆内存的地址给调用者,因此必须使用指针变量配合才能访问堆内存
malloc
realloc
calloc
学习建议:指针就是一种工具,目的是完成任务,而使用指针是有危险性,所以除了以上三种情况需要使用指针以外,不要轻易使用指针
使用指针需要注意的问题:
空指针:
指针变量中存储的NULL,那么它就是空指针
操作系统规定程序不能访问NULL指向的内存,只要访问必定段错误
当函数的返回值是指针类型时,函数执行出错时一般返回NULL,作为函数的错误标志
NULL也可以作为初始值给指针变量初始化
#include <stdio.h>
int* func(void) {
return NULL; //表示执行出错
}
int main() {
int* p = NULL;
int num= 10;
p = #
printf("%d\n,",*p); // 必定段错误
}
如何避免空指针产生的段错误?
对来历不明的指针进行解引用前先判断是否是空指针
1、当自己写的函数的参数中有指针类型时,在使用该参数时,需要先判断是否是空指针再使用
2、当使用别人提供的函数时,它的返回值类型是指针类型时,获取返回值后,也需要先判断是否是空指针再使用
int* p = malloc(4);
if(NULL == p) {
printf("内存申请失败\n");
} else {
*p = 100;
}
if(NULL == p) // 正确写法
if(p == NULL) // 容易漏写= 变成赋值 错误写法
if(!p) { // 绝大多数系统中 NULL 是0,少数系统中是1
// 通用性不够强
}
注意:必须导入 stdio.h 后 NULL才可以使用
野指针:
指针变量中存储的地址,无法确定是哪个地址、是否是合法地址,此时该指针就称为野指针
对野指针解引用的后果:
1、一切正常,刚好指针变量中存储的是空闲且合法的地址
2、段错误,刚好指针变量中存储的是非法的地址
3、脏数据,存储的是其它变量的地址
野指针比空指针的危害性更大
1、空指针可以通过if(NULL==p)判断出来,但是野指针一旦产生,无法通过代码判断,只能通过经验人为判断
2、野指针就算暂时不暴露问题,不代表没有问题,后期可能随时暴露
如何避免产生野指针:
所有的野指针都是人为造成的,因此想要避免野指针的危害,只能通过不人为制造野指针
1、定义指针变量时一定初始化
2、函数不要返回局部变量、块变量的地址,因为当函数执行结束后,该地址指向的内存就会被自动销毁回收,如果非要接收,就接受到了一个野指针
3、与堆内存配合的指针,当堆内存手动释放后,该指针要及时置空
int* p = malloc(4);
*p = 100;
free(p);
p = NULL;
if(NULL==p)
标签:变量,int,高级,C语言,num,内存,NULL,指针
From: https://www.cnblogs.com/sleeeeeping/p/18173795