浅析C语言中的内存模型

简介: 浅析C语言中的内存模型

前言

近来学习深觉自己在语言的底层方面理解欠缺, 正好之前有看过一点内存模型相关的文章(虽然没看懂), 正好就花点时间研究一下这方面相关的知识。以此文整合一下自己学到的知识, 文章中有错误或不足之处还请师傅们斧正。

重点讲堆栈与堆区两个动态区的概念, 简单分析其中的区别和各自的优势。

动态区

栈区(stack)

  1. 栈区也被称为堆栈, 栈区的分配和释放都由系统自动分配, 栈区存放函数的参数值以及函数的局部变量,以及函数调用开辟的栈帧;按照程序的调用顺序依次入栈。函数结束返回时自动释放空间,栈区使用LIFO结构。 栈区的内存地址是连续且固定长度的。
    2. 在Windows中默认栈区大小上限为1M或者2M, 如果在分配内存时剩余栈的空间不足以分配通常会抛出段错误(segmentation fault)或者是缓冲区溢出(Buffer overflow)的异常报错告警, 在Linux系统中栈区的默认上限为8M。
    3. 栈是一种
    限定性线性表
    ,是数据结构的一种。将线性表的插入和删除操作限制为仅在表的一端进行,通常将表中允许进行插入、删除操作的一端称为栈顶,因此栈顶的当前位置是动态变化的,它由一个称为栈顶指针的位置指示器来指示。同时表的另一端被称为栈底当栈中没有元素时称为空栈。栈的插入操作被形象地称为进栈入栈(push),删除操作称为出栈退栈(pop)
    4. 栈指针是一个指向栈区域内部的指针,它的值是一个地址,这个地址位于栈区的下界和栈区的上界之间。栈指针把这个栈区域分为两个部分,一个是已经使用的区域,一个是没有使用的区域。
    5. 在函数调用结束后, 局部变量先出栈, 然后是参数, 最后是栈顶指针指向的地址。
    6. 栈帧保存了每一个函数的返回位置、实参、局部变量、返回值地址

image.png

堆区(heap)

  1. 堆区的内存由自己手动分配手动释放的, 如果在使用完后没有及时释放在程序运行完后将由操作系统自动回收, 堆区的内存地址通常是不连续的, 每个堆区都有一个固定8bytes长度的头部标识信息, 且由于内存对齐制度,后面的块长度如果不足8字节则补空对齐。
    (PS:看的文章有点驳杂,暂时没找到个讲的比较全又比较清晰的文章,有关内存对齐的补充可以看看下面讲malloc的文章)
    2. 堆区是一种经过排序之后的树形结构, 也就是**二叉树, ** 堆中某个节点的值总是不大于或不小于其父节点的值 。
    3. 在C语言中堆内存通常使用 中的malloccalloc函数来进行分配, 也可以在分配之后使用realloc重新分配堆区大小, 而在Java中则使用new关键字来进行分配。两者不同之处是Java会在堆内存使用完后自动回收, 而C则需要在使用完后使用freedelete(C++)函数手动进行回收,并且需要将指针置空,尽量避免野指针的出现
    4. 对堆来说,频繁分配和释放(malloc / free)不同大小的堆空间势必会造成内存空间的不连续,从而造成大量碎片,导致程序效率降低;而对栈来讲,则不会存在这个问题。 在日常编写代码时需要尽量减少使用频次,不要频繁地申请和释放内存,数据量不大或非动态内存也尽量使用栈内存。 在运行中如果内存管理没有做好的话是有可能会出现内存泄露的情况的, 会严重危害服务器运行内存和影响程序运行效率。
    功能:释放一块堆内存,不能重复释放,也不能释放非法地址,但是可以释放 NULL
    注意:释放的仅仅是使用权,里面的数据不会全部清理,是会清理前4个字节为0
    程序一旦结束属于他的所有资源都会被操作系统回收
  2. 在内存对齐之后每个内存块之间会留4个字节的长度记录malloc的维护信息,这些维护信息决定了malloc下次分配内存的位置,以及借助这个维护信息计算出每个内存块的大小,当这些信息被破坏时,就会造成堆损坏。
    6. 在使用malloc创建了一个堆后,返回的指针地址指向堆的头部地址,如果此时对指针进行地址的修改,则会造成堆损坏,且此时访问数据有可能会造成越界访问。


#include <stdio.h>
#include <stdlib.h>
int main(){
    int *p = (int *)malloc(100);  // 分配一个100字节大小的malloc堆
    p++; // 将指针往高位移动一个块, 指向了原本p[1]所在的地址, 
    printf("%d\n", &p[24]);  // 此时访问的p[24]不在malloc分配的堆块范围内, 属于非法访问, 如果访问到了被使用中的内存则会造成脏数据,
               // 但是只要不访问到一些特殊的内存地址也并不会有异常抛出
    free(p);  // 如果在此时指针偏移的情况下释放堆会造成堆损坏, 在释放内存时会检查堆块内存的完整性, 如果没有通过则会造成堆损坏, 但是不会有异常抛出
        // 此处不多深究堆损坏的原因和修复方案, 感兴趣可以自行学习。
    return 0;
}

