C语言存储类别和链接

​ 最近详细的复习C语言,看到存储类别的时候总感觉一些概念模糊不清,现在认真的梳理一下。C语言的优势之一能够让程序员恰到好处的控制程序,可以通过C语言的内存管理系统指定变量的作用域和生存周期,实现对程序的控制。

存储类别

  • 基本概念

对象:在C语言中所有的数据都会被存储到内存中,被存储的值会占用一定的物理内存,这样的一块内存被称为对象,它可以储存一个或者多个值,在储存适当的值时一定具有相应的大小。(C语言对象不同于面向对象语言的对象)

标识符:程序需要一种方法来访问对象,这就需要声明变量来实现,例如: int identifier = 1,在这里identifier就是一个标识符,标识符是一个名称并遵循变量的命名规则。所以在本例中identifier即是C程序指定硬件内存中的对象的方式并提供了存储的值的大小“1”。在其它的情况中 int * ptint arr[10],pt就是一个标志符,它指定了储存地址的变量,但是表达式*p不是一个标志符,因为它不是一个名称。arr的声明创建了一个可容纳10个int类型元素的对象,该数组的每一个元素也是一个对象。

作用域:描述程序中可访问标识符的区域。因为一个C变量的作用域可以是块作用域、函数作用域、文件作用域和函数原型作用域。

块作用域:简单来说块作用域就是一对花括号括起来的代码区域。定义在块中的变量具有块作用域,范围是定义处到包含该定义块的末尾。

函数原型作用域:范围是从形参定义处到函数原型声明的结束。我们知道编译器在处理函数形参时只关心它的类型,而形参的名字通常无关紧要。例如:

  1. void fun(int n,double m); 同样可以声明为
  2. void fun(int ,double );

还有一点要注意的是函数体的形参虽然声明在函数的左花括号之前但是它具有的是块作用域属于函数体这个块。

文件作用域:变量的定义在所有函数的外面,从它的定义处到该文件的末尾处均可见称这个变量拥有文件作用域。所以文件作用域变量也被称为全局变量

链接:C变量有三种链接属性:内部链接、外部链接和无链接。具有块作用域、函数原型作用域的变量都是无链接变量,这就意味这他们属于定义他们的块或者函数原型私有。文件作用域变量可以是外部链接或是内部链接,外部链接可以在多个文件中使用,内部链接只能定义它的文件单元中使用。

存储期

指对象在内存中保留了多长时间,作用域和链接描述了对象的可见性。存储期则描述了标识符访问对象的生存期。

C对象有4种存储期:

  1. 静态存储期:如果一个对象具有静态存储期,那么它在程序的执行期间一直存在。文件作用域变量具有静态存储期,注意关键字static表明的是链接属性而不是存储期。以static声明的文件作用域变量具有内部链接,无论具有内部链接还是外部链接,所有的文件作用域变量都具有静态存储期。

还有一种情况块作用域变量也可以拥有静态存储期,把变量声明在块中并在变量名前加static关键字,例:

  1. int fun(int num)
  2. {
  3. static int Index;
  4. ...
  5. }

在这里变量Index就被存储在静态内存中,从程序被载入到程序结束都会存在,但是只有程序进入这个块中才会访问它指定的对象。

  1. 线程存储期:用于并发程序设计,一个程序的执行可以分为多个线程,具有线程存储期的变量从被声明时到线程结束一直存在。以关键字_Thread_local声明一个对象时,每个线程都获得该变量的私有备份。

  2. 自动存储期:块作用域变量通常具有自动存储期,当程序进入定义这些变量的块时,会为这些变量分配内存,当程序离开这个块时会自动释放变量占用的内存,这种做法相当于把自动变量占用的内存视为可重复利用的工作区或暂存区。

  3. 动态分配存储期

五种存储类别

  • 五种存储类别
存储类别 存储期 作用域 链接 声明方式
自动 自动 无链接 块内
寄存器 自动 无链接 块内 关键字regsiter
静态外部链接 静态 文件 外部 所有函数外
静态内部链接 静态 文件 外部 所有函数外 关键字static
静态无链接 静态 块内 关键字static
  • 自动变量

自动变量属于自动识别的变量具有自动存储期,块作用域且无链接。可以显示的使用auto关键字进行声明。

注意: auto是存储类别说明符和C++中的auto用法完全不同

