2024年10月16日 09:59

This commit is contained in:
许大仙 2024-10-16 01:59:59 +00:00
parent 19c2b3eba2
commit cb44321003
3 changed files with 124 additions and 0 deletions

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 271 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 271 KiB

View File

@ -2014,3 +2014,119 @@ int main()
> >
> * ① 在用字符串给字符数组赋值时,要保证数组长度大于字符串长度,以容纳结束符`'\0'`。 > * ① 在用字符串给字符数组赋值时,要保证数组长度大于字符串长度,以容纳结束符`'\0'`。
> * ② `数组溢出`通常发生在动态分配内存或者通过不安全的函数(如: `strcpy`)进行字符串操作。 > * ② `数组溢出`通常发生在动态分配内存或者通过不安全的函数(如: `strcpy`)进行字符串操作。
# 第七章C 语言中的数组 VS Java 语言中的数组(⭐)
## 7.1 Linux 下 32 位环境的用户空间内存分布情况
* 对于 32 位的环境而言,理论上程序是可以拥有 4GB 的虚拟地址空间的,在 C 语言中使用到的变量、函数、字符串等都会对应内存中的一块区域。
* 但是,在这 4GB 的地址空间中,要拿出一部分给操作系统内核使用,应用程序无法直接访问这一段内存,这一部分内存地址被称为`内核空间`Kernel Space
> [!NOTE]
>
> - ① Windows 在默认情况下会将高地址的 2GB 空间分配给内核(也可以配置为 1GB
> - ② 而 Linux 默认情况下会将高地址的 1GB 空间分配给内核。
* 也就是说,应用程序只能使用剩下的 2GB 或 3GB 的地址空间,称为`用户空间`User Space
* Linux 下 32 位环境的经典内存模型,如下所示:
![](./assets/43.svg)
* 各个内存分区的说明,如下所示:
| 内存分区 | 说明 |
| :------------------------ | :----------------------------------------------------------- |
| 程序代码区code | 存储程序的执行代码,通常为只读区,包含程序的指令。 程序启动时,这部分内存被加载到内存中,并不会在程序执行期间改变。 |
| 常量区constant | 存放程序中定义的常量值,通常也是只读的,这些常量在程序运行期间不可修改。 |
| 全局数据区global data | 存储程序中定义的全局变量和静态变量。 这些变量在程序的整个生命周期内存在,且可以被修改。 |
| 堆区heap | 用于动态分配内存,例如:通过 `malloc``new` 分配的内存块。 堆区的内存由程序员手动管理,负责分配和释放。 如果程序员不释放,程序运行结束时由操作系统回收。 |
| 动态链接库 | 动态链接库(如: `.dll``.so` 文件)被加载到内存中特定的区域,供程序运行时使用。 |
| 栈区stack | 用于存储函数调用的局部变量、函数参数和返回地址。 栈是自动管理的,随着函数的调用和返回,栈上的内存会自动分配和释放。 |
> [!NOTE]
>
> - ① 程序代码区、常量区、全局数据区在程序加载到内存后就分配好了,并且在程序运行期间一直存在,不能销毁也不能增加(大小已被固定),只能等到程序运行结束后由操作系统收回,所以全局变量、字符串常量等在程序的任何地方都能访问,因为它们的内存一直都在。
> - ② 函数被调用时,会将参数、局部变量、返回地址等与函数相关的信息压入栈中,函数执行结束后,这些信息都将被销毁。所以局部变量、参数只在当前函数中有效,不能传递到函数外部,因为它们的内存不在了。
> - ③ 常量区、全局数据区、栈上的内存由系统自动分配和释放不能由程序员控制。程序员唯一能控制的内存区域就是堆Heap它是一块巨大的内存空间常常占据整个虚拟空间的绝大部分在这片空间中程序可以申请一块内存并自由地使用放入任何数据。堆内存在程序主动释放之前会一直存在不随函数的结束而失效。在函数内部产生的数据只要放到堆中就可以在函数外部使用。
## 7.2 C 语言中的数组
* 之前,我们都是这么使用数组的,如下所示:
```c
#include <stdio.h>
int main() {
// 定义数组和全部初始化:数组初始化的元素个数等于数组的长度。
int arr[5] = {1, 2, 3, 4, 5};
return 0;
}
```
* 其实,这样定义的数组是在`栈`中的,而栈的内存空间是有限的,如果数组中的元素过多,将会出现 `Stack Overflow` 的现象,即:栈溢出。
> [!NOTE]
>
> * ① 栈内存的大小和编译器有关,编译器会为栈内存制定一个最大值。
> * ② 在 VS 中,默认是 1MB在 GCC 下,默认是 8 MB。
> * ③ 虽然可以通过参数来修改栈内存的大小;但是,在实际开发中,我们一般不会这么做。
* 所以,在实际开发中,如果我们要使用数组,就需要在`堆`中开辟内存空间,因为堆中的内存空间是可以动态扩容和缩容的,只不多在 C 语言中对于堆中申请的内存空间,需要程序员在用完之后,手动释放掉;否则,将会造成内存泄漏现象。
```c
#include <stdio.h>
#include <stdlib.h>
int main() {
int n; // 数组的大小
printf("请输入数组的大小: ");
scanf("%d", &n);
// 使用 malloc 申请内存,申请 n 个 int 类型的空间
int *array = (int *)malloc(n * sizeof(int));
// 检查 malloc 是否成功
if (array == NULL) {
printf("内存分配失败!\n");
return 1; // 程序退出
}
// 初始化数组并输出
for (int i = 0; i < n; i++) {
array[i] = i + 1; // 简单赋值操作
printf("array[%d] = %d\n", i, array[i]);
}
// 使用完毕后,释放内存
free(array);
return 0;
}
```
## 7.3 Java 语言中的数组
* Java 语言和 C 语言不同Java 语言从语法层面就将数组在内存中的开辟放到了`堆`中。
```c
public class Test {
public static void main(String[] args){
// 在堆内存开辟数组,使用完毕后,不需要手动回收对应的内存空间
int[] arr = new int[4] ;
}
}
```
> [!NOTE]
>
> * ① 在 Java 语言中,数组的内存分配是由 JVMJava Virtual MachineJava 虚拟机)自动管理的,开发者不需要像在 C 语言中那样手动调用 `malloc` 来申请内存。Java 提供了更加高级的内存管理机制,所有数组在堆中动态分配。
> * ② 在 Java 中声明和初始化数组的过程本质上就是在堆内存中分配数组内存的过程。每个数组在创建时都会被分配到堆中并且由垃圾回收机制Garbage CollectorGC自动负责内存的回收。
> * ③ 我们甚至可以理解为Java 语言是 C 语言的最佳实践版本。