动态内存分配及变量存储类别(第二部分)

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

我们知道,变量是有数据类型的,用以说明它占用多大的内存空间,可以进行什么样的操作。

除了数据类型,变量还有一个属性,称为“存储类别”存储类别就是数据在内存中的存放区域。一个正在运行的C程序的内存空间可以分为五个区域:程序代码区、静态数据区、堆区、栈区和命令行参数区,其中静态数据区和栈区可以用来存放变量的值。

静态数据区的内存在程序启动时就已经由操作系统分配好,占用的空间固定,程序运行期间不再改变,程序运行结束后才由操作系统释放;它可以存放全局变量、静态变量、一般常量和字符串常量。

栈区的内存在程序运行期间由操作系统根据需要来分配使用到变量才分配内存;如果定义了变量但没有执行到该代码,也不会分配内存),占用的空间实时改变,使用完毕后立即释放,不必等到程序运行结束;它可以存放局部变量、函数参数等。

可以通过C语言中的关键字来控制变量的存放区域;C语言共有 4 个关键字用来指明变量的存储类别:auto(自动的)、static(静态的)、register(寄存器的)、extern(外部的)。

知道了变量的存储类别,就可以知道变量的生存期。通俗地讲,生存期指的是在程序运行过程中,变量从创建到销毁的一段时间,生存期的长短取决于变量的存储类别,也就是它所在的内存区域。

auto 变量

auto 是自动或默认的意思,很少用到,因为所有的变量默认就是 auto 的。也就是说,定义变量时加不加 auto 都一样,所以一般把它省略,不必多此一举。

例如:

  1. int n = 10;

  1. auto int n = 10;

的效果完全一样。

6. C语言extern变量和函数

在所有的代码块(函数、if 块、switch 块等)之外定义的变量称为全局变量,它的作用范围默认是整个程序,也就是所有的源文件,包括 .c 和 .h 文件。

如果你一直在编写单个 .c 文件的程序,那么请注意,全局变量的作用范围不是从变量定义处到该文件结束,在其他文件中也有效。

虽然全局变量的作用范围是整个程序,但是如果希望在 a.c 中使用 b.c 中的变量,也必须先进行声明。声明使用 extern 关键字,请看下面的代码。

a.c 源码:

1 #include <stdio.h>
2 #include <stdlib.h>
3 extern int num; // 必须对 num 进行声明
4 int main ()
5 {
6     printf("num = %d", num);
7     system("pause");
8     return 0;
9 }

b.c 源码:

1 int num = 100; // 对 num 进行定义

运行结果:
num = 100

我们在 b.c 中定义了一个全局变量 num,在 a.c 中调用了它。extern int num; 的作用是告诉编译器 num 不在 a.c 中,请到其他文件中查找。如果没有 extern,编译器就会在当前文件中查找,发现没有就会报错。

提示:编译是针对单个源文件的,在编译 a.c 时编译器找不到 num,链接时才会在 b.c 的目标代码(.obj 文件)中找到 num。

与其他变量不同,extern 变量有声明和定义之分。

extern 变量的定义格式为:

  1. extern type name = value;

不过 extern 可以省略(我们通常就是这么做的),全局变量默认就是 extern 的,如 b.c 文件所示。

声明格式为:

  1. extern type name;


注意:

  • 在定义 extern 变量时不能省略 value,否则就变成了变量声明。
  • 声明 extern 变量时要指明数据类型(必须和定义时的数据类型一致)。
  • 声明可以有多次,定义只能有一次。


在 a.c 中,我们在所有代码块外部对 num 进行了声明,这个时候 num 的作用范围是 a.c 整个文件(确切的说是从声明开始处到文件结束)。如果在代码块内部声明会怎样呢?

对 a.c 进行更改:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 int main ()
 4 {
 5     {
 6         extern int num;
 7         printf("num = %d", num);
 8     }
 9     printf("num = %d", num);
10     system("pause");
11     return 0;
12 }

编译时报错,第 9 行的 num 未声明。这说明 extern 变量的作用域跟它的声明位置有关,在代码块内声明的 extern 变量在代码块外无效。

extern 函数

从本质上讲,函数和变量是类似的,它们都指向内存中的一块区域:函数指向存放了函数体二进制代码的程序代码区,变量指向静态数据区、栈区或堆区。