一个变量具有自动存储期就意味着当程序进入这个块时变量存在,退出块时变量消失,原来变量占用的内存另作他用。

  1. void hiding()
  2. {
  3. int x = 30;
  4. printf("x in outer block: %d at %p\n", x, &x);
  5. {
  6. x = 77;
  7. printf("x in inner block: %d at %p\n", x, &x);
  8. }
  9. // 块中内存被释放隐藏的x恢复 x = 30
  10. printf("x in outer block: %d at %p\n", x, &x);
  11. while (x++ < 33)
  12. {
  13. int x = 100;
  14. x++;
  15. printf("x in while loop: %d at %p\n", x, &x);
  16. }
  17. printf("x in outer block: %d at %p\n", x, &x);
  18. }

没有花括号时

  1. void forc()
  2. {
  3. int n = 8;
  4. printf(" Initially, n = %d at %p\n", n, &n);
  5. for (int n = 1; n < 3; ++n)
  6. printf(" loop 1:n = %d at %p\n", n &n);
  7. // 离开循环后原始的你又起作用了
  8. printf("After loop 1:n = %d at %p\n", n &n);
  9. for (int n = 1; n < 3; ++n)
  10. {
  11. printf("loop 2 index n = %d at %p\n", n, &n);
  12. // 重新初始化的自动变量,作用域没有到循环里的n
  13. int n = 6;
  14. printf(" loop 2:n = %d at %p\n", n, &n);
  15. // 起作用的仍然是循环中的n
  16. n++;
  17. }
  18. // 离开循环后原始的n又起作用了
  19. printf(" loop 2:n = %d at %p\n", n, &n);
  20. }

输出为

  • 寄存器变量

使用关键字register,储存在CPU的寄存器中,存储在最快的可用内存中。

  • 块作用域的静态变量

​ 首先要明确概念静态变量并不是指值不改变的变量,而是指它在内存中的位置不变。具有文件作用域的静态变量自动具有静态存储期。

前面提到我们可以创建一个静态存储期,块作用域的局部变量,这种变量和自动变量一样具有相同的作用域,但是在程序离开块时并不会消失,

  1. void trystat();
  2. int main()
  3. {
  4. int count = 1;
  5. for (count = 1; count <= 3; count++)
  6. {
  7. printf("Here comes iteration %d:\n", count);
  8. trystat();
  9. }
  10. trystat();
  11. return 0;
  12. }
  13. void trystat()
  14. {
  15. int fade = 1;
  16. static int stay = 1;
  17. printf(" fade = %d and stay = %d\n", fade++, stay++);
  18. }

输出:

​ 可以看出每次离开块fade变量的值都会被重新的初始化,而stay只是在编译函数void trystat()的时候被初始化了一次,在离开自己函数体的块和for循环块之后都会递增,说明stay访问的对象一直存在并没有像自动变量一样被释放掉。

  • 外部链接的静态变量

​ 具有外部链接、静态存储期和文件作用域,属于该类别的变量属于外部变量。只需要把变量的声明放在所有函数的外面就创建了一个外部变量。为了表明该函数使用了外部变量,需要使用关键字extern来再次申明。如果在一个源文件中使用的外部变量声明在了另一个源文件中,则必须要使用extern来申明。

外部变量可以显示初始化,如果没有则会被默认初始化为0。

  • 内部链接的静态变量

具有文件作用域、静态存储期和内部链接,在所有函数外用static来声明一个变量就是内部链接的静态变量。

例:

  1. static int val = 1;
  2. int main()
  3. {
  4. ...
  5. }

普通的外部变量可以用于程序中的任意一个函数处,但是内部链接的静态变量只能用于同一个文件中的函数。都可以使用extern说明符,在函数中重复任何声明文件作用域变量并不会改变他们的链接属性。

例:

  1. int global = 1;
  2. static int local_global = 2;
  3. int main
  4. {
  5. extern int global = 1;
  6. extern int local_global = 2;
  7. ...
  8. }

只有在多文件中才能区别内部链接和外部链接的重要性。

总结一下存储类别的说明符中关键字共有六个autoregister_Thread_localstaticexterntypedef ,其中staticextern的含义取决于上下文。

存储类别和函数

函数也有存储类别,分为外部函数(默认)和静态函数。外部函数可以被其它的文件访问,而静态函数只能被定义所在的文件访问。

例:

  1. double gamma(double);
  2. static double beta(int,int);
  3. extern double(double,int);

