C语言(内存管理)

打印 上一主题 下一主题

主题 1012|帖子 1012|积分 3040

main函数原型

界说:main函数有多种界说格式,main函数也是函数,函数相关的结论对main函数也有用(也可
以界说main函数的函数指针)。
main函数的完备写法:
  1. int main(int argc,char *argv[]){}  
  2. int main(int argc,char **argv){}
复制代码
扩展写法:
  1. main(){} 等价 int main(){}
  2. int main(void){}
  3. void main(){}
  4. void main(void){}
  5. int main(int a){}
  6. int main(int a,int a,int c){}
  7. ...
复制代码
阐明:

  • argc,argv是形参,它们俩可以修改
  • main函数的扩展写法有些编译器不支持,编译报告诫
  • argc和argv的通例写法:
    argc:存储了参数的个数
    argv:存储了全部参数的字符串形式
  1. #include <stdio.h>
  2. int main(int argc,char *argv[])
  3. {
  4. printf("argc=%d\n",argc);
  5. }
  6. int i = 1;
  7. for(;i < argc; i++)
  8. {
  9. printf("%s,%s\n",argv[i],*(argv+i));// 下标法和指针法
  10. }
  11. return 0;
复制代码

4. main函数是体系通过函数指针的回调形调用的
   发起:1. 假如一个函数必要返回数组,发起将这个函数界说成
指针函数(返回值为指针的函数)
2. 假如一个被调函数必要接收主调函数传递一个非char类型的数组,发起被调函数的参数用
数组指针
  1. #include <stdio.h>
  2. /**
  3. * 定义一个函数,从从成绩中求某一个学生的成绩
  4. * @param n 索引,表示某个学生
  5. * @param arr 数组指针,表示总成绩
  6. */
  7. float* get_score(float (*arr)[4],int n)
  8. {
  9. return arr[n];// *(arr+n)
  10. }
  11. int main(int argc,char *argv[])
  12. {
  13. // 创建一个二维数组
  14. float scores[3][4] = {{66,67,78,88},{99,89,78,86},{56,78,67,57}};
  15. float *p = get_score(scores,1);
  16. printf("%5.2f\n",*(p+2));
  17. return 0;
  18. }
复制代码

  • 假如一个被调函数的参数是一个字符数组{“aaa”,“bbb”…},发起将参数类型界说为字符
    指针数组char *arr[]或者字符二级指针char **arr
  1. #include <stdio.h>
  2. // 需求:用一个指针数组, 存储一组字符串,要求写一个函数,取出数组中的字符串
  3. char* get_str(char **p,int n)
  4. {
  5.     return *(p+n);
  6. }
  7.    
  8. int main(int argc,char *argv[])
  9. {
  10.     // 指针数组
  11.     char *arr[3] = {"hello","wangwu","zhangsan"};
  12.     char *str = get_str(arr,1);
  13.     printf("%s\n",str);
  14.     return 0;
  15. }
