C语言程序的内存分布和指针简介
C语言程序的内存分布
代码区
只读区域,程序运行过程中无法做任何修改的存储区域,用于存放CPU执行的机器指令
通常,代码区是可共享的(即另外的执行程序可以调用它),因为对于频繁被执行的程序,只需要在内存中有一份代码即可。
代码区通常是只读的,使其只读的原因是防止程序意外地修改了它的指令。
数据区
包括已初始化的数据段(.data)和未初始化的数据段(.bss)
已初始化的数据段:通常简称为数据段,是指用来存放程序中已初始化的全局变量的一块内存区域。数据段属于静态内存分配。数据段中的静态数据区存放的是程序中已初始化的全局变量、静态变量和常量。
未初始化的数据段 :亦称BSS区,存入的是全局未初始化变量。BSS区的数据在程序开始执行之前被内核初始化为0。
堆区
用于动态内存分配。
当进程调用malloc,calloc,realloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free 等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)
栈区
由编译器自动分配释放内存的区间,所得的内存空间一般都是连续的,是用来存放函数的参数值、局部变量的值、函数的返回值等。
在程序运行时由编译器在需要的时候分配,在不需要的时候自动清除。
指针
阅读代码,思考该程序的输出是什么
1 |
|
内存与地址
在计算机中,数据是存放在内存单元中的,一般把内存中的一个字节称为一个内存单元。为了更方便地访问这些内存单元,可预先给内存中的所有内存单元进行地址编号,根据地址编号,可准确找到其对应的内存单元。由于每一个地址编号均对应一个内存单元,因此可以形象地说一个地址编号就指向一个内存单元。C 语言中把地址形象地称作指针。
C语言中的每个变量均对应内存中的一块内存空间,而内存中每个内存单元均是有地址编号的。在 C 语言中,可以使用运算符 & 求某个变量的地址。
1 |
|
1 | //某次运行的结果: |
区分变量的地址值和变量的值。如上例中,变量 a 的地址值(指针值)为12ff40,而变量 a 的值为 100。
一个指针是一个地址,是一个常量。而一个指针变量却可以被赋予不同的指针值,是变量。但是常把指针变量简称为指针。为了避免混淆,我们约定:“指针” 是指地址,是常量,“指针变量”是指取值为地址的变量。
指针名、数组名、函数名就是地址,它们分别表示指针所指向元素的地址、数组的首地址和函数的入口地址
指针变量的 定义格式 为:
数据类型符 * 变量名;
如:
1 | int *p1; |
指针变量初始化的方法:
1 | int a; |
引用指针变量
1 | int a; |
void*类型指针
表示形式为 void*p;表示不指定 p 是指向哪一种数据类型的指针变量。使用时要进行强制类型转换。例如:
1 | char *p1; |
空指针NULL
在C语言中,如果一个指针不指向任何数据,我们就称之为空指针,用NULL
表示。例如:
1 | int *p = NULL; |
注意区分大小写,null 没有任何特殊含义,只是一个普通的标识符。
NULL 是一个宏定义,在stdio.h被定义为:
1 |
我们知道,变量一旦定义就要分配内存,指针变量也是如此。例如:
1 | int *p; //它不是空指针 |
它的值是随机的,是垃圾值,如果不小心使用了它,运行时一般会引起段错误,导致程序退出,甚至会不知不觉地修改数据。
所以,为了实现交换两个数的值的功能,可以将形参改为接收实参的地址
1 |
|
malloc 与 free
malloc
头文件:stdlib
原型:void malloc(size_t size)
所以需要根据实际你需要的类型对其强制类型转换
返回值
成功时,返回指向新分配内存的指针。
失败时,返回空指针(NULL)
参数:size : 要分配的字节数
free
头文件:stdlib
原型:void free( void* ptr );
参数:指向要解分配的内存的指针
返回值:无
我们需要一个大小为 N ( N < 1000)的数组,我们可能会想写成arr[N],由输入决定数组的大小,但是这样写编译会出错。
所以我们通常这么写:
1 | int main(void) { |
但是这样的不确定可能会造成空间的浪费。
其实我们可以这样写
1 | int* arr = (int*)malloc(sizeof(int) * N) |
该代码定义了一个指针arr指向sizeof(int) * N这么大的空间