extern 除了用于变量,也可以用于函数,请看下面的代码:

sum.c 源码:

1 int sum(int n1, int n2)
2 {
3     return n1 + n2;
4 }

main.c 源码:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 extern int sum(int, int);
 4 int main ()
 5 {
 6     int num1 = 20, num2 = 110;
 7     printf("%d + %d = %d", num1, num2, sum(num1, num2));
 8     system("pause");
 9     return 0;
10 }

运行结果:
20 + 110 = 130

我们在 sum.c 中定义了一个函数 sum() 用来计算两个数的和,在 main.c 中对函数进行了调用。extern 的作用是告诉编译器 sum() 函数不在 main.c 中,请到其他文件中去查找。

但是,函数和变量的声明有所不同,对于函数,你可以省略 extern。例如将 main.c 中的:

  1. extern int sum(int, int);

改为:

  1. int sum(int, int);

仍然能够编译通过并正确运行。

这是因为,函数的定义和声明区别很明显,有函数体就是定义,没有函数体就是声明,所以有没有 extern 都是函数声明。但是变量不一样,没有 extern 就是变量定义,重复定义是错误的。

 

 7. C语言static变量和函数

上一节我们讲到,全局变量和函数的作用范围默认是整个程序,也就是所有的源文件。这给我们带来了很大的方便,让我们能够在 A 文件中调用 B 文件中定义的变量和函数,不必把所有的代码都集中到一个文件,有利于模块化的程序设计。

但是有时候这也会带来冲突,例如在 a.c 中定义了一个全局变量 n,在 b.c 中又定义了一次,编译时就会发生重复定义的错误,因为变量只能定义一次。

如果两个文件都是我们自己编写的或者其中一个是,遇到这样的情况还比较好处理,改变变量的名字就可以;但如果两个文件都是其他程序员编写的,或者是第三方的库,修改起来就颇费精力了。所以,实际开发中我们一般将不需要被其他文件调用的全局变量或函数的作用范围限制在当前文件中。

可以通过 static 关键字来限制,请看下面的代码。

a.c 源码:

1 #include <stdio.h>
2 static int n = 10;
3 void print_n_a()
4 {
5     printf("n(a.c) = %d\n", n);
6 }

b.c 源码:

1 #include <stdio.h>
2 static int n = 20;
3 void print_n_b()
4 {
5     printf("n(b.c) = %d\n", n);
6 }

main.c 源码:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 int n = 100;
 4 int main ()
 5 {
 6     print_n_a();
 7     print_n_b();
 8     printf("n(main.c) = %d\n", n);
 9     system("pause");
10     return 0;
11 }

运行结果:
n(a.c) = 10
n(b.c) = 20
n(main.c) = 100

我们在 a.c、b.c 和 main.c 中都定义了变量 n,a.c 和 b.c 中的变量 n 都只在各自的文件内有效,main.c 中的变量 n 在整个程序内有效。

由此可见,加了 static 的变量或函数的作用范围仅限于当前文件,对其他源文件隐藏,利用这一特性可以在不同的文件中定义同名的变量或函数,而不必担心命名冲突。

static 局部变量

static 声明的变量称为静态变量,不管是全局变量还是局部变量,都存储在静态数据区(全局变量本来就存储在静态数据区,即使不加 static)。

静态数据区的数据在程序启动时就会初始化,直到程序运行结束;对于代码块中的静态局部变量,即使代码块执行结束,也不会销毁。

注意:静态数据区的变量只能初始化(定义)一次,以后只能改变它的值,不能再被初始化,即使有这样的语句,也无效。

请看下面的代码:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 int main ()
 4 {
 5     int result, i;
 6     for(i = 1; i<=100; i++)
 7     {
 8         result = sum(i);
 9     }
10     printf("1+2+3+...+99+100 = %d\n", result);
11     system("pause");
12     return 0;
13 }
14 int sum(int n)
15 {
16 // 也可以不赋初值 0,静态数据区的变量默认初始化为 0
17     static int result = 0;
18     result += n;
19     return result;
20 }

运行结果:
1+2+3+...+99+100 = 5050

我们在 sum() 中定义了一个静态局部变量 result,它存储在静态数据区,sum() 函数执行结束也不会销毁,下次调用继续有效。静态数据区的变量只能初始化一次,第一次调用 sum() 时已经对 result 进行了初始化,所以再次调用时就不会初始化了,也就是说 static int result = 0; 语句无效。

