C语言学习_内存分区
发布日期:2021-06-24 18:23:16 浏览次数:2 分类:技术文章

本文共 4702 字,大约阅读时间需要 15 分钟。

1.1 数据类型
  • 数据类型的本质:固定内存大小的别名
  • 数据类型的作用:编译器预算对象(变量)分配的内存空间大小

    int a;  // 告诉编译器分配4个字节的内存
  • 数据类型可以通过typedef起别名
  • 数据类型可以通过sizeof()测类型大小
  • void数据类型(无类型,万能类型)

    • 如果函数没有返回值,必须用void修饰

      // 对函数返回的限定void fun(int a);
    • 如果函数没有参数,参数可以用void修饰

      // 对函数参数的限定int fun(void);
    • 不能定义void类型的普通变量

      void a; // error,不能确定分配内存空间的大小
    • 万能指针

      void * p; // ok, 万能指针,指针类型都是4个字节,函数参数,函数有返回值//1. void* 可以指向任何类型的数据,被称为万能指针void test03(){  int a = 10;  void* p = NULL;  p=&a;  printf("a:%d\n",*(int*)p);  char c = 'a';  p=&c;  printf("c:%c\n",*(char*)p);}//2. void* 常用于数据类型的封装void test04(){  void * memcpy(void * _Dst, const void * _Src, size_t _Size);}
  • sizeof操作符注意事项

    • sizeof返回的占用空间大小是为这个变量开辟的大小,而不只是它用到的空间。所以对结构体用的时候,大多情况下就得考虑字节对齐的问题了;
    • sizeof返回的数据结果类型是unsigned int
    • 要注意数组名和指针变量的区别。通常情况下,我们总觉得数组名和指针变量差不 多,但是在用sizeof的时候差别很大:

      • 对数组名用sizeof返回的是整个数组的大小
      • 对指针变量进行操作的时候返回的则是指针变量本身所占得空间,在32位机的条件下一般都是4。
      • 当数组名作为函数参数时,在函数内部,形参也就是个指针,所以不再返回数组的大小

示例代码:

//1. sizeof基本用法void test01(){  int a = 10;  printf("len:%d\n", sizeof(a));  printf("len:%d\n", sizeof(int));  printf("len:%d\n", sizeof a);}//2. sizeof 结果类型void test02(){  unsigned int a = 10;  if (a - 11 < 0)  {    printf("结果小于0\n");  }  else  {    printf("结果大于0\n");  }  int b = 5;  if (sizeof(b) - 10 < 0)  {    printf("结果小于0\n");  }  else  {    printf("结果大于0\n");  }}//3. sizeof 碰到数组void TestArray(int arr[]){  printf("TestArray arr size:%d\n",sizeof(arr));}void test03(){  int arr[] = { 10, 20, 30, 40, 50 };  printf("array size: %d\n",sizeof(arr));  //数组名在某些情况下等价于指针  int* pArr = arr;  printf("arr[2]:%d\n",pArr[2]);  printf("array size: %d\n", sizeof(pArr));  //数组做函数函数参数,将退化为指针,在函数内部不再返回数组大小  TestArray(arr);}
1.2 变量
1.2.1 变量的概念

​既能读又能写的内存对象,称为变量;

若一旦初始化后不能修改的对象则称为常量。

变量定义的形式:类型 标识符,标识符,…,标识符
1.2.2 变量的本质
  • 变量名的本质:一段连续内存空间的别名;
  • 程序通过变量来申请和命名内存空间int a = 0
  • 通过变量名访问内存空间;
  • 不是向变量名读写数据,而是向变量所代表的内存空间中读写数据;
  • 变量的三要素:名称、大小、作用域
  • 变量的生命周期:结合内存四区模型来看

修改变量的两种方式:

void test(){    int a = 10;    //1. 直接修改    a = 20;    printf("直接修改,a:%d\n",a);    //2. 间接修改    int* p = &a;    *p = 30;    printf("间接修改,a:%d\n", a);}
1.3 程序的内存四区模型
1.3.1 内存分区
  • 程序运行之前:

    C程序编译过程

    1)预处理:宏定义展开、头文件展开、条件编译,这里并不会检查语法

    2)编译:检查语法,将预处理后文件编译生成汇编文件

    3)汇编:将汇编文件生成目标文件(二进制文件)

    4)链接:将目标文件链接为可执行程序

    可执行程序内部已经分好3段信息,分别为代码区text、数据区data和未初始化数据区bss 3个部分(有些人直接把databss合起来叫做静态区或全局区)。

    总体来讲说,程序源代码被编译之后主要分成两种段:程序指令(代码区)和程序数据(数据区)。代码段属于程序指令,而数据域段和.bss段属于程序数据。

  • 程序运行之后:

    程序在加载到内存前,代码区和全局区(data和bss)的大小就是固定的,程序运行期间不能改变。然后,运行可执行程序,操作系统把物理硬盘程序load(加载)到内存,除了根据可执行程序的信息分出代码区(text)、数据区(data)和未初始化数据区(bss)之外,还额外增加了栈区堆区

