C程序在执行时,对内存进行分区:
内存分区意义:
不同区域存放的数据,赋予不同的生命周期,给我们更大的灵活编程。
分区 | 存放内容 | 特点 |
---|---|---|
栈区stack | 由编译器自动分配释放,存放函数的参数值,局部变量(为auto类型)。 | 栈的申请是由系统自动分配,,如在函数内部申请一个局部变量int a,同时判断所申请空间 是否 小于栈的剩余空间,如果小于则为其开辟空间,为程序提供内存,否则将报异常提示栈溢出。 |
堆区heap | 由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收。分配方式类似于链表,申请则是程序员自己操作使用malloc或new。 | 申请过程比较复杂,当系统收到程序的申请时,会遍历记录空闲内存地址的链表,以求寻找第一个空间大于所申请空间的堆节点,然后将该节点从空闲节点链表中删除,并将该节点的空间分配给程序,有些情况下,新申请的内存块的首地址记录本次分配的内存块的大小,这样在free、delete时能正确的释放内存空间。 |
全局静态区 | 存放全局与静态变量分两个段 bss 段存全局未初始化、静态未初始化数据; data段存全局初始化、静态初始化数据 | 可读可写,程序结束后由系统释放 |
代码区 | 存放可执行文件的二进制代码 (函数),这里还可以有 字符串等常量数据,对应 text段, | 共享、只读,程序结束后由系统释放 |
可执行程序程序分段 text段,data段,bss段
一个程序的 3个基本段:text段,data段,bss段 (size 命令查看)
数据段,代码段在程序运行之前就已经确定了的。
代码段 text段在编译时确定且内存中被映射为只读,但date数据段与bss数据段是可写的。
bss段存放的是未初始化的全局变量和静态变量,data段存放的是初始化后的全局变量和静态变量。
//main.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int global_init_var = 0; //全局初始化区 data段
int global_uninit_var; //全局未初始化区 bss段
char *p1; //全局未初始化区 bss段
int main()
{
int b; //栈区
char s[] = "abc"; // 栈区
char *p2; // 栈区
char *p3 = "123456"; // 123456\0 在常量区,p3在栈上。
static int static_init_var = 0; //全局(静态)初始化区 data段
static int static_uninit_var; //全局(静态)未初始化区 bss段
//malloc分配得来得10和20字节的区域就在堆区
p1 = (char *)malloc(sizeof(char) * 10); //堆区
p2 = (char *)malloc(sizeof(char) * 20); //堆区
// 123456\0 放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。
strcpy(p1, "123456");
return 0;
}
size 命名可用于查看目标文件、库或可执行文件中 各段(text、data、bss) 及其(dec)总和的大小
cxx@ubuntu16:~/C$
cxx@ubuntu16:~/C$ gcc main.c -o main
cxx@ubuntu16:~/C$ size main
text data bss dec hex filename
1384 560 32 1976 7b8 main
cxx@ubuntu16:~/C$
段名 | 说明 | 特点 |
---|---|---|
未初始化数据段 bss | (Block Started by Symbol)通常是指用来存放程序中未初始化的全局变量和静态变量的一块内存区域,属静态存储区。 | 可读可写,在程序执行之前.bss段会自动清0。 所以,未初始的全局变量、静态变量在程序执行之前已经成0了。 |
初始化数据段 data | 存放在编译阶段(而非运行时)就能确定的数据(已初始化),属静态存储区。 | 可读可写,赋了初值的全局变量和赋初值的静态变量存放在这个区域, 常量也可能存在这个区域。 |
代码段 text | 通常是指用来==存放程序执行代码(函数)==的一块内存区域,属代码区。 | 这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读,这里也有可能包含一些只读的常数变量,例如字符串常量等。 |
储存方式划分:
储存方式 | 说明 |
---|---|
静态存储(static storage) | 静态存储可分为bss 、data 、rodata等数据段(section) |
自动存储(automatic storage) | 自动存储则对应的是栈(stack) |
动态分配存储(allocated or dynamic storage) | 动态分配存储对应的是堆(heap) |
存储类型关键字auto 、extern、static、register
关键字 | 说明 | 存储区 | 生命周期 | 作用域 |
---|---|---|---|---|
auto | 自动型,为变量的默认存储方式 也就是不加修饰词的变量(普通变量) | 全局:函数外auto修饰为自动存储的静态存储区的 .data段;局部:函数内auto修饰则为自动存储的 栈区 | 全局变量为程序运行到程序结束,局部变量为函数调用到返回 | 定义在文件中函数外为定义点至该文件尾部;定义在函数中则为定义点至该函数尾部。 |
extern | 外部型 | 静态存储区的 .data段 | 程序运行到程序结束 | 声明点至该文件尾部 |
static | 静态型 | 静态存储区的 .data段 | 程序运行到程序结束 | 定义在文件中函数外为定义点至该文件尾部;定义在函数中则为定义点至该函数尾部。 |
还有一个 register寄存器型,寄存器存储类用于定义应存储在寄存器而不是RAM中的局部变量。这意味着该变量的最大位长度等于寄存器的位长度(通常是一个字),并且不能对其使用’&'运算符(因为它没有存储在内存中)。
仅应用于需要快速访问的变量(例如计数器)。
还应注意定义“register”并不意味着变量将一定会存储在寄存器中。它可能根据不同硬件和实现限制存储在寄存器中。
生存周期指 可存取该变量或调用该函数的时间范围,作用域指可存取该变量或调用该函数的代码范围。
普通变量
普通变量 没写有修饰词的变量,此处暂不讨论函数参数列表。
根据定义位置分 函数体外定义的为全局变量,函数体内定义的为局部变量。
普通变量类型 | 系统默认初始化值 | 定义初始化必要性 | 存取变量时值为 |
---|---|---|---|
全局变量 | 未初始化默认初始化为0 | 可不初始化 | 值为最后一次访问修改后的值。 |
全局变量 | 未初始化默认初始化为随机数,大佬表明一般该随机数不可用 | 须初始化否则值为随机数 | 该函数每次被调用都会重新初始化值。 |
注意:全局变量与函数局部变量重名时,会屏蔽全局变量优先使用局部变量;C++ 可用 :: 引用全局变量。
变量类型 | 生存周期 | 作用域 | 储存区 |
---|---|---|---|
全局变量 | 程序运行到程序结束 | 定义点到本文件尾部,其他文件可用extern 引用 | ==静态存储区 .data段 == |
局部变量 | 函数调用到函数返回 | 定义点到函数尾部 | 自动存储区的栈区 |
static 静态的
static在C里面可以用来修饰变量,也可以用来修饰函数。
数据类型 | 生存周期 | 作用域 | 位置 | 修饰词 |
---|---|---|---|---|
静态全局变量 | 程序运行到程序结束 | 定义点至该文件尾部 | 静态存储区 | static修饰 |
静态局部变量 | 程序运行到程序结束 | 定义点至该函数尾部 | 静态存储区 | static修饰 |
静态、内部函数 | 整个程序运行生命周期 | 定义点至该文件尾部 | 静态存储区 | static修饰函数 |
函数的定义和声明默认情况下是extern的,但static静态函数限制了它的使用范围,不能被其他文件所用。
static函数好处:
1. 其它文件中可以定义相同名字的函数,不会发生冲突;
2. 静态函数不能被其他文件所用,不能被其他文件模块调用,避免了代码维护时意外调用。
const 只读的
const修饰变量,也称之为 “const常量。”
const 修饰函数参数,防止其在函数体内发生意想不到的变化(防止误操作)。
如果形参是一个指针, 为了防止在函数内部修改指针指向的数据也可以用 const 加以限制。
const 修饰只读变量时,对变量加以限制不能被修改,const常量必须在定义的时候同时被初始化。
编译器通常不为普通const常量分配存储空间,将它们保存在符号表中,就成了一个编译期间的常量。
const 和指针一起使用:
const指针用法:int a = 10,b = 20; //定义有两个变量
用法类型 | 示例 | 说明 | 修饰词 |
---|---|---|---|
常量指针 | const int *p = &a; | 只能改值 *p = b; | const |
指针常量 | int * const p = &a; | 只能改指向 p = &b; | const |
指向常量的指针常量 | const int * const p = &a; | 都不能改 | const |
const 与引用:
int& c = a; //引用,给变量起别名 ,引用本质是指针常量;
int& c = a; ==> int * const c = &a;
//编译器遇到 引用 int& c = a;编译器优化,自动转换为 int * const c = &a; 指针常量处理。
引用与指针的区别:
- 引用必须初始化, 指针不必初始化
- 引用初始化后不能改变, 但是指针可以改变所指的对象
- 不存在空值的引用, 但是存在空值的指针
和一些常见问题:
堆栈溢出的原因有哪些?
1. 函数调用层次太深, 函数递归调用时, 系统要在栈中不断保存函数调用时的线
程和产生的变量, 递归调用太深, 会造成栈溢出, 这是递归无法返还。
2. 动态申请空间使用后没有释放。 由于 C 语言中没有垃圾资源自动回收机制, 因
此需要程序员主动释放已经不再使用的动态地址空间。
3. 数组访问越界, C 语言没有提供数组下标越界检查, 如果在程序中出现数组下
标访问超出数组范围, 运行过程中可能会存在内存访问错误。
4. 指针非法访问, 指针保存了一个非法地址, 通过这样的指针访问所指向的地址
时会产生内存访问错误。
全局变量可不可以定义在可被多个.c 文件包含的头文件中, 为啥?
可以, 在不同的 C 文件中各自用 static 声明的全局变量, 变量名可能相同, 但是各自 C 文件中 ==static修饰的全局变量==的作用域为仅该文件, 所以互不干扰。
简述static 关键字在 全局变量、 局部变量、 函数的区别?
1. 全局变量+static : 改变作用域, 改变(限制) 其使用范围。
只初始化一次, 防止在其他文件单元中被引用。 全局变量的作用域是整个
源程序, 在各个源文件中都是有效的, 而加了静态后的全局变量的作用域 仅限
于一个源文件中。
2. 局部变量+static : 改变了它的存储方式, 也就是改变了它的生存期为整个程序
3. 普通函数+static :限制其作用域, 仅在本文件。
解释Stack和Heap区别:
1. 申请方式:
Stack (栈): 由编译器自带分配释放, 存放函数的参数值, 局部变量等。
Heap(堆): 程序员自己申请, 并指定大小。 ==malloc 函数==。
2. 申请后的系统响应:
Strack (栈): 只要栈剩余空间大于所申请空间, 都会提供。
Heap (堆): 操作系统有记录空闲内存的链表,就提供,过程: 收到申请,遍历链表,寻找,申请空间的堆结点。
3. 申请内存的大小限制
Strack(栈): 向低地址扩展的数据结果, 连续内存区域, 栈 获得的空间较小。
Heap(堆): 向高地址扩展的, 不连续内存区域; 链表遍历方向为 (低地址到高地址)。栈获得空间灵活, 空间也大。
4. 申请效率
Strack(栈): 系统自由分配, 速度快。
Heap (堆): 速度慢, 容易产生内存碎片。
5. 存储内容:
Strack(栈):
第一进栈 :
主函数中的下一条指令的地址=>函数的各个参数,参数由右往左进栈。=>函数的局部变量(静态变量不入栈) 。
出栈:调用结束后, 出栈顺序相反, 局部变量先出栈。
Heap(堆): 程序员自己安排
6. 分配方式
Strack(栈): 栈有两种分配方式, 静态分配和动态分配。
静态分配是编译器完成的, 比如局部变量的分配,
动态分配由 alloca 函数进行分配, 但栈的动态分配和堆是不同的, 栈的动态内存由编译器进行释放, 无需手工实现。
Heap(堆): 堆都是动态分配的, 没有静态分配的堆。