C语言中堆区的作用,详解C/C++中堆和栈及静态数据区

本文详细解析了C/C++中的变量作用域,包括全局作用域、文件作用域以及不同存储类型如自动、静态、外部和寄存器类型的特性与应用场景。并通过实例演示了各种作用域和存储类型的具体表现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

详解C/C++中堆和栈及静态数据区

2.全局作用域

对于具有全局作用域的变量,我们可以在程序的任何位置访问它们。当一个变量是在所有函数的外部声明,也就是在程序的开头声明,那么这个变量就是全局变量。

void add(int);

int num;

main()

{

int n=5;

add(n);

printf("%d\n",num);  /*输出6*/

}

void add(num)   /*形式参数没有指定类型*/

{

num++;

printf("%d\n",num);  /*输出6*/

}

上面的main()和add()里面,并没有声明num,但是在最后输出的时候却要求输出num,这是由于在程序的开始声明了num是全局变量,也就是在 所有函数里都可以使用这个变量。这时候一个函数里改变了变量的值,其他函数里的值也会出现影响。上面的例子输出都是6,因为在add()函数里改变了 num的值,由于num是全局变量,就好象它们两个函数共用一个变量,所以在main()函数里的num也随之改变了。

3.文件作用域

在很多C语言书上,都没有说明文件作用域,或者只是略微的提到,其实文件作用域在较大程序中很有作用(在多文件系统中)。文件作用域是指外部标识符仅在声 明它的同一个转换单元内的函数汇总可见。所谓转换单元是指定义这些变量和函数的源代码文件(包括任何通过#i nclude指令包含的源代码文件)。static存储类型修饰符指定了变量具有文件作用域。

static int num;

static void add(int);

main()

{

scanf("%d",&num);

add(num)

printf("%d\n",num);

}

void add(num)

{

num++;

}

上面的程序中变量num和函数add()在声明是采用了static存储类型修饰符,这使得它们具有文件作用域,仅爱定义它们的文件内可见。

由于我们提到的大多数程序都只有一个编译文件组成,所以这种写法没有实际意义。但是实际工程上的文件有很多,它们不是由一个人写成的,由很多人共同完成, 这些文件都是各自编译的,这难免使得某些人使用了一样的全局变量名,那么为了以后程序中各自的变量和函数不互相干扰,就可以使用static修饰符,这样 在连接到同一个程序的其他代码文件而言就是不可见的。

二、变量存储类型

前面我们说了,声明变量时用如下类似的形式:

int num;

float total;

它们都没有存储类型修饰符,我们在声明时也可以通过存储类型修饰符来告诉编译器将要处理什么类型的变量。存储类型有以下四种:自动(auto)、静态(static)、外部(extern)、寄存器(regiser)。

1.自动存储类型

自动存储类型修饰符指定了一个局部变量为自动的,这意味着,每次执行到定义该变量的语句块时,都将会为该变量在内存中产生一个新的拷贝,并对其进行初始化。实际上,如果不特别指明,局部变量的存储类型就默认为自动的,因此,加不加auto都可以。

main()

{

auto int num=5;

printf("%d\n",num);

}

在这个例子中,不论变量num的声明是否包含关键字auto,代码的执行效果都是一样的。函数的形式参数存储类型默认也是自动的。

2.静态存储变量

前面已经使用了static关键字,但是对于局部变量,静态存储类型的意义是不一样的,这时,它是和自动存储类型相对而言的。静态局部变量的作用域仍然近 局限于声明它的语句块中,但是在语句块执行期间,变量将始终保持它的值。而且,初始化值只在语句块第一次执行是起作用。在随后的运行过程中,变量将保持语 句块上一次执行时的值。

看下面两个对应的程序:

/*1.C*/        /*2.C*/

int add();        int add();

main()         main()

{          {

int result;       int result;

result=add()       result=add();

printf("%d ",result);     printf("%d ",result);

result=add();       result=add();

printf("%d ",result);     printf("%d ",result);

result=add();       result=add();

printf("%d",result);     printf("%d",result);

}          }

int add()        int add()

{          {

int num=50;       static int num=50;

num++;         num++;

return num;       return num;

}          }

上面两个源文件,只有函数add()里的变量声明有所不同,一个是自动存储类型,一个是静态存储类型。

对于1.C文件,输出结果为51 51 51;这很好理解,每次初始值都是50,然后加1上来。

对于2.C文件,输出结果为51 52 53;这是由于变量是静态的,只在第一次初始化了50,以后都是使用上次的结果值。当第一次调用add()时,初始化为50,然后加1,输出为51;当第 二次调用时,就不初始化了,这时num的值为上次的51,然后加1,输出52;当第三次调用时,num为52,加1就是53了。

比较就会发现它们的不同之处了。静态变量在下一节要说的递归函数中经常使用到。

当第一次不指明静态变量的初始值时,默认为0。

下面举一个例子,把我们说到的静态变量理解一下。

求1+2+……+100的值的代码如下:

void add();

int result;

main()

{

int i;

result=0;

for(i=0;i<100;i++) add();

printf("%d\n",result);

}

void add()

{

static int num=0;

num++;

result+=num;

}

add()函数被调用了100次,num的值从1一直变到100,这样就可以求出它们的和了。如果写成int num=0;那就是求1+1+……+1这100个1的值了。

实际上类似的这类问题我们可以通过递归函数来解决,什么是递归,我们下一节介绍。

3.外部存储类型

外部存储类型声明了程序将要用到的、但尚未定义的外部变量。通常,外部存储类型都是用于声明在另一个转换单元中定义的变量。下面举一个例子,这个例子包括两个文件。

/*1.C*/

void a();

main()

{

extern int num;

a();

printf("%d\n",num);

}

/*2.C*/

int num;

void a()

{

num=5;

}

这两个程序是分别编译的,然后连接成一个执行文件。具体如何操作,可以查看一些手册,这儿我简单说了一下。把上面两个文件都编译好后,再制作一个.prj文件,里面的内容是:

1.c

2.c

只有这两行,这可在编辑状态下写成,存盘,取名为1.prj。

然后选择project选项,选择project name,填入1.prj文件名,按F9后,即可生成1.exe文件。

main()函数中变量num是在另一个文件中定义的。因此,当编译器编译1.c时,无法确定该变量的地址。这时,外部存储类型声明告诉编译器,把所有对 num的引用当作暂且无法确定的引用,等到所有便宜好的目标代码连接成一个可执行程序模块时,再来处理对变量num的引用。

外部变量的声明既可以在引用它的函数的内部,也可以在外部。如果变量声明在函数外部,那么同一转换单元内的所有函数都可以使用这个外部变量。反之,如果在函数内部,那么只有这一个函数可以使用该变量。

前面说了文件作用域的问题,如果在声明全局变量时,加上static修饰符,那么该变量只在当前文件内可见,而extern又可以引用其它文件里的变量。 所以在一个大型程序中,每个程序员只是完成其中的一小块,为了让自己的变量不让其他程序员使用,保持一定的独立性,经常在全局变量前加static。我们可以这样来说明一下:

还是上面的两个文件,现在再增加一个文件3.c,内容为:

static int num;

void a()

{

num=6;

}

把1.prj文件后面加上3.c 这样,我们生成的1.exe文件,执行时输出是5,而不是6。因为3.c文件的num变量增加了文件作用域,在其他文件中是无法使用它的。

4.寄存器存储类型

被声明为寄存器存储类型的变量,除了程序无法得到其地址外,其余都和自动变量一样。至于什么是变量地址,以后说指针时会详细介绍。

main()

{

egister int num;

num=100;

printf("%d",num);

}

使用寄存器存储类型的目的是让程序员指定某个局部变量存放在计算机的某个硬件寄存器里而不是内存中,以提高程序的运行速度。不过,这只是反映了程序员的主观意愿,编译器可以忽略寄存器存储类型修饰符。

寄存器变量的地址是无法取得的,因为绝大多数计算机的硬件寄存器都不占用内存地址。而且,即使编译器忽略寄存器类型修饰符把变量放在可设定地址的内存中,我们也无法取地址的限制仍然存在。

要想有效的利用寄存器存储类型,必须象汇编语言程序员那样了解处理器的内部构造,知道可用于存放变量的寄存器的数量和种类,以及他们是如何工作的。但是, 不同计算机在这些细节上未必是一样的,因此对于一个可移植的程序来说,寄存器存储类型的作用不大。特别是现在很多编译器都能提供很好的优化效果,远比程序 员来选择有效的多。不过,寄存器存储类型还是可以为优化器提供重要的参考。

C的作用域还有一种,静态块。比如:

/* 静态块作用域 */

{

...;

...;

}

/* 函数作用域 */

main()

{

...;

}

由编译器自动分配释放管理。局部变量及每次函数调用时返回地址、以及调用者的环境信息(例如某些机器寄存器)都存放在栈中。新被调用的函数在栈上为其自动和临时变量分配存储空间。通过以这种方式使用栈,C函数可以递归调用。

需要由程序员分配释放管理,若程序员不释放,程序结束时可能由OS回收。通常在堆中进行动态存储分配。

非初始化数据段

通常将此段称为b s s段,这一名称来源于早期汇编程序的一个操作符,意思是“block started by symbol(由符号开始的块)”,未初始化的全局变量和静态变量存放在这里。在程序开始执行之前,内核将此段初始化为0。函数外的说明:long sum[1000] ; 使此变量存放在非初始化数据段中。

初始化的数据

通常将此段称为数据段,它包含了程序中需赋初值的变量。初始化的全局变量和静态变量存放在这里。例如,C程序中任何函数之外的说明:int maxcount = 99; 使此变量以初值存放在初始化数据段中。

正文段

CPU执行的机器指令部分。通常,正文段是可共享的,所以即使是经常环境指针环境表环境字符串执行的程序(如文本编辑程序、C编译程序、s h e l l等)在存储器中也只需有一个副本,另外,正文段常常是只读的,以防止程序由于意外事故而修改其自身的指令。

对于x86处理器上的Linux,正文段从0x08048000单元开始,栈底在0xC0000000之下开始(栈由高地址向低地址方向增长)。堆顶和栈底之间未用的虚拟空间很大。

Shell的size命令可以看到一个程序的正文段(text)、数据段(data)、非初始化数据段(bss)及文件长度.

[foxman@17:01:49 ]$size mydesign

text data  bss  dec  hex filename

79210 1380  404 80994 13c62 mydesign

【详解C/C++中堆和栈及静态数据区】相关文章:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值