avatar

zian

A text-focused Halo theme

  • Java
  • 面试
  • 首页
  • C语音
  • liunx
  • 数据结构与算法
  • 控制台
Home C语言 内存
文章

C语言 内存

Posted 2024-12-28 Updated 2025-01- 6
By Administrator
24~31 min read

内存

  1. 什么是内存
    1. 软件在运行是,临时用来存储数据的
    2. 操作系统会将内存按照字节划分为 N 多个小格子
  2. 什么是内存地址
    1. 其实就是格子的编号
    2. 32 位操作系统:以 32 位的二进制表示
    3. 64 位操作系统:以 64 位的二进制表示
  3. 内存地址的作用
    1. 快速操作内存中存储的数据
  4. C 语言中如何获取内存地址
    1. &变量名
    2. 获取的内存地址值,其实是首地址,就是一个数据的第一个字节的内存地址
  5. 数组内存地址
    1. 是第一个元素的第一个字节空间,也就是首地址
    2. 索引:偏移量
  6. 如何内存中存储数据
    1. 确定首地址
    2. 确定数据的数据类型

内存管理

任何一个程序,正常运行都需要内存资源,用来存放诸如变量、常量、函数代码等等。这些不同的内容,所存储的内存区域是不同的,且不同的区域有不同的特性。因此我们需要研究清楚内存布局,逐个了解不同内存区域的特性。

虚拟内存

每个C语言进程都拥有一片结构相同的虚拟内存,所谓的虚拟内存,就是从实际物理内存映射出来的地址规范范围,最重要的特征是所有的虚拟内存布局都是相同的,极大地方便内核管理不同的进程。例如三个完全不相干的进程p1、p2、p3,它们很显然会占据不同区段的物理内存,但经过系统的变换和映射,它们的虚拟内存的布局是完全一样的。

PM:Physical Memory,物理内存。
VM:Virtual Memory,虚拟内存。

虚拟内存.jpeg

可以开启两个终端分别运行 demo1 和 demo2 打印虚拟内存地址相同,存储的值不一样;

理解:

  1. 将内存中一块空间拿出来给程序运行(内存是独立)
  2. 将拿出中的内存空间,映射成一个内存结构(这个就是虚拟内存)
  3. 虚拟内存,地址值是从 0 开始的。

内存布局

将其中一个C语言进程的虚拟内存放大来看,会发现其内部包下区域:

  • 栈(stack)
  • 堆(heap)
  • 数据段
  • 代码段

进程内存结构.jpeg

虚拟内存中,内核区段对于应用程序而言是禁闭的,它们用于存放操作系统的关键性代码,另外由于 Linux 系统的历史性原因,在虚拟内存的最底端 0x0 ~ 0x0804 8000 之间也有一段禁闭的区段,该区段也是不可访问的。

栈内存

什么东西存储在栈内存中?

  • 环境变量
  • 命令行参数
  • 局部变量(包括形参)

栈内存有什么特点?

  • 空间有限,尤其在嵌入式环境下。因此不可以用来存储尺寸太大的变量。
  • 每当一个函数被调用,栈就会向下增长一段,用以存储该函数的局部变量。(每一个函数都自己独立的空间)
  • 每当一个函数退出,栈就会向上缩减一段,将该函数的局部变量所占内存归还给系统。(系统控制)

注意:
栈内存的分配和释放,都是由系统规定的,我们无法干预。

堆内存

堆内存(heap)又被称为动态内存、自由内存,简称堆。堆是唯一可被开发者自定义的区段,开发者可以根据需要申请内存的大小、决定使用的时间长短等。但又由于这是一块系统“飞地”,所有的细节均由开发者自己把握,系统不对此做任何干预,给予开发者绝对的“自由”,但也正因如此,对开发者的内存管理提出了很高的要求。对堆内存的合理使用,几乎是软件开发中的一个永恒的话题。

堆内存基本特征:

  • 相比栈内存,堆的总大小仅受限于物理内存,在物理内存允许的范围内,系统对堆内存的申请不做限制。
  • 相比栈内存,堆内存从下往上增长。
  • 堆内存是匿名的,只能由指针来访问。
  • 自定义分配的堆内存,除非开发者主动释放,否则永不释放,直到程序退出。

总结:

项目 栈内存 堆内存
控制者 系统 开发者
访问方式 变量和指针 指针
大小限制 有限 受物理内存限制
存储数据 局部变量、形参、环境变量、命令行参数 数据结构、大型数据集合
释放方式 系统自动释放 手动释放或者程序退出
适用场景 函数调用、临时数据存储 大型数据结构、动态数据存储

数据段与代码段

