diff --git a/docs/notes/02_c-leap/05_xdx/assets/5.svg+xml b/docs/notes/02_c-leap/05_xdx/assets/5.svg+xml new file mode 100644 index 0000000..f621490 --- /dev/null +++ b/docs/notes/02_c-leap/05_xdx/assets/5.svg+xml @@ -0,0 +1,4 @@ + + + +
保留区域
程序代码区
常量区
(一般常量、字符串常量)
全局数据区
(全局变量、静态变量)
堆(向上增长)
未被分配的内存
动态链接库
未被分配的内存
栈(向下增长)
内核空间
0x00000000
0x08048000
0x40000000
0xc0000000
0xffffffff
虚拟内存地址,
由小到大
这部分内存会随着程序的运行不断分配和释放
这部分内存在程序运行期间会一直存在
可读写数据
只读数据
可读写数据
虚拟内存地址
虚拟内存
\ No newline at end of file diff --git a/docs/notes/02_c-leap/05_xdx/assets/7.svg b/docs/notes/02_c-leap/05_xdx/assets/7.svg new file mode 100644 index 0000000..f621490 --- /dev/null +++ b/docs/notes/02_c-leap/05_xdx/assets/7.svg @@ -0,0 +1,4 @@ + + + +
保留区域
程序代码区
常量区
(一般常量、字符串常量)
全局数据区
(全局变量、静态变量)
堆(向上增长)
未被分配的内存
动态链接库
未被分配的内存
栈(向下增长)
内核空间
0x00000000
0x08048000
0x40000000
0xc0000000
0xffffffff
虚拟内存地址,
由小到大
这部分内存会随着程序的运行不断分配和释放
这部分内存在程序运行期间会一直存在
可读写数据
只读数据
可读写数据
虚拟内存地址
虚拟内存
\ No newline at end of file diff --git a/docs/notes/02_c-leap/05_xdx/index.md b/docs/notes/02_c-leap/05_xdx/index.md index 3492bbf..5547970 100644 --- a/docs/notes/02_c-leap/05_xdx/index.md +++ b/docs/notes/02_c-leap/05_xdx/index.md @@ -1718,6 +1718,11 @@ int main() { * 在函数和代码块(分支语句、循环语句等)以外定义的变量、标识符常量、数组等具有全局作用域,在程序的任何地方都可以被访问,通常称它们为`全局变量`、全局常量、全局数组等。 +> [!NOTE] +> +> * ① 可以利用全局变量进行函数间的数据传递,简单而运行效率高。 +> * ② 全局变量使用过多增加了函数间联系的复杂性,降低了函数的独立性。 + * 示例: @@ -1766,13 +1771,151 @@ int main() { } ``` +### 3.1.4 局部变量 VS 全局变量 +* 回顾 Linux 32 位环境用户空间的内存分布情况,如下所示: + +![](./assets/7.svg) + +* 作用域对比: + +| 类别 | 作用域 | +| -------- | ------------------------------------------------------------ | +| 局部变量 | 它的作用域只能在其定义的函数或代码块内部,超出该范围将无法访问。 | +| 全局变量 | 它的作用域默认是整个程序,也就是所有的代码文件。 | + +* 访问权限对比: + +| 类别 | 作用域 | +| -------- | ------------------------------------------------------------ | +| 局部变量 | 由于局部变量的作用域仅限于定义它们的函数或代码块,只有在该范围内才能访问它们。
其他函数无法直接访问局部变量。 | +| 全局变量 | 全局变量可以被程序中的任何函数访问,只要它们在被访问之前已经被声明。 | + +* 生命周期对比: + +| 类别 | 作用域 | +| -------- | ------------------------------------------------------------ | +| 局部变量 | 局部变量的生存周期仅限于定义它们的函数或代码块的执行时间。
它们在函数或代码块执行结束后会被销毁。 | +| 全局变量 | 全局变量的生命周期从程序开始运行直到程序结束。
它们在程序整个运行期间都存在。 | + +* 初始值对比: + +| 类别 | 作用域 | +| -------- | ------------------------------------------------------------ | +| 局部变量 | 系统不会对其默认初始化,必须对局部变量初始化后 才能使用。
否则,程序运行后可能会异常退出。 | +| 全局变量 | 如果没有显式初始化,它们会被自动、默认初始化为 零或空值,具体取决于数据类型。
int 类型的默认初始化值是 0 。
char 类型的默认初始化值是 '\0' 或 0 。
float 类型的默认初始化值是 0.0f 。
double 类型的默认初始化值是 0.0 。
指针的默认初始化值是 NULL 或 nullptr 。 | + +* 内存中的位置对比: + +| 类别 | 作用域 | +| -------- | ------------------------------------------------------ | +| 局部变量 | 保存在`栈`中,函数被调用时才动态地为变量分配存储单元。 | +| 全局变量 | 保存在内存的`全局存储区` 中,占用静态的存储单元。 | + +### 3.1.5 全局变量的使用建议 + +* 全局变量的使用建议是:`非必要不要使用全局变量`。 + +> [!NOTE] +> +> * ① `占用内存时间长` :全局变量在程序的全部执行过程中都占用存储单元,而不是仅在需要时才开辟单元。 +> * ② `降低了函数、程序的可靠性和通用性` :如果在函数中引用了全局变量,那么执行情况会受到有关的外部变量的影响;如果将一个函数移到另一个文件中,还要考虑把有关的外部变量及其值一起移过去。但是若该外部变量与其它文件的变量同名时,就会出现问题。一般要求把 C 程序中的函数做成一个相对的封闭体,除了可以通过 “实参—形参”的渠道与外界发生联系外,没有其它渠道。这样的程序移植性好,可读性强。 +> * ③ `程序容易出错` :使用全局变量过多,人们往往难以清楚地判断 出每个瞬时各个外部变量的值。由于在各个函数执行时都可能改变外部变量的值,程序容易出错。因此,要限制使用全局变量。 ## 3.2 按照存储方式不同分类 +### 3.2.1 概述 + +* 在 C 语言中,每一个变量都有两个属性: `数据类型` 和 `数据的存储类别` 。`存储类别`指的是数据在内存中存储的方式,如:`静态存储`和`动态存储`。在声明变量时,一般应同时指定其数据类型和存储类别,也可以采用默认方式指定(即如果用户不指定,系统会隐含地指定为某一种存储类别)。 +* 变量的存储有两种不同的方式:`静态存储方式`和`动态存储方式`。 + +### 3.2.2 动态(自动)存储方式 + +* 动态存储方式:在程序运行期间根据需要进行`动态的分配存储空间`的方式,数据存放在`动态存储区` ,即:`栈`。 +* 在动态存储区中存放以下数据: + * 函数形参:在调用函数时给形参分配存储空间。 + * 函数中定义的局部变量且没有用关键字 static 声明的变量,即自动变量。 + * 函数调用时的返回地址等。 + +* 在调用该函数时,系统会给这些变量分配存储空间,在函数调用结束时就自动释放这些存储空间。因此这类局部变量称为自动变量。 自动变量用关键字 auto 作存储类别的声明。 +* 语法: + +```c +auto 数据类型 变量 = 初始化值; // 等价于 数据类型 变量名 = 初始化值; +``` + +* 实际上,关键字 `auto` 可以省略,不写 `auto` 则隐含指定为“自动存储类 别”,它属于动态存储方式。程序中大多数变量属于自动变量。每个函数中的局部变量的生命周期与函数的执行周期相匹配。 +* 如果在一个程序中两次调用同一函数,而在此函数中定义了局部变量,在两次调用时,函数的内部变量都会重新初始化,不会保留上一次运行的值。分配给这些局部变量的存储空间的地址可能是不相同的。 + +### 3.2.3 静态存储方式 + +* 静态存储方式:在程序运行期间数据存放在`静态存储区` 。它们在程序整个运行期间都不释放,故生命周期存在于程序的整个运行过程。 + +* `局部变量`使用 `static` 修饰之后,使用静态存储方式。 + + * 有时希望函数中的局部变量的值在函数调用结束后不消失而继续保留原值,即其占用的存储单元不释放,在下一次再调用该函数时,该变量已有值(就是上一次函数调用结束时的值)。这时就应该指定该局部变量为“静态局部变量”,用关键字 `static` 进行声明。 + * 静态局部变量在声明时未赋初值,编译器也会把它初始化为 0。 + + ```c + static int a; + // 等同于 + static int a = 0; + ``` + +* `全局变量`大多存放在静态存储区中(不包括 extern 修饰和 malloc 函数分配的方式),在程序开始执行时给全局变量分配存储区,程序执行完毕就释放。在程序执行过程中它们占据固定的存储单元,而不是动态地进行分配和释放。 + + * 普通全局变量对整个工程可见,其他文件可以使用 extern 外部声明后直接使用。也就是说其他文件不能再定义一个与其相同名字的变量了(否则编译器会认为它们是同一个变量)。 + * 而全局变量使用 static 修饰,则称为静态全局变量,静态全局变量仅对当前文件可见 ,其他文件不可访问,其他文件可以定义与其同名的变量,两者互不影响。定义不需要与其他文件共享的全局变量时,加上 static 关键字能够有效地降低程序模块之间的耦合,避免不同文件同名变量的冲突,且不会误使用。 + +> [!CAUTION] +> +> * ① 虽然静态局部变量在函数调用结束后仍然存在,但其它函数 是不能引用它的。因为它是局部变量,只能被本函数引用,而不能被其它函数引用。 +> * ② 如果在定义局部变量时不赋初值的话,则对静态局部变量来 说,编译时自动赋初值 0(对数值型变量)或空字符 ′\0′(对字符变量)。而对自动变量来说,它的值是一个不确定的值。这是由于每次函数调用结束后存储单元已释放,下次调用时又重新另分配存储单元,而所分配的单元中的内容是不可知的。 + +> [!TIP] +> +> 若非必要,不要频繁使用静态局部变量,原因如下: +> +> * ① 静态存储要久占内存(长期占用不释放,而不能像动态存储那样一个存储单元可以先后为多个变量使用,节约内存)。 +> * ② 降低了程序的可读性,当调用次数多时往往弄不清静态局部变量的当前值是什么。 +* 示例: + +```c +#include + +void nonStaticFun() { + int n = 10; // 动态存储方式 + printf("n=%d\n", n); + n++; + printf("n++=%d\n", n); +} +void staticFun() { + static int n = 10; // 静态存储方式 + printf("static n=%d\n", n); + n++; + printf("n++=%d\n", n); +} + +// 主函数 +int main() { + + // 禁用 stdout 缓冲区 + setbuf(stdout, nullptr); + + nonStaticFun(); + staticFun(); + + printf("\n\n"); + + nonStaticFun(); + staticFun(); + + return 0; +} +```