C 语言的内存模型

C 语言的内存模型

C语言是一门比较偏底层的语言,所以它的内存模型与操作系统的一些东西(进程)的内存模型相同,了解了C语言的内存模型对以后的学习很有帮助。

简单内存模型

请添加图片描述

  • 栈(stack):存放程序中的局部变量(但不包括static声明的变量,static变量放在静态常量区中)。同时,在函数被调用时,栈用来传递参数和返回值。由于栈先进后出特点。所以栈特别方便用来保存/恢复调用现场(适合递归)。

  • 堆(heap):用来存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc分配内存时,新分配的内存就被动态添加到堆上,当进程调用free释放内存时,会从堆中剔除。

  • 静态常量区:用于存放一些全局变量,静态变量,字符串常量…

int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
	static int staticVar = 1;
	int localVar = 1;
	int num1[10] = { 1, 2, 3, 4 };
	char char2[] = "abcd";
	const char* pChar3 = "abcd";
	int* ptr1 = (int*)malloc(sizeof(int) * 4);
	int* ptr2 = (int*)calloc(4, sizeof(int));
	int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
	free(ptr1);
    free(ptr2);
	free(ptr3);
}

接下来看一下详细的C内存模型

C内存模型

请添加图片描述

kernel

  1. 进程管理:内核负责管理计算机系统中的进程(process)。它创建、调度和终止进程,分配和管理进程所需的资源,以及处理进程间的通信和同步。

  2. 内存管理:内核负责管理计算机系统的物理内存和虚拟内存。它分配和回收内存空间,将虚拟地址映射到物理地址,处理页面置换和内存分页等操作。

  3. 文件系统管理:内核提供文件系统的管理功能。它负责文件和目录的创建、修改和删除,以及文件的读取和写入操作。内核还管理文件的访问权限和文件的元数据信息。

  4. 设备管理:内核负责管理计算机系统中的硬件设备。它提供设备驱动程序,用于与硬件设备进行通信和控制。内核管理设备的初始化、中断处理、设备驱动程序的加载和卸载等操作。

  5. 系统调用接口:内核提供系统调用接口,允许用户程序通过特定的函数调用请求内核提供的服务。这些服务包括文件操作、进程管理、网络通信等,用户程序可以通过系统调用接口与内核进行交互。

总之,内核是 C 内存模型中的关键组成部分,它负责管理系统的资源、提供对硬件的访问接口,并提供系统调用接口供用户程序使用。内核在操作系统中扮演着非常重要的角色,确保计算机系统的正常运行和资源的有效管理。

请添加图片描述

ulimit -s//查看栈的大小

栈的特点

  1. 后进先出(LIFO):栈遵循后进先出的原则,最后进入栈的元素首先被访问和移除。

  2. 有限大小:栈的大小是有限的,通常由操作系统或编程语言定义。当栈的容量达到上限时,继续向栈中添加元素会导致栈溢出。

  3. 自动分配和释放:栈上的内存空间由编译器自动分配和释放。当函数被调用时,函数的局部变量和函数调用的上下文信息(如返回地址、参数等)被分配在栈上。当函数执行完毕时,栈上的这些数据会被自动释放。

栈在程序中的主要作用是管理函数的调用和返回。当一个函数被调用时,它的局部变量和其他相关信息被压入栈中,函数执行完毕后,这些信息被弹出栈。这样可以确保函数的局部变量和上下文信息在函数调用过程中的正确性和独立性。

此外,栈还可以用于存储临时数据、递归算法、表达式求值等。栈的大小相对较小,但访问速度较快,因此在需要快速分配和释放内存的场景下,栈是一个常用的数据结构。

我们在解决问题时,可试着去模拟栈,可以用线性表和链表去模拟。

栈帧

每一次函数的调用,都会在调用栈(call stack)上维护一个独立的栈帧空间(stack frame),每个独立的栈帧一般包括:

  • 函数的返回地址和参数
  • 临时变量: 包括函数的非静态局部变量以及编译器自动生成的其他临时变量
  • rsprbp这两个寄存器中存放的是地址,这两个地址是用来确认各变量,用来维护函数栈帧的
  • rbp(栈底指针):该指针永远指向系统栈最上面一个栈帧的底部
  • rsp(栈顶指针):该指针永远指向系统栈最上面一个栈帧的栈顶
  • 栈是从高地址向低地址延伸,一个函数的栈帧用rbprsp 这两个寄存器来划定范围,rbp 指向当前的栈帧的底部,rsp 始终指向栈帧的顶部
  • 压栈pushrsp上移朝低地址移动;出栈pop:栈顶元素弹出,rsp下移高地址

堆(Heap)是计算机内存中用于动态分配内存的一部分。它是在程序运行时动态分配和释放内存的区域,用于存储程序运行时创建的对象、数据结构和动态分配的内存块。

堆的特点

  1. 动态分配:堆内存的大小可以在程序运行时动态地增长或缩小,根据程序的需要进行内存分配和释放。

  2. 随机访问:堆中的内存块可以通过指针进行随机访问,程序可以根据需要在堆中分配和访问任意大小的内存块。

  3. 持久性:堆中分配的内存块在分配后会一直存在,直到显式释放或程序结束。

  4. 不连续分配:堆中的内存块不一定是连续的,可以是散布在堆内存区域的不同位置。

在大多数编程语言中,如C、C++、Java等,提供了堆内存的动态分配和释放机制。程序可以使用特定的函数或操作符(如mallocnew等)来在堆中分配内存,并使用相应的函数或操作符(如freedelete等)来释放已分配的内存。

mallocfree的原理