数据段

  1. .bss 段:存放未初始化的静态数据和初始化的全局变量,它们将被系统自动初始化为0
    #include <stdio.h>
    // .bss 特点:由系统自动初始为 0 
    // .bss 2. 未初始化的全局变量
    int globalArr[3];
    
    int main(int argc, const char* argv[]) {
    
      // .bss  1. 未初始化的静态变量
      static int arr[3];
      for ( int i = 0; i < 3; i++ ) {
        printf("%d \n", arr[i]);
      }
    
    
      for ( int i = 0; i < 3; i++ ) {
        printf("%d \n", globalArr[i]);
      }
    
    
      return 0;
    }
    
    
  2. .data段:存放已初始化的静态数据和已初始化的全局变量
    #include <stdio.h>
    
    // .bss 2. 已初始化的全局变量
    int globalArr[3] = { 1 , 2 ,3 };
    int main(int argc, const char* argv[]) {
    
      // .data 段:1. 以初始的静态变量
      static int arr[3] = { 1 ,2 , 3 };
      for ( int i = 0; i < 3; i++ ) {
        printf("%d \n", arr[i]);
      }
    
      for ( int i = 0; i < 3; i++ ) {
        printf("%d \n", globalArr[i]);
      }
      return 0;
    }
    
    
  3. .rodata(Read-Only Data) 段:存放常量数据,如字符串常量、const 修饰的全局变量,这些数据在程序执行期间不被修改,因此被存放在只读段中
    #include <stdio.h>
    // const 修饰的全局变量
    const int a = 10;
    
    const int b = 10;
    
    int main(int argc, const char* argv[]) {
    
      // .rodata 段:只读数据,可复用数据
      //  1. 字符串常量
      char* str = "123";
    
      char* str2 = "123";
      printf("%p \n", str);
      printf("%p \n", str2);
    
      // a 和 b 的地址是一样的
      printf("%p \n", a);
      printf("%p \n", b);
    
      return 0;
    }
    
    

代码段

  1. .text 段: 是整个程序中最关键的部分,它包含了程序的机器码,cpu可以直接执行的指令(如你编写的所有函数 main函数 的机器码),以及程序自动生成的代码(如启动代码等等)
  2. .init 段: 并不是所有操作编译器都有该段明确物理段,在程序启动时 main执行之前会进行一些初始化工作(全局\静态变量初始化)可以视为一个初始化段他只是一个概念

静态数据

C语言中,静态数据有两种:

全局变量:定义在函数外部的变量,全局变量也是一种静态数据。
静态局部变量:定义在函数内部,且被 static修饰的变量。
示例:

#include <stdio.h>
#include <unistd.h>
void fun() {
  // static 修饰的数据,存储的数据段中,初始的在 .data 段中 未初始化的 .bss 段
  // 特点:只会初始化一次,只有程序结束才会销毁
  static int b = 9;
  printf("%d \n", b);
  b++;
}

int main(int argc, const char* argv[]) {

  while ( 1 ) {
    fun();
    sleep(1);

  }


  return 0;
}

  1. 为什么需要静态数据?
    全局变量在默认的情况下,对所有文件可见,为某些需要在各个不同文件和函数间访问的数据提供操作上的方便。
  2. 当我们希望一个函数退出后依然能保留局部变量的值,以便于下次调用时还能用时,静态局部变量可帮助实现这样的功能。
  3. 注意:
    1. 若定义时未初始化,则系统会将所有的静态数据自动初始化为0
    2. 静态数据初始化语句,只会执行一遍。
      静态数据从程序开始运行时便已存在,直到程序退出时才释放。
  4. 注意:
    1. static修饰局部变量:使之由栈内存临时数据,变成了静态数据。
    2. static修饰全局变量:使之由各文件可见的静态数据,变成了本文件可见的静态数据。
    3. static修饰函数:使之由各文件可见的函数,变成了本文件可见的静态函数。

动态内存申请

申请堆内存:malloc() / calloc()

malloc (配置内存空间)

头文件 #include <stdlib.h>
定义函数 void *malloc(size_t size);
函数说明 malloc()用来配置内存空间, 其大小由指定的 size 决定. ,
返回值 若配置成功则返回一指针, 失败则返回 NULL. ,返回是一个指针指针指向一片连续的内存空间。
范例

 int *ptr = (int *)malloc(10 * sizeof(int)); 

示例:

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

int main(int argc, const char* argv[]) {

  // malloc 在堆中申请一篇连续的空间,大小有 参数决定:单位是字节数
  int* p = malloc(sizeof(int) * 5);

  // 如果申请不到内存空间返回 NULL, 所以需要判断一下
  if ( p == NULL ) {
    perror("申请堆内存失败 \n");
    return -1;
  }

  // malloc 申请内存空间是不会初始化的
  for ( int i = 0; i < 5; i++ ) {
    printf("%d \n", p[i]);
  }



  return 0;
}

calloc (配置内存空间)

头文件 #include <stdlib.h>
定义函数 void *calloc(size_t nmemb, size_t size);
函数说明 calloc()用来配置nmemb个相邻的内存单位, 每一单位的大小为size, 并返回指向第一个元素的指针.
这和使用下列的方式效果相同:malloc(nmembsize);不过, 在利用 calloc()配置内存时会将内存内容初始化
为 0.
返回值 若配置成功则返回一指针, 失败则返回 NULL.
范例 /
动态配置 10 个 struct test 空间 */

 #include <stdlib.h> 

 main() 
 { 
 int *ptr = (int *)calloc(sizeof(int), 10); 
 }

清零堆内存:bzero()

bzero (将一段内存内容全清为零)