复制代码

  • 假如必要将一个函数作为另一个函数的形参,发起将该函数的形参用函数指针表示
  1. int add(int a,int b){ return a+b; }
  2. int jisuan(int a,int b,int (*ADD)(int,int))
  3. {
  4. printf("开始计算:\n");
  5. // 执行函数add
  6. ADD(a,b);   
  7. }
  8. int main()
  9. {
  10. int a = 5,b = 3;
  11. jisuan(a,b,add);
复制代码
内存管理

C历程内存布局

任何一个步伐,正常运行都必要内存资源,用来存放诸如变量、常量、函数代码等等。这些差别的
内容,所存储的内存地区是差别的,且差别的地区有差别的特性。因此我们必要研究C语言历程的
内存布局,逐个相识差别内存地区的特性。
每个C语言历程都拥有一片布局相同的虚拟内存,所谓的虚拟内存,就是从现实物理内存映射出来
的地址规范范围,最重要的特性是全部的虚拟内存布局都是相同的,极大地方便内核管理差别的进
程。比方三个完全不相干的历程p1、p2、p3,它们很显然会占据差别区段的物理内存,但颠末系
统的变换和映射,它们的虚拟内存的布局是完全一样的。
PM:Physical Memory,物理内存。
VM:Virtual Memory,虚拟内存。

将其中一个C语言含如历程的虚拟内存放大来看,会发现其内部包下地区:
栈(stack)
堆(heap)
数据段
代码段

虚拟内存中,内核区段对于应用步伐而言是禁闭的,它们用于存放操纵体系的关键性代码,别的由
于 Linux 体系的历史性原因,在虚拟内存的最底端 0x0 ~ 0x08048000 之间也有一段禁闭的区段,
该区段也是不可访问的。
虚拟内存中各个区段的具体内容:

栈内存

什么东西存储在栈内存中?
情况变量
下令行参数
局部变量(包罗形参)
栈内存有什么特点?
空间有限,尤其在嵌入式情况下。因此不可以用来存储尺寸太大的变量。
每当一个函数被调用,栈就会向下增长一段,用以存储该函数的局部变量。
每当一个函数退出,栈就会向上缩减一段,将该函数的局部变量所占内存归还给体系。
留意:
栈内存的分配和开释,都是由体系规定的,我们无法干预。

示例代码:
  1. void func(int a, int *p) // 在函数 func 的栈内存中分配
  2. {
  3. double f1, f2;        
  4. ...                  
  5. }
  6. // 在函数 func 的栈内存中分配
  7. // 退出函数 func 时,系统的栈向上缩减,释放内存
  8. int main(void)
  9. {
  10. int m  = 100;  // 在函数 main 的栈内存中分配
  11. func(m, &m);  // 调用func时,系统的栈内存向下增长
  12. }
复制代码
静态数据

C语言中,静态数据有两种:
全局变量:界说在函数外部的变量。
静态局部变量:界说在函数内部,且被static修饰的变量。
示例:
  1. int a; // 全局变量,退出整个程序之前不会释放
  2. void f(void)
  3. {
  4. static int b; // 静态局部变量,退出整个程序之前不会释放
  5. printf("%d\n", b);
  6. b++;
  7. }
  8. int main(void)
  9. {
  10. f();
  11. f(); // 重复调用函数 f(),会使静态局部变量 b 的值不断增大
  12. }
复制代码
为什么必要静态数据?

  • 全局变量在默认的情况下,对全部文件可见,为某些必要在各个差别文件和函数间访问的数据
    提供操纵上的方便。
  • 当我们希望一个函数退出后依然能保留局部变量的值,以便于下次调用时还能用时,静态局部
    变量可帮助实现这样的功能。
    留意1:
    若界说时未初始化,则体系会将全部的静态数据自动初始化为0
    静态数据初始化语句,只会实行一遍。
    静态数据从步伐开始运行时便已存在,直到步伐退出时才开释。
    留意2:
    static修饰局部变量:使之由栈内存暂时数据,酿成了静态数据。
    static修饰全局变量:使之由各文件可见的静态数据,酿成了本文件可见的静态数据。
    static修饰函数:使之由各文件可见的函数,酿成了本文件可见的静态函数。
数据段与代码段

数据段细分成如下几个地区:
.bss 段:存放未初始化的静态数据,它们将被体系自动初始化为0
.data段:存放已初始化的静态数据
.rodata段:存放常量数据
代码段细分成如下几个地区:
.text段:存放用户代码
.init段:存放体系初始化代码

  1. int a;      
  2. // 未初始化的全局变量,放置在.bss 中
  3. int b = 100; // 已初始化的全局变量,放置在.data 中
  4. int main(void)
  5. {
  6. static int c;      
  7. // 未初始化的静态局部变量,放置在.bss 中
  8. static int d = 200; // 已初始化的静态局部变量,放置在.data 中
  9. }
  10. // 以上代码中的常量100、200防止在.rodata 中
复制代码
堆内存

堆内存(heap)又被称为动态内存、自由内存,简称堆。堆是唯一可被开辟者自界说的区段,开辟
者可以根据必要申请内存的巨细、决定利用的时间长短等。但又由于这是一块体系“飞地”,全部的
细节均由开辟者本身把握,体系不对此做任何干预,给予开辟者绝对的“自由”,但也正因云云,对
开辟者的内存管理提出了很高的要求。对堆内存的合理利用,几乎是软件开辟中的一个永恒的话
题。
堆内存根本特性:
相比栈内存,堆的总巨细仅受限于物理内存,在物理内存允许的范围内,体系对堆内存的申
请不做限定。
相比栈内存,堆内存从下往上增长。
堆内存是匿名的,只能由指针来访问。
自界说分配的堆内存,除非开辟者自动开释,否则永不开释,直到步伐退出。

相关API:
申请堆内存:malloc() / calloc()
清零堆内存:bzero()
开释堆内存:free()

示例:
  1. int *p = malloc(sizeof(int)); // 申请1块大小为 sizeof(int) 的堆内存
  2. bzero(p, sizeof(int));        
  3. // 将刚申请的堆内存清零
  4. *p = 100; // 将整型数据 100 放入堆内存中
  5. free(p);  // 释放堆内存
  6. // 申请3块连续的大小为 sizeof(double) 的堆内存
  7. double *k = calloc(3, sizeof(double));
  8. k[0] = 0.618;
  9. k[1] = 2.718;
  10. k[2] = 3.142;
  11. free(k);  // 释放堆内存
复制代码
留意:
malloc()申请的堆内存,默认情况下是随机值,一样平常必要用 bzero() 来清零。
calloc()申请的堆内存,默认情况下是已经清零了的,不必要再清零。
free()只能开释堆内存,并且只能开释整块堆内存,不能开释别的区段的内存或者开释一部
分堆内存。
开释内存的寄义:
开释内存意味着将内存的利用权归还给体系。
开释内存并不会改变指针的指向。
开释内存并不会对内存做任何修改,更不会将内存清零。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

刘俊凯

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表