静态局部变量虽然存储在静态数据区,但是它的作用域仅限于定义它的代码块,sum() 中的 result 在函数外无效,与 main() 中的 result 不冲突,除了变量名一样,没有任何关系。

总结起来,static 变量的主要作用有两个。

1) 隐藏

程序有多个源文件时,将全局变量或函数的作用范围限制在当前文件,对其他文件隐藏。

2) 保持变量内容的持久化

将局部变量存储到静态数据区。静态数据区的内存在程序启动时就已分配好(内存中所有的字节默认值都是0x00),直到程序运行结束。

 

8. C语言register变量

一般情况下,变量的值是存储在内存中的,CPU 每次使用数据都要从内存中读取。如果有一些变量使用非常频繁,从内存中读取就会消耗很多时间,例如 for 循环中的增量控制:

1 int i;
2 for(i=0; i<1000; i++)
3 {
4 // Some Code
5 }

执行这段代码,CPU 为了获得 i,会读取 1000 次内存。

为了解决这个问题,可以将使用频繁的变量放在CPU的通用寄存器中,这样使用该变量时就不必访问内存,直接从寄存器中读取,大大提高程序的运行效率。

寄存器、缓存、内存

为了加深对 register 变量的理解,这里有必要讲一下CPU寄存器。

按照与CPU的远近来分,离CPU最近的是寄存器,然后是缓存,最后是内存。

寄存器是最贴近CPU的,而且CPU只在寄存器中进行存取。寄存的意思是暂时存放数据,不用每次都从内存中取,它是一个临时的存放数据的空间。

而寄存器的数据又来源于内存,于是 CPU <-- 寄存器 <-- 内存,这就是它们之间的信息交换。

那么为什么还需要缓存呢?因为如果频繁地操作内存中同一地址上的数据会影响速度,于是就在寄存器和内存之间设置一个缓存,把使用频繁的数据暂时保存到缓存,如果寄存器需要读取内存中同一地址上的数据,就不用大老远地再去访问内存,直接从缓存中读取即可。

缓存的速度远高于内存,价格也是如此。

注意:缓存的容量是有限的,寄存器只能从缓存中读取到部分数据,对于使用不是很频繁的数据,会绕过缓存,直接到内存中读取。所以不是每次都能从缓存中得到数据,这就是缓存的命中率,能够从缓存中读取就命中,否则就没命中。

关于缓存的命中率又是一门学问,哪些数据保留在缓存,哪些数据不保留,都有复杂的算法。


注意:上面所说的CPU是指CPU核心,从市场上购买的CPU已是封装好的套件,附带了寄存器和缓存,插到主板上就可以用。

从经济和速度的综合考虑,缓存又被分为一级缓存、二级缓存和三级缓存,它们的存取速度和价格依次降低,容量依次增加。购买到的CPU一般会标出三级缓存的容量。

register 变量

寄存器的数量是有限的,通常是把使用最频繁的变量定义为 register 的。

来看一个计算 π 的近似值的例子,求解的一个近似公式如下:


为了提高精度,循环的次数越多越好,可以将循环的增量控制定义为寄存器变量,如下所示:

 1 #include <stdio.h>
 2 #include <conio.h>
 3 int main()
 4 {
 5     register int i = 0; // 寄存器变量
 6     double sign = 1.0, res = 0, ad = 1.0;
 7     for(i=1; i<=100000000; i++)
 8     {
 9         res += ad;
10         sign=-sign;
11         ad=sign/(2*i+1);
12     }
13     res *= 4;
14     printf("pi is %f", res);
15     getch();
16     return 0;
17 }

运行结果:
pi is 3.141593

关于寄存器变量有以下事项需要注意:
1) 为寄存器变量分配寄存器是动态完成的,因此,只有局部变量和形式参数才能定义为寄存器变量。

2) 局部静态变量不能定义为寄存器变量,因为一个变量只能声明为一种存储类别。

3) 寄存器的长度一般和机器的字长一致,所以,只有较短的类型如int、char、short等才适合定义为寄存器变量,诸如double等较大的类型,不推荐将其定义为寄存器类型。

