计算机内存区域中的每字节都对应一个编号,这个编号就是“地址”。如果在程序中定义了一个变量,那么在对程序进行编译时,系统就会给这个变量分配内存单元。如果我们在程序中需要将某个整型、浮点型或字符型数据变量的地址保存下来,需要用到 C 语言为我们提供的指针。在 C 语言中,指针变量是一种特殊的变量,它用来存放变量地址。掌握指针,对于学习操作系统、理解操作系统原理会有非常大的帮助。以下为我在学习和实战练习过程中所做的笔记,可供参考。
一、取地址操作符和取值操作符
取地址操作符为 &
,也称引用,通过该操作符我们可以获取一个变量的地址值;取值操作符为 *
,也称解引用,通过该操作符我们可以得到一个地址对应的数据。取地址操作符和取值操作符优先级相同,按照自右向左的顺序相结合。
1 | int main() |
指针变量前面的表示该变量为指针型变量,例如 float **pointer_1;
的指针变量名是 pointer_1
。在定义指针变量时必须指定其类型,只有整型变量的地址才能放到指向整型变量的指针变量中。
二、指针的传递和偏移
C 语言中的函数调用均为值传递。指针的传递将变量 i 的地址传递给 change 函数时,实际效果是 j=&i
,这时 j 是一个指针变量,依然是值传递, 只是 j 内部存储的是变量 i 的地址,所以通过 *j
就间接访问到了与变量 i 相同的区域,通过 *j=5
就实现了对变量 i 的值的改变。
指针提供随意访问内存的能力:
1 | void change(int* j) |
我们把对指针的加减称为指针的偏移,加就是向后偏移,减就是向前偏移,但对指针进行乘除是没有意义的。
1 | //指针的偏移 |
三、指针与一维数组
数组在传递时弱化为指针。这是由于一维数组名中存储的是数组的首地址,因此一维数组在函数调用进行传递时,它的长度子函数无法知道。定义一个指针变量时,指针变量的类型要和数组的数据类型保持一致,通过取值操作, 就可获取数组元素, 这种方法称为指针法。也可以通过取下标的方式来获取数组元素并进行修改,这种方法称为下标法。
1 | void change(char *d) //*d 为形参 |
四、指针与动态内存申请
C 语言的数组长度固定是因为其定义的整型、浮点型、字符型变量、数组变量都在栈空间中,而栈空间的大小在编译时是确定的。如果使用的空间大小不确定,那么就要使用堆空间申请动态内存申请。栈空间由系统自动管理,而堆空间的申请和释放需要自行管理,所以在具体例子中需要通过 free 函数释放堆空间。
栈是计算机系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈操作、出栈操作都有专门的指令执行,这就决定了栈的效率比较高;堆则是 C/C++ 函数库提供的数据结构,它的机制很复杂,例如为了分配一块内存,库函数会按照一定的算法在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能由于内存碎片太多)那么就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后返回。显然,堆的效率要比栈低得多。
1 | int main() |
C 初始动态分配 vs C++ 初始动态分配
1 | // C 初始动态分配 |