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

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;每一行的皇后…

Core Java Volume I — 3.6. Strings

3.6. StringsConceptually, Java strings are sequences of Unicode characters&#xff08;Java的字符串是一个Unicode序列&#xff09;. For example, the string "Java\u2122" consists of the five Unicode characters J, a, v, a, and ?. Java does not have a…

Android实用代码七段(五)

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

oracle 自增1,oracle自增无法从1开始

问题描述我想让XH字段从1开始增加,由于是varchar类型的,所以就用这种方式,但我发现我的数据表中XH字段是从217开始增加的,为什么啊问题出现的环境背景及自己尝试过哪些方法相关代码// 请把代码文本粘贴到下方(请勿用图片代替代码)declarej number;i number;begini:1;j:1;for i …

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;经过个人努力终…

2015 UESTC 数据结构专题G题 秋实大哥去打工 单调栈

秋实大哥去打工 Time Limit: 1 Sec Memory Limit: 256 MB 题目连接 http://acm.uestc.edu.cn/#/contest/show/59Description 天行健&#xff0c;君子以自强不息。地势坤&#xff0c;君子以厚德载物。天天过节的秋实大哥又要过节了&#xff0c;于是他要给心爱的妹子买礼物。但由…

oracle怎么通过sid确定表名,如何获取Oracle的SID列表

更好的方法是&#xff0c;如果您有权访问主机并且Oracle安装使用以下命令&#xff1a;lsnrctl status。这适用于Unix&#xff0c;Linux和Windows机器。 status命令将显示所有监听器(及其相关的SID)。C:\>lsnrctl statusLSNRCTL for 32-bit Windows: Version 10.2.0.1.0 - Pr…

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…

YTU 2924: 文件操作--二进制文件读入

2924: 文件操作--二进制文件读入 时间限制: 1 Sec 内存限制: 128 MB提交: 58 解决: 20题目描述 现有100名学生的姓名(name)、学号(num)、英语(English)、数学(Math)、语文(Chinese)成绩存储在一个二进制文件student.dic中(姓名用char[20]&#xff0c;学号和各科成绩用int存储…

oracle 9.2.0.4,CentOS 4.7 安装Oracle 9.2.0.4的一些问题

#vi/etc/sysconfig/iptables&#xff0c;增加如下-A INPUT -p udp -s 0/0 -d 0/0 --dport 177 -j ACCEPT-A INPUT -p tcp -s 0/0 -d 0/0 --dport telnet -j ACCEPT-A INPUT -p tcp -s 0/0 -d 0/0 --dport ssh -j ACCEPT-A INPUT -p tcp -s 0/0 -d 0/0 --dport login -j ACCEPT-…

《机电传动控制》----学习笔记六

《机电传动控制》与其他学科的联系 1、《液压传动与气压传动》中提到的液压控制阀中的电液伺服阀与《机电传动控制》中的控制电动机里的伺服电机有着密切的联系&#xff0c;都要求我们对伺服系统有着很好的理解。 2、《电路理论》中电机作为独立的一章&#xff0c;讲到了用向量…

Oracle Imp and Exp (导入和导出) 数据 工具使用

Oracle 提供两个工具imp.exe 和exp.exe分别用于导入和导出数据。这两个工具位于Oracle_home/bin目录下。 导入数据exp 1 将数据库ATSTestDB完全导出,用户名system 密码123456 导出到c:\export.dmp中 exp system/123456ATSTestDB filec:\export.dmp fully 其中ATSTestDB为数据库…

[Oracle][Corruption]究竟哪些检查影响到 V$DATABASE_BLOCK_CORRUPTION

根据 471716.1&#xff0c;11g 之后&#xff0c;下列动作如果遇到坏块&#xff0c;都会输出记录到 V$DATABASE_BLOCK_CORRUPTION。- Analyze table .. Validate structure- CTAS(Create table as Select)- Export另外&#xff0c;这些也会记录的&#xff1a;RMAN > Vali…

oracle使用loop将增加十天,使用loop循环操作DML语句

---loop循环&#xff1a;--创建测试表&#xff1a;suxingPROD>create table total3(2 t1 number(8),3 t2 number(8),4 cr date default sysdate);Table created.#测试表已经创建。--查看表中原来的数据&#xff1a;suxingPROD>select * from total3;T1 T2 CR-…

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…

maven设置jdk版本

两种方式&#xff1a;一、可以修改 MAVEN 的 setting.xml 文件&#xff0c;统一修改。<profiles> <profile> <id>jdk-1.6</id> <activation> <activeByDefault>true</activeByDefault>…

获取系统时间出错oracle-,oracle 获取系统时间(转)

Oracle中如何获取系统当前时间select to_char(sysdate,yyyy-mm-dd hh24:mi:ss) from dual;ORACLE里获取一个时间的年、季、月、周、日的函数select to_char(sysdate, yyyy ) from dual; --年select to_char(sysdate, MM ) from dual; --月select to_char(sysdate, dd ) f…