头文件 #include <strings.h>
定义函数 void bzero(void *s, int n);
函数说明 bzero()会将参数 s 所指的内存区域前 n 个字节, 全部设为零值.

 int *ptr = (int *)malloc(10 * sizeof(int));
 bzero(ptr, 10 * sizeof(int))

释放堆内存:free()

 int *ptr = (int *)malloc(10 * sizeof(int));
 bzero(ptr, 10 * sizeof(int))

 // some code 对*ptr进行运算完毕后

 free(ptr);
 ptr = NULL;

realloc(重新分配内存空间)

realloc 是 C 语言标准库中的一个函数,用于重新分配内存。当你需要改变之前通过 malloc、calloc 或 realloc 分配的内存块的大小时,可以使用 realloc 函数。这个函数尝试在原有的内存块后面分配额外的内存,如果成功,它将原有内存块中的数据复制到新的内存块中,并返回新内存块的指针;如果失败,它将返回 NULL,并且原有内存块保持不变。

定义函数 void *realloc(void *ptr, size_t new_size);

参数:

  1. 要扩容的指针
  2. 扩容的内存大小
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, const char* argv[]) {
  int len = 10;
  int* p = malloc(len * sizeof(int));
  if ( p == NULL ) {
    perror("申请空间失败 \n");
    return -1;
  }

  for ( int i = 0; i < len; i++ ) {
    p[i] = len - i;
  }

  for ( int i = 0; i < len; i++ ) {
    printf("%d \n", p[i]);
  }

  // 扩容 realloc
  // 参数1: 要扩容的指针 , 参数2: 扩容后的大小 
  // 注意点:
  //   - 1. 新增加的空间,保存的数据值是不确定的
  //   - 2. 使用 realloc 扩容成功后返回的指针可能可以原来的指针一样,
  // 是否一样取决于 malloc 原来指向的空间是否能够进行追加, 若不能追加才会开辟新的内存空间,返回新的地址值
  //   - 3. 使用 free 释放扩容后的指针,就行了原来的旧的指针不用释放,在 realloc 底层会帮忙释放
  int len2 = 20;
  int* p2 = realloc(p, len * sizeof(int));
  if ( p2 == NULL ) {
    perror("扩容失败 \n");
    return -1;
  }

  printf("%p \n", p);
  printf("%p \n", p2);


  for ( int i = 0; i < len2; i++ ) {
    printf(" p2 = %d \n", p2[i]);
  }
  // 释放空间
  // free(p);
  // free(p); // 错误的
  free(p2);
  // 释放内存,没有回收指针,会造成野指针。
  // for ( int i = 0; i < len; i++ ) {
  //   printf("%d \n", p[i]);
  // }
  // 回收指针
  p = NULL;


  return 0;
}

使用 realloc 函数注意点:

  1. 新增加的空间,保存的数据值是不确定的
  2. 使用 realloc 扩容成功后返回的指针可能可以原来的指针一样,是否一样取决于 malloc 原来指向的空间是否能够进行追加, 若不能追加才会开辟新的内存空间,返回新的地址值
  3. 使用 free 释放扩容后的指针,就行了原来的旧的指针不用释放,在 realloc 底层会帮忙释放

free(释放申请好的内存空间)

作用:释放申请的内存空间

注意点:释放内存完毕后记得把指针赋值为NULL ,防止出现野指针。

int* p = malloc(10 * sizeof(int));
free(p);
for(int i = 0 ; i < 10 ; i++){
    printf("%d \n" , *(p+1)) ; // 这里大概率获取的是错误的数据,不要使用
}
C语音
License:  CC BY 4.0
Share

Further Reading

Jan 7, 2025

C语言 其他

作用域 关键字 register 在C语言中,register 关键字是一种存储类说明符,用于建议编译器将变量存储在CPU寄存器中,而不是内存中。这样做可以提高访问变量的速度,因为寄存器的访问速度通常比内存快得多。然而,这个关键字只是一个提示,编译器可以选择忽略它。 使用register关键字的变量

Jan 6, 2025

C语言 结构体

结构体 问题的引入 多个相同数据类型的数据可以用数组表示,那么,如果多个不同数据类型的数据如何用一个集合表示呢?? 前面我们所介绍的普通数据类型实际上远远未能满足我们对实际应用中的要求,比如说一个学生,可能包含的属性有年龄、姓名、分数等等,不可能用一个基本数据类型(int 、float 、char)

Dec 28, 2024

C语言 内存

内存 什么是内存 软件在运行是,临时用来存储数据的 操作系统会将内存按照字节划分为 N 多个小格子 什么是内存地址 其实就是格子的编号 32 位操作系统:以 32 位的二进制表示 64 位操作系统:以 64 位的二进制表示 内存地址的作用 快速操作内存中存储的数据 C 语言中如何获取内存地址 &变量

OLDER

C语言 指针

NEWER

MQTT入门

Recently Updated

  • 其他
  • Elasticsearch 面试
  • Spring 面试
  • RabbitMQ 面试
  • Redis 面试

Trending Tags

ruoyi docker java

Contents

©2025 zian. Some rights reserved.

Using the Halo theme Chirpy