内存
内存划分为⼀个个的内存单元,每个内存单元的⼤⼩取1个字节。
⼀个⽐特位可以存储⼀个2进制的位1或者0。
内存单元的编号 == 地址 == 指针
指针类型的理解
int a = 10;
int * pa = &a;
*说明pa是指针变量,int 说明pa指向的是整型(int)类型的对象。
指针变量的大小
32位平台下地址是32个bit位,指针变量⼤⼩是4个字节
64位平台下地址是64个bit位,指针变量⼤⼩是8个字节
注意指针变量的⼤⼩和类型是⽆关的,只要指针类型的变量,在相同的平台下,⼤⼩都是相同的。
指针的解引用
指针的类型决定了,对指针解引用的时候有多大的权限(⼀次能操作几个字节)。
比如: char* 的指针解引用就只能访问一个字节,而int* 的指针的解引用就能访问四个字节。
指针+-整数
char* 类型的指针变量+1跳过1个字节, int* 类型的指针变量+1跳过了4个字节。
指针+1,其实跳过1个指针指向的元素。指针可以+1,那也可以-1。
指针的类型决定了指针向前或者向后走一步有多大(距离)。
void*指针
在指针类型中有⼀种特殊的类型是 void * 类型的,可以理解为无具体类型的指针(或者叫泛型指针,这种类型的指针可以用来接受任意类型地址。但是也有局限性, void* 类型的指针不能直接进行指针的+-整数和解引用的运算。
一般 void* 类型的指针是使用在函数参数的部分,用来接收不同类型数据的地址。
const修饰指针变量
const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变,但是指针变量本⾝的内容可变。
const如果放在*的右边,修饰的是指针变量本⾝,保证了指针变量的内容不能修改,但是指针指向的内容可以通过指针改变。
指针+-整数
数组在内存中是连续存放的,只要知道第⼀个元素的地址,顺藤摸瓜就能找到后面的所有元素。(指针+-整数)
int *p;
p+i是跳过i*sizeof(int)个字节
指针-指针
指针-指针的绝对值是指针和指针之间元素的个数
指针-指针,计算的前提条件是两个指针指向的是同一块空间
野指针
野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
野指针成因:
1.指针未初始化
2.指针越界访问
3.指针指向的空间释放
3. #
include
<stdio.h>
int
*
test
()
{
int
n =
100
;
return
&n;
}
int
main
()
{
int
*p = test();
printf
(
"%d\n"
, *p);
return
0
;
}
如何规避野指针
1.如果明确知道指针指向哪⾥就直接赋值地址,如果不知道指针应该指向哪⾥,可以给指针赋值NULL.
NULL 是C语言中定义的⼀个标识符常量,值是0,0也是地址,这个地址是⽆法使⽤的,读写该地址会报错。
2.小心指针越界
⼀个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是越界访问。
3.指针变量不再使用时,及时置NULL,指针使⽤之前检查有效性。
4.避免返回局部变量的地址
assert 断⾔
assert.h 头文件定义了宏 assert() ,用于在运行时确保程序符合指定条件,如果不符合,就报错终止运行。这个宏常常被称为“断言”。
assert(p != NULL);
上⾯代码在程序运行到这一行语句时,验证变量 p 是否等于 NULL 。如果确实不等于 NULL ,程序继续运行,否则就会终止运行,并且给出报错信息提示。
assert() 宏接受一个表达式作为参数。如果该表达式为真(返回值非零), assert() 不会产生任何作用,程序继续运行。如果该表达式为假(返回值为零), assert() 就会报错,在标准错误流stderr中写入一条错误信息,显示没有通过的表达式,以及包含这个表达式的文件名和行号。
如果已经确认程序没有问题,不需要再做断言,就在 #include <assert.h> 语句的前⾯,定义⼀个宏 NDEBUG 。
#
define
NDEBUG
#
include
<assert.h>