4) CPU的寄存器数目有限,因此,即使定义了寄存器变量,编译器可能并不真正为其分配寄存器,而是将其当做普通的auto变量来对待,为其分配栈内存。当然,有些优秀的编译器,能自动识别使用频繁的变量,如循环控制变量等,在有可用的寄存器时,即使没有使用 register 关键字,也自动为其分配寄存器,无须由程序员来指定。

 

转载于:https://www.cnblogs.com/chunlanse2014/articles/4422153.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/257434.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

oracle的em能干什么,转载 解决Oracle的EM登录

转载 解决Oracle的EM登录(2011-03-13 20:53:39)标签&#xff1a;杂谈这几天解决了EM无法登录的问题&#xff0c;顺便也把j数据库程序中常出现的ORA_12518错误解决了&#xff0c;有必要总结一下&#xff0c;我最初遇到的情况是这样的&#xff1a;1. 编写java程序访问oracle数据库…

python 回溯法 子集树模板 系列 —— 1、8 皇后问题

问题 88格的国际象棋上摆放八个皇后&#xff0c;使其不能互相攻击&#xff0c;即任意两个皇后都不能处于同一行、同一列或同一斜线上&#xff0c;问有多少种摆法。 分析 为了简化问题&#xff0c;考虑到8个皇后不同行&#xff0c;则每一行放置一个皇后&#xff0c;每一行的皇后…

Android实用代码七段(五)

前言 每次分享意味着每次都有进步&#xff0c;本系列以实用为主&#xff0c;欢迎和我分享和推荐好用的代码段~~声明欢迎转载&#xff0c;但请保留文章原始出处:) 博客园&#xff1a;http://www.cnblogs.com农民伯伯&#xff1a; http://over140.cnblogs.com 正文 1、展开、收起…

ceph Luminous版手动安装零散记录

1.安装必要的依赖包&#xff0c;关防火墙&#xff0c;向/etc/hosts内添加域名等 2.安装ceph 配置yum源 (如果嫌慢&#xff0c;可以配置cachedir/home/yum/$basearch/$releasever和keepcache1两个参数&#xff0c;在第一次安装时将安装包下载到本地做成yum源&#xff0c;给后面的…

C#最简单最完整的webservice实例

我做java&#xff0c;但最近接触crm所以必须研究一下C#中的webservice以备后用&#xff0c;其实就是个新手&#xff0c;哈哈&#xff0c;这个实例是我在参考了网上诸多不完整的例子的情况下&#xff0c;自己摸索完成的。期间遇到过一系列的棘手的问题&#xff0c;经过个人努力终…

51 Nod 1007 正整数分组【类01背包】

1007 正整数分组 基准时间限制&#xff1a;1 秒 空间限制&#xff1a;131072 KB 分值: 10难度&#xff1a;2级算法题将一堆正整数分为2组&#xff0c;要求2组的和相差最小。例如&#xff1a;1 2 3 4 5&#xff0c;将1 2 4分为1组&#xff0c;3 5分为1组&#xff0c;两组和相差1…

iOS富文本

iOS富文本 背景&#xff1a;前些天突然想做一个笔记本功能&#xff0c;一开始&#xff0c;觉得挺简单的呀&#xff0c;一个UITextView,网络缓存也不干了&#xff0c;直接本地NSUserDefault存储&#xff0c;然后完事了&#xff0c;美工&#xff0c;弄几张好看的图片&#xff0c;…

SQL编程题-----1

首先&#xff0c;题目给出这个数据库表格 要求写出SQL语句使之变成如下表格 解决方法&#xff1a; SELECT t1.Rq,t1.胜,t2.负 FROM //t1和t2是自己命的新表格的名字 (SELECT Rq,COUNT(*) AS 胜 //As 胜意思是输出结果时列名为”胜“FROM testtableWHERE Sh…

六角填数---第五届蓝桥杯