通常使用extern关键字声明定义在其他文件中的函数,这样做是为了表明当前文件中使用的函数定义在别处。除非使用static关键字,否则一般函数声明都默认为extern

  • 随机函数和静态变量

使用内部链接的静态变量的函数,随机函数(伪随机数)

  1. // 程序 12.7
  2. static unsigned long int next = 1;
  3. unsigned int rand0();
  4. int main()
  5. {
  6. int count = 1;
  7. for (count = 0; count < 5; count++)
  8. {
  9. printf("%d\n", rand0());
  10. }
  11. return 0;
  12. }
  13. unsigned int rand0()
  14. {
  15. next = next * 1103515245 + 12345;
  16. return (unsigned int) (next/65536) % 32768;
  17. }

分配内存malloc()和free()

​ 存储类别有一个共同之处,在确定使用哪一种类型之后,会根据已经规定好的内存管理规则自动选择其作用域和储存储期。现在我们使用更加灵活的方式库函数分配和管理内存。

静态数据在程序载入内存时分配,而自动数据在程序执行时自动分配,在程序离开时销毁。现在我们可以在使用malloc()函数在程序中动态的分配内存,malloc()接收一个参数,所需要要内存的字节数,在内存中自动寻找一个空闲的内存块使用,malloc()分配内存是匿名的并不会为分配的内存块赋名称,但是动态内存会返回这个内存的首地址,所以使用一个指针类型的变量来接收它,而malloc()函数可用于返回指向数组的指针、指向结构的指针等,通常使用强制类型转换将返回的地址转为匹配的类型。

例如申请一个可容纳30个double类型值的数组

  1. double* pt;
  2. pt = (double*)malloc(30*sizeof(double))

注意:pt是数组的首元素地址,按照C语言的用法数组名就是首元素的地址,所以访问这个数组中元素的方法就可以这样表示pt[0]、pt[1]. . .

因此也就有了三种来表示数组的方法:

1)使用常量表达式来表示数组的维度,用数组名来访问数组的元素 。可以使用静态内存和自动内存

2)声明变长数组,用变量表达式来表示数组的维度,用数组名访问数组的元素,具有这种特性的数组只能在自动内存中创建

3)使用malloc()动态内存来创建一个数组,先声明一个指针,接收函数返回的地址。可以使用指针访问数组的元素,指针的类型可以是静态的或者自动的。

malloc()函数要和free()配套使用,申请的内存从malloc()开始到free()结束。

  1. void dyn_arr()
  2. {
  3. double* ptd;
  4. int max;
  5. int number;
  6. int i = 0;
  7. puts("what is the maxnum number of type double entries!");
  8. if (scanf("%d", &max) != 1)
  9. {
  10. puts("Number not correctly entered --bye");
  11. exit(EXIT_FAILURE);
  12. }
  13. ptd = (double*)malloc(max * sizeof(double));
  14. if (ptd == NULL)
  15. {
  16. puts("Memory allocation failed. GoodBye.");
  17. exit(EXIT_FAILURE);
  18. }
  19. puts("Enter ther values (q to quit)");
  20. while (i < max && scanf("%lf", &ptd[i]) == 1)
  21. ++i;
  22. printf("Here are your %d enteries:\n", number = i);
  23. for (i = 0; i < number; i++)
  24. {
  25. printf("%7.2f", ptd[i]);
  26. if (i % 7 == 6)
  27. putchar('\n');
  28. }
  29. if (i % 7 != 0)
  30. putchar('\n');
  31. puts("Done.");
  32. free(ptd);
  33. return 0;
  34. }