堆区与栈区之间的区别

  1. 栈区的速度是要比堆区快的, 且因为栈区内存用完即立刻释放, 在面对某些不需要多次复用的代码时要比堆区更为可靠。
    因为访问模式使从中分配内存和取消分配内存变得微不足道(指针/整数只是递增或递减),而堆的分配或释放则涉及到更为复杂的簿记工作。而且堆栈中的每个字节都倾向于被非常频繁地重用,这意味着它倾向于被映射到处理器的高速缓存中,从而使其非常快。堆的另一个性能损失是,堆(通常是全局资源)通常必须是多线程安全的,即,每个分配和释放都必须(通常)与程序中的“所有”其他堆访问同步。
    2. 与栈区不同的是, 栈区的内存是从高位开始向低地址扩展的数据结构, 而堆区的内存是从低地址向高地址扩展的数据结构
    3. 栈区的空间是固定的, 随线程分配; 堆区的空间是动态的, 由自己手动分配和释放。

静态区

全局区(static)

  1. data段, 这些数据会在程序结束后由操作系统自动释放, data段由三部分组成
- 读写(Read & Write)数据段: 初始化过的全局变量+静态变量
 - 只读(Read Only)数据段: 未初始化过的全局变量+静态变量 (BSS段)
 - 只读(Read Only)数据段: 常量
  1. BSS段(bss segment)通常是指用来存放程序中未初始化初始化为0的全局变量的一块区域 (C语言规定未显式初始化的全局变量值默认为0)

代码块(text segment/code segment)

<br />1.   text段, 代码段就是程序中的可执行部分,直观来讲代码段就是由一个个函数的堆叠来组成的, 并且是只读的(某些架构也许会允许修改, 没去深入研究过)

常量存储区(const)

  1. 用于存放定义的文字常量与宏以及不可修改的常量的静态区域, 由系统分配和释放内存


#include <stdio.h>
int main(){
  // 此处指针s虽然存放在栈区, 但是所指向的字符串takahashi存放在常量区
  char* s = "takahashi";
  const char Str1 = "a";      // 此处Str1存放在栈区中
  char Str2 = "a";            // 此处Str2同样也存放在栈区当中
  char strings1[9] = "takahashi"  // 此处strings1存放在堆区当中
  const strings2[9] = "takahashi";// 此处strings2存放在哪个区当中?
  return 0;
}
目录
相关文章
|
2月前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
41 3
|
21天前
|
存储 编译器 程序员
【C语言】内存布局大揭秘 ! -《堆、栈和你从未听说过的内存角落》
在C语言中,内存布局是程序运行时非常重要的概念。内存布局直接影响程序的性能、稳定性和安全性。理解C程序的内存布局,有助于编写更高效和可靠的代码。本文将详细介绍C程序的内存布局,包括代码段、数据段、堆、栈等部分,并提供相关的示例和应用。
32 5
【C语言】内存布局大揭秘 ! -《堆、栈和你从未听说过的内存角落》
|
21天前
|
存储 缓存 算法
【C语言】内存管理函数详细讲解
在C语言编程中,内存管理是至关重要的。动态内存分配函数允许程序在运行时请求和释放内存,这对于处理不确定大小的数据结构至关重要。以下是C语言内存管理函数的详细讲解,包括每个函数的功能、标准格式、示例代码、代码解释及其输出。
49 6
|
24天前
|
传感器 人工智能 物联网
C 语言在计算机科学中尤其在硬件交互方面占据重要地位。本文探讨了 C 语言与硬件交互的主要方法,包括直接访问硬件寄存器、中断处理、I/O 端口操作、内存映射 I/O 和设备驱动程序开发
C 语言在计算机科学中尤其在硬件交互方面占据重要地位。本文探讨了 C 语言与硬件交互的主要方法,包括直接访问硬件寄存器、中断处理、I/O 端口操作、内存映射 I/O 和设备驱动程序开发,以及面临的挑战和未来趋势,旨在帮助读者深入了解并掌握这些关键技术。
42 6
|
1月前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
105 13
|
25天前
|
大数据 C语言
C 语言动态内存分配 —— 灵活掌控内存资源
C语言动态内存分配使程序在运行时灵活管理内存资源,通过malloc、calloc、realloc和free等函数实现内存的申请与释放,提高内存使用效率,适应不同应用场景需求。
|
1月前
|
存储 编译器 数据处理
C 语言结构体与位域:高效数据组织与内存优化
C语言中的结构体与位域是实现高效数据组织和内存优化的重要工具。结构体允许将不同类型的数据组合成一个整体,而位域则进一步允许对结构体成员的位进行精细控制,以节省内存空间。两者结合使用,可在嵌入式系统等资源受限环境中发挥巨大作用。
60 11
|
25天前
|
存储 算法 程序员
C 语言指针详解 —— 内存操控的魔法棒
《C 语言指针详解》深入浅出地讲解了指针的概念、使用方法及其在内存操作中的重要作用,被誉为程序员手中的“内存操控魔法棒”。本书适合C语言初学者及希望深化理解指针机制的开发者阅读。
|
1月前
|
存储 C语言 开发者
C 语言指针与内存管理
C语言中的指针与内存管理是编程的核心概念。指针用于存储变量的内存地址,实现数据的间接访问和操作;内存管理涉及动态分配(如malloc、free函数)和释放内存,确保程序高效运行并避免内存泄漏。掌握这两者对于编写高质量的C语言程序至关重要。
52 11
|
23天前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
54 1