存储类别、链接和内存管理
存储类修饰符
- 变量作用域基本概念
- 变量作用域:变量的可用范围
- 按照作用域的不同,变量可以分为:局部变量和全局变量
- 局部变量
- 定义在函数内部的变量以及函数的形参, 我们称为局部变量
- 作用域:从定义的那一行开始, 直到遇到
}结束或者遇到return返回为止 - 生命周期: 从程序运行到定义哪一行开始分配存储空间到程序离开该变量所在的作用域
- 存储位置: 局部变量会存储在内存的栈区中
- 特点:
- 相同作用域内不可以定义同名变量
- 不同作用范围可以定义同名变量,内部作用域的变量会覆盖外部作用域的变量
- 全局变量
- 定义在函数外面的变量称为全局变量
- 作用域范围:从定义哪行开始直到文件结尾
- 生命周期:程序一启动就会分配存储空间,直到程序结束
- 存储位置:静态存储区
- 特点: 多个同名的全局变量指向同一块存储空间
static 关键字
- 对局部变量的作用
- 延长局部变量的生命周期,从程序启动到程序退出,但是它并没有改变变量的作用域
- 定义变量的代码在整个程序运行期间仅仅会执行一次
#include <stdio.h>
void test();
int main()
{
test();
test();
test();
return 0;
}
void test(){
static int num = 0; // 局部变量
num++;
// 如果不加static输出 1 1 1
// 如果添加static输出 1 2 3
printf("num = %i\n", num);
}
- 对全局变量的作用
- 全局变量分类:
- 内部变量:只能在本文件中访问的变量
- 外部变量:可以在其他文件中访问的变量,默认所有全局变量都是外部变量
- 默认情况下多个同名的全局变量共享一块空间, 这样会导致全局变量污染问题
- 如果想让某个全局变量只在某个文件中使用, 并且不和其他文件中同名全局变量共享同一块存储空间, 那么就可以使用 static
// A文件中的代码
int num; // 和B文件中的num共享
void test(){
printf("ds.c中的 num = %i\n", num);
}
// B文件中的代码
#include <stdio.h>
#include "ds.h"
int num; // 和A文件中的num共享
int main()
{
num = 666;
test(); // test中输出666
return 0;
}
// A文件中的代码
static int num; // 不和B文件中的num共享
void test(){
printf("ds.c中的 num = %i\n", num);
}
// B文 件中的代码
#include <stdio.h>
#include "ds.h"
int num; // 不和A文件中的num共享
int main()
{
num = 666;
test(); // test中输出0
return 0;
}
extern 关键字
- 对局部变量的作用
- extern 不能用于局部变量
- extern 代表声明一个变量, 而不是定义一个变量, 变量只有定义才会开辟存储空间
- 所以如果是局部变量, 虽然提前声明有某个局部变量, 但是局部变量只有执行到才会分配存储空间
#include <stdio.h>
int main()
{
extern int num;
num = 998; // 使用时并没有存储空间可用, 所以声明了也没用
int num; // 这里才会开辟
printf("num = %i\n", num);
return 0;
}
- 对全局变量的作用
- 声明一个全局变量, 代表告诉编译器我在其它地方定义了这个变量, 你可以放心使用
#include <stdio.h>
int main()
{
extern int num; // 声明我们有名称叫做num变量
num = 998; // 使用时已经有对应的存储空间
printf("num = %i\n", num);
return 0;
}
int num; // 全局变量, 程序启动就会分配存储空间
static 与 extern 对函数的作用
-
内部函数:只能在本文件中访问的函数
-
外部函数:可以在本文件中以及其他的文件中访问的函数
-
默认情况下所有的函数都是外部函数
-
static 作用
-
声明一个内部函数
static int sum(int num1,int num2);
- 定义一个内部函数
static int sum(int num1,int num2)
{
return num1 + num2;
}
- extern 作用
- 声明一个外部函数
extern int sum(int num1,int num2);
- 定义一个外部函数
extern int sum(int num1,int num2)
{
return num1 + num2;
}
- 注意点:
- 由于默认情况下所有的函数都是外部函数, 所以 extern 一般会省略
- 如果只有函数声明添加了 static 与 extern, 而定义中没有添加 static 与 extern, 那么无效
auto
- 设计目的:显式标注“自动存储期”(进入作用域分配、离开作用域释放),用于与
static的“静态存储期”区分。现代 C 中局部变量默认即为auto,通常无需显式书写。 - 基本示例:
#include <stdio.h>
void demo_auto(void) {
auto int a = 1; // 等价于 int a = 1;
int b = 2; // 局部变量默认就是 auto
printf("%d %d\n", a, b);
}
void counter_auto(void) {
auto int c = 0; // 每次调用都会重新分配,输出始终为 1
c++;
printf("%d\n", c);
}
void counter_static(void) {
static int c = 0; // 静态存储期,跨调用保留值:1, 2, 3...
c++;
printf("%d\n", c);
}
register
- 设计目的:向编译器“建议”把高频使用的局部标量放入 CPU 寄存器以减少内存访问。只是提示而非强制;现代优化器通常会自行决定是否放入寄存器。
- 限制:不能对
register变量取地址(不允许&var)。 - 基本示例:
#include <stdio.h>
void sum_n(int n) {
register int i; // 建议放入寄存器
int s = 0;
for (i = 0; i < n; i++) {
s += i;
}
printf("%d\n", s);
// int *p = &i; // 编译错误:不能取得 register 变量地址
}
内存管理
进程空间
- 程序,是经源码编译后的可执行文件,可执行文件可以多次被执行,比如我们可以多次打开 office。
- 而进程,是程序加载到内存后开始执行,至执行结束,这样一段时间概念,多次打开的 wps,每打开一次都是一个进程,当我们每关闭一个 office,则表示该进程结束。
- 程序是静态概念,而进程动态/时间概念。 ###进程空间图示
有了进程和程序的概念以后,我们再来看一下,程序被加载到内存以后内存空间布局是什么样的

栈内存(Stack)
- 栈中存放任意类型的变量,但必须是 auto 类型修饰的,即自动类型的局部变量, 随用随开,用完即消。
- 内存的分配和销毁系统自动完成,不需要人工干预
- 栈的最大尺寸固定,超出则引起栈溢出
- 局部变量过多,过大 或 递归层数太多等就会导致栈溢出
int ages[10240*10240]; // 程序会崩溃, 栈溢出
#include <stdio.h>
int main()
{
// 存储在栈中, 内存地址从大到小
int a = 10;
int b = 20;
printf("&a = %p\n", &a); // &a = 0060FEAC
printf("&b = %p\n", &b); // &b = 0060FEA8
return 0;
}