总的来说,内存分区模型:

代码区:可执行代码段,不可修改。

未初始化数据区(BSS):全局未初始化,静态未初始化数据,数据的生存周期为整个程序运行过程 。

全局初始化数据区/静态数据区(data segment):全局初始化,静态初始化数据,文字常量(只读),数据的生存周期为整个程序运行过程。

栈区(stack):先进后出的内存结构,由编译器自动分配释放 ,存放函数的参数值、返回值、局部变量等

堆区(heap):容量要远远大于栈,用于动态内存分配。堆在内存中位于BSS区和栈区之间。一般由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收。

1.3.2 分区模型
  • 栈区

    由系统进行的内存管理,主要存放函数的参数以及局部变量,函数完成后系统自动释放。

    #char* func(){  char p[] = "hello world!"; //在栈区存储 乱码  printf("%s\n", p);  return p;}void test(){  char* p = NULL;  p=func();    printf("%s\n",p);}
  • 堆区

    手工申请,手工释放

    char* func(){  char* str = malloc(100);  strcpy(str, "hello world!");  printf("%s\n",str);  return str;}void test01(){  char* p = NULL;  p=func();  printf("%s\n",p);}void allocateSpace(char* p){  p=malloc(100);  strcpy(p, "hello world!");  printf("%s\n", p);}void test02(){  char* p = NULL;  allocateSpace(p);  printf("%s\n", p);}

    图片描述

    堆分配内存API:

    void *calloc(size_t nmemb, size_t size);/** 功能:* 在内存动态存储区中分配nmemb块长度为size字节的连续区域。calloc自动将分配的内存置0。* 参数:* nmemb:所需内存单元数量* size:每个内存单元的大小(单位:字节)* 返回值:*  成功:分配空间的起始地址*  失败:NULL*/
  • 全局/静态区

    全局静态区内的变量在编译阶段已经分配好内存空间并初始化。这块内存在程序运行期间一直存在,它主要存储全局变量静态变量常量

    int v1 = 10;        //全局/静态区const int v2 = 20;  //常量,一旦初始化,不可修改static int v3 = 20; //全局/静态区char *p1;           //全局/静态区,编译器默认初始化为NULL//那么全局static int 和 全局int变量有什么区别?void test(){  static int v4 = 20; //全局/静态区}// 加深理解char* func(){  static char arr[] = "hello world!"; //在静态区存储 可读可写  arr[2] = 'c';  char* p = "hello world!"; //全局/静态区-字符串常量区   //p[2] = 'c'; //只读,不可修改   printf("%d\n",arr);  printf("%d\n",p);  printf("%s\n", arr);  return arr;}void test(){  char* p = func();  printf("%s\n",p);}

总结:

数据区包括:堆,栈,全局/静态存储区。

全局/静态存储区包括:常量区,全局区、静态区。
常量区包括:字符串常量区、常变量区。
代码区:存放程序编译后的二进制代码,不可寻址区。

可以说,C/C++内存分区其实只有两个,即代码区和数据区。

1.3.3 函数调用模型
int func(int a,int b){  int t_a = a;  int t_b = b;  return t_a + t_b;}int main(){  int ret = 0;  ret = func(10, 20);  return EXIT_SUCCESS;}

图片描述

1.3.4 栈的生长方向和内存存放方向

图片描述

//1. 栈的生长方向void test01(){  int a = 10;  int b = 20;  int c = 30;  int d = 40;  printf("a = %d\n", &a);  printf("b = %d\n", &b);  printf("c = %d\n", &c);  printf("d = %d\n", &d);  //a的地址大于b的地址,故而生长方向向下}//2. 内存生长方向(小端模式)void test02(){  //高位字节 -> 地位字节  int num = 0xaabbccdd;  unsigned char* p = #  //从首地址开始的第一个字节  printf("%x\n",*p);  printf("%x\n", *(p + 1));  printf("%x\n", *(p + 2));  printf("%x\n", *(p + 3));}

转载地址:https://blog.csdn.net/weixin_34248258/article/details/88766023 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:脚本化HTTP 取得响应 指定请求
下一篇:HTTP笔记

发表评论

最新留言

初次前来,多多关照!
[***.217.46.12]2024年04月03日 11时53分41秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章