/** 如图【1.png】所看到的六角形中&#xff0c;填入1~12的数字。使得每条直线上的数字之和都同样。图中&#xff0c;已经替你填好了3个数字&#xff0c;请你计算星号位置所代表的数字是多少&#xff1f;请通过浏览器提交答案。不要填写多余的内容。*/ public class 六角填数 {…

linux命令编写,编写简单的linux命令

8种机械键盘轴体对比本人程序员&#xff0c;要买一个写代码的键盘&#xff0c;请问红轴和茶轴怎么选&#xff1f;又到了周四分享环节&#xff0c;鉴于最近在看linux编程实践&#xff0c;所以就的讲一下如何编写一个简单的who命令。PPTManual PageManual Page 也就是大家常用的m…

linux不登录用户就关机,Linux无法被远程登录;用户的关机, 重启,注销,新增用户,删除用户...

不能使用xshell连接到我的Linux服务器通过再windows的cmd中ping了我的Linux地址&#xff0c;发现网络不通。查看百度发现是因为网络没有选桥接模式&#xff0c;然后选完桥接模式告诉我&#xff1a;然后发现没有虚拟网卡&#xff0c;原因是之前装过vm没有卸载干净&#xff0c;使…

程序员的国庆节如何安排,你想好了吗?

2019独角兽企业重金招聘Python工程师标准>>> 就要国庆放假了&#xff0c;国庆节是旅游的黄金期&#xff0c;同时也是我们买买买的幸福期&#xff0c;作为一名技术开发&#xff0c;除了要安排假期旅游行程外也不要忘记提升自己&#xff0c;准备好学习计划&#xff0c…

微服务实战(一):微服务架构的优势与不足

本文讲的是微服务实战&#xff08;一&#xff09;&#xff1a;微服务架构的优势与不足&#xff0c;【编者的话】本文来自Nginx官方博客&#xff0c;是微服务系列文章的第一篇&#xff0c;主要探讨了传统的单体式应用的不足&#xff0c;以及微服务架构的优势与挑战。正如作者所说…

linux创建zip+函数,linux+shell基础知识

目录&#xff1a;1.路径&#xff1a;2.进程&#xff1a;3.清屏和退出当前命令操作&#xff1a;4.ls 参数&#xff1a;5.创建目录\文件\复制文件&#xff1a;6.查看文件内容&#xff1a;7.linux通配符&#xff1a;8.grep:9.终止命令&#xff1a;10.搜索文件&#xff1a;11.查看网…

关于浮动float属性和position:absolute属性的区别

最近返回头看了很多书籍&#xff0c;一直在纠结float属性和absolute绝对定位的区别和使用的情况&#xff0c;给大家分享一下自己的心得和体会吧。 1&#xff0c;float属性 float属性意义是让元素拜托独占一行的霸道总裁&#xff0c;成为一个普普通通的人。比如下面这个例子 如图…

Climbing Stairs

You are climbing a stair case. It takes n steps to reach to the top. Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top? 分析&#xff1a;考虑走第n步时的情况&#xff0c;可以从第n-1个台阶走一步&#xff0c;也可以从…

高级组合技打造“完美” 捆绑后门

0x00 简介 之前写过一篇关于客户端钓鱼的文章&#xff1a;《使用powershell Client进行有效钓鱼》中&#xff0c;在使用各个Client进行测试的过程中&#xff0c;个人发现CHM文件是最好用的一个&#xff0c;但是其缺点就是会弹黑框&#xff0c;这样就会让被攻击者察觉。那么怎么…

Cloudera Manager内部结构、功能包括配置文件、目录位置等

2019独角兽企业重金招聘Python工程师标准>>> 问题导读 1.CM的安装目录在什么位置&#xff1f; 2.hadoop配置文件在什么位置&#xff1f; 3.Cloudera manager运行所需要的信息存在什么位置&#xff1f; 4.CM结构和功能是什么&#xff1f; 1. 相关目录 /var/log/cloud…

python 学习笔记(一)

在Windows上安装Python 首先&#xff0c;从Python的官方网站www.python.org下载最新的2.7.9版本&#xff0c;地址是这个&#xff1a; http://www.python.org/ftp/python/2.7.9/python-2.7.9.msi 然后&#xff0c;运行下载的MSI安装包&#xff0c;在选择安装组件的一步时&#x…

Qualcomm QXDM工具简介和log抓取

高通工具简介QXDM 简介QXDM 安装QXDM 激活QXDM 使用AT打开Diagnostic口 QXDM 配置1 Message View ConfigurationMessage PacketsLog PacketsLog PacketsOTAEvent ReportsStrings2 Log View Config3 QXDM-保存配置文件4 QXDM-导入配置文件QPST 端口配置QXDM 抓取log QXDM LOG保存…