1)当开辟的空间小于 128K 时,调用 brk()函数,malloc 的底层实现是系统调用函数 brk(),其主要移动指针 _enddata(此时的 _enddata 指的是 Linux 地址空间中堆段的末尾地址,不是数据段的末尾地址)

int brk(void *addr);
void *sbrk(intptr_t increment);

brk 用于返回堆的顶部地址;sbrk 用于扩展堆,通过参数 increment 指定要增加的大小,如果扩展成功,返回 brk 的旧值。如果 increment 为零,返回 brk 的当前值。

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    void *current_brk;
    int *dynamic_memory;

    // 获取当前的 brk 指针位置
    current_brk = sbrk(0);
    if (current_brk == (void *)-1) {
        perror("sbrk");
        exit(1);
    }

    printf("Current brk pointer: %p\n", current_brk);

    // 扩展堆空间
    if (sbrk(sizeof(int)) == (void *)-1) {
        perror("sbrk");
        exit(1);
    }

    // 在新分配的内存中存储值
    dynamic_memory = (int *)current_brk;
    *dynamic_memory = 42;

    printf("Dynamic memory value: %d\n", *dynamic_memory);

    return 0;
}

我们不会直接通过 brk 或 sbrk 来分配堆内存,而是先通过 sbrk 扩展堆,将这部分空闲内存空间作为缓冲池,然后通过 malloc / free 管理缓冲池中的内存。这是一种池化思想,能够避免频繁的系统调用,提高程序性能。

2)当开辟的空间大于 128K 时,mmap()系统调用函数来在虚拟地址空间中(堆和栈中间,称为“文件映射区域”的地方)找一块空间来开辟。

malloc和free的简易模拟

#include <stdio.h>
#include <string.h>
#include <unistd.h>

typedef struct mallocBlock 
{
    struct mallocBlock *before;
    int size;
    int use;
} mb;

static mb *mb_last = NULL;

void *my_malloc(int size) 
{
    mb *mbp;

    for (mbp = mb_last; mbp != NULL; mbp = mbp->before) 
    {
        if (mbp->use == 0 && mbp->size >= size) 
        {
            break;
        }
    }
    if (mbp == NULL) 
    {
        mbp = sbrk(sizeof(mb) + size);
        if (mbp == (void *)-1)
            return (void *)0;
        mbp->size = size;
        mbp->before = mb_last;
        mb_last = mbp;
    }

    mbp->use = 1;
    return mbp + 1;
}

int my_free(void *p) 
{
    if (p == NULL)
        return 0;
    mb *mbp = (mb *)p - 1;
    mbp->use = 0;
    return 0;
}

int main(void) 
{
    char *a = my_malloc(1);
    char *b = my_malloc(2);
    char *c = my_malloc(3);
    my_free(a);
    my_free(b);
    my_free(c);
    printf("%p,%p,%p\n", a, b, c);
    b = my_malloc(1);
    printf("%p,%p,%p\n", a, b, c);
    return 0;
}

Valgrind的简易使用

valgrind --leak-check=yes ./a.out arg1 arg2

数据段

BSS段

BSS段储存的是未初始化的全局变量或初始化为0的全局变量, BSS段不占据执行文件空间,但占据程序运行时的内存空间。

执行期间必须将BSS段内容全部设为0。

rodata段

rodata段存储常量数据,比如程序中定义为const的全局变量,#define定义的常量,以及诸如“Hello World”的字符串常量。只读数据,存储在ROM中。

const修饰的全局变量在常量区;const修饰的局部变量只是为了防止修改,没有放入常量区。

编译器会去掉重复的字符串常量,程序的每个字符串常量只有一份。

wqdata段

data存储已经初始化的全局变量,属于静态内存分配。(注意:初始化为0的全局变量还是被保存在BSS段)

static声明的变量也存储在数据段,链接时初值加入执行文件。

代码段

text段存放程序代码,运行前就已经确定(编译时确定),通常为只读。

32位和64位的差距

32位

img

64位

img

  • 25
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
JVM(Java Virtual Machine)内存模型指的是JVM在运行时对内存的使用和管理,包括程序计数器、虚拟机栈、本地方法栈、堆和方法区等。 1. 程序计数器 程序计数器是一块较小的内存区域,可以看做是当前线程所执行的字节码的行号指示器。在虚拟机切换线程时,会将当前线程的程序计数器值保存到内存中,等待下次线程执行时再恢复。 2. 虚拟机栈 虚拟机栈是线程私有的内存区域,用于存储局部变量、方法参数、返回值和操作数栈等。每个方法在执行的时候都会创建一个栈帧,栈帧包含了方法的局部变量表、操作数栈、动态链接、方法出口等信息。 虚拟机栈的大小可以通过-Xss参数来设置。 3. 本地方法栈 本地方法栈与虚拟机栈类似,不同的是本地方法栈为本地方法服务。本地方法是使用C或C++等其他语言编写的方法,本地方法栈用于为执行本地方法时分配内存。 4. 堆 堆是JVM中最大的一块内存区域,用于存储对象实例和数组。堆的大小可以通过-Xmx和-Xms参数来设置。 堆被划分为新生代和老年代两个区域,新生代又被划分为Eden区、Survivor0区和Survivor1区。 5. 方法区 方法区用于存储类信息、常量、静态变量、即时编译器编译后的代码等。方法区的大小可以通过-XX:MaxPermSize参数来设置。 方法区也被称为永久代,但是在JDK8中,永久代被移除,取而代之的是元空间(Metaspace)。 总体来说,JVM内存模型的设计是为了更好地管理内存,提高程序的性能和稳定性。但是如果内存使用不当,就容易导致内存溢出等问题,因此需要开发人员在编写程序时注意内存的使用和管理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

marsevilspirit

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值