C语言存储类别和链接的更多相关文章

  1. C语言中存储类别、链接与内存管理

      第12章 存储类别.链接和内存管理 通过内存管理系统指定变量的作用域和生命周期,实现对程序的控制.合理使用内存是程序设计的一个要点. 12.1 存储类别 C提供了多种不同的模型和存储类别,在内存中 ...

  2. C Primer Plus学习笔记(十一)- 存储类别、链接和内存管理

    存储类别 从硬件方面来看,被储存的每个值都占用一定的物理内存,C 语言把这样的一块内存称为对象(object) 对象可以储存一个或多个值.一个对象可能并未储存实际的值,但是它在储存适当的值时一定具有相 ...

  3. C/C++ 存储类别

    table { margin: auto; } 本文介绍 C/C++ 中的存储类别.所谓的"存储类别"究竟是什么意思? 存储类别主要指在内存中存储数据的方式,其大致牵涉到变量的三个 ...

  4. C语言杂谈(三)存储类别

    本文讨论C语言中的存储类别,包括数据在内存的存储.变量的存储类别.函数的存储类别.生存周期.下图为计算机的存储空间,有寄存器和内存. 一.存储区域 1.寄存器:存放立即参加运算的数据. 2.系统区:存 ...

  5. C语言变量的存储类别

    我们知道,从变量的作用域(即从空间)角度来分,可以分为全局变量和局部变量. 从另一个角度,从变量值存在的作时间(即生存期)角度来分,可以分为静态存储方式和动态存储方式. 静态存储方式:是指在程序运行期 ...

  6. C语言_了解一下C语言中的四种存储类别

    C语言是一门通用计算机编程语言,应用广泛.C语言的设计目标是提供一种能以简易的方式编译.处理低级存储器.产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言. C语言中的四种存储类别:auto ...

  7. C语言中存储类别又分为四类:自动(auto)、静态(static)、寄存器的(register)和外部的(extern)。

    除法运算中注意: 如果相除的两个数都是整数的话,则结果也为整数,小数部分省略,如8/3 = 2:而两数中有一个为小数,结果则为小数,如:9.0/2 = 4.500000. 取余运算中注意: 该运算只适 ...

  8. c语言 变量的存储类别以及对应的内存分配?

    <h4><strong>1.变量的存储类别</strong></h4>从变量值存在的角度来分,可以分为静态存储方式和动态存储方式.所谓静态存储方式指在程 ...

  9. C语言的存储类别和动态内存分配

    存储类别分三大类: 静态存储类别 自动存储类别 动态分配内存 变量.对象--->内存管理 内存考虑效率(时间更短.空间更小) 作用域 链接.---->空间 存储器   ----->时 ...

随机推荐

  1. springboot 项目打包部署后设置上传文件访问的绝对路径

    1.设置绝对路径 application.properties的配置 #静态资源对外暴露的访问路径 file.staticAccessPath=/upload/** #文件上传目录(注意Linux和W ...

  2. 利用threading模块开线程

    一多线程的概念介绍 threading模块介绍 threading模块和multiprocessing模块在使用层面,有很大的相似性. 二.开启多线程的两种方式 1.创建线程的开销比创建进程的开销小, ...

  3. .net core 3.0 Signalr - 实现一个业务推送系统

    ## 介绍 ASP.NET Core SignalR 是一个开源代码库,它简化了向应用添加实时 Web 功能的过程. 实时 Web 功能使服务器端代码能够即时将内容推送到客户端. SignalR 的适 ...

  4. 什么是VR中的Locomotion?

    Locomotion,本文中我称之为移位,是VR研究中最重要的话题之一.因为它属于VR中三大元老级操作(Selection选择,Manipulation操纵物体,Locomotion移位),其中,前两 ...

  5. navicat安装及其简单使用

    一.安装 下载地址:https://pan.baidu.com/s/1bpo5mqj 下载完之后,直接解压出来就能用,看一下解压之后的目录: 双击打开下面这个文件(可以把它添加一个桌面快捷方式,或者添 ...

  6. Redis AOF 持久化详解

    Redis 是一种内存数据库,将数据保存在内存中,读写效率要比传统的将数据保存在磁盘上的数据库要快很多.但是一旦进程退出,Redis 的数据就会丢失. 为了解决这个问题,Redis 提供了 RDB 和 ...

  7. Eclipse的Debug(一)

    本文链接:https://blog.csdn.net/u011781521/article/details/55000066    http://blog.csdn.net/u010075335/ar ...

  8. PHP 上传文件限制

    随笔于新浪面试失败: 需要好好补补了 Windows 环境下的修改方法 ================================================================ ...

  9. TinyXML2的快速实践

    最近遇到个需要在C++中处理XML文件的需求,虽然对此方面并不是很熟,但好在有GitHub上的awesome-cpp项目的帮助,还是收获了足够的相关知识. 类库 常用的或被推荐的XML类库有以下数个选 ...

  10. 【linux】jdk安装及环境变量配置

    登录linux后,切换目录到 /usr/local cd /user/local 在/usr/local目录新建文件夹java用于存放jdk文件 mkdir java 在文件夹java中下载jdk文件 ...