文章目录

  • 实验内容
  • 一、基本概念
    • (一)、全局变量
    • (二)、局部变量
    • (三)、堆和栈
  • 二、编程验证
    • (一)、基于Ubuntu用Linux系统编写C程序
    • (二)、基于STM32用Keil编写C程序
  • 三、归纳分析
  • 四、总结
  • 五、参考文献

实验内容

实验内容:编写一个C程序,重温全局变量、局部变量、堆、栈等概念,在Ubuntu(x86)系统和STM32(Keil)中分别进行编程、验证(STM32 通过串口printf 信息到上位机串口助手) 。归纳出Ubuntu、stm32下的C程序中堆、栈、全局、局部等变量的分配地址,进行对比分析。

一、基本概念

(一)、全局变量

1.全局变量,也称外部变量,它是在函数外部定义的变量。它不属于哪一个函数,而是属于一个源程序文件,其作用域是整个源程序。
2.几乎程序中的所有函数都能使用全局变量,客观上全局变量就起到了在函数间传递数据的作用,甚至可以减少形参和实参的数量。当然在享用它的好处时,也要慎重,避免全局变量过多带来的降低函数通用性及存储空间的浪费。
3.对于全局变量还有以下几点说明:
1)、全局变量从程序运行起即占据内存,在程序整个运行过程中可随时访问,程序退出时释放内存。与之对应的局部变量在进入语句块时获得内存,仅能由语句块内的语句访问,退出语句块时释放内存,不再有效。
2)、局部变量定义后不会自动初始化,除非程序员指定初值。全局变量在程序员不指定初值的情况下自动初始化为零。
3)、在同一源文件中,允许全局变量和局部变量同名。在局部变量的作用域内,全局变量不起作用。

(二)、局部变量

1.局部变量:也称内部变量,是指在一个函数内部或复合语句内部定义的变量。
2.局部变量的生存期:从函数被调用的时刻算起到函数返回调用处的时刻结束。
3.局部变量的作用域是定义该变量的函数或定义该变量的复合语句。也就是说,局部变量只在定义它的函数或复合语句范围内有效,只能在定义它的函数或复合语句内才能使用它们。

(三)、堆和栈

1.栈区(stack)— 由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
2.堆区(heap)— 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
3.堆栈是内存中的一个连续的块。一个叫堆栈指针的寄存器(SP)指向堆栈的栈顶。堆栈的底部是一个固定地址。堆栈有一个特点就是,后进先出。也就是说,后放入的数据第一个取出。它支持两个操作,PUSH和POP。PUSH是将数据放到栈的顶端,POP是将栈顶的数据取出。
4.在高级语言中,程序函数调用、函数中的临时变量都用到堆栈。为什么呢?因为在调用一个函数时,我们需要对当前的操作进行保护,也为了函数执行后,程序可以正确的找到地方继续执行,所以参数的传递和返回值也用到了堆栈。通常对局部变量的引用是通过给出它们对SP的偏移量来实现的。另外还有一个基址指针(FP,在Intel芯片中是BP),许多编译器实际上是用它来引用本地变量和参数的。通常,参数的相对FP的偏移是正的,局部变量是负的。

5.当程序中发生函数调用时,计算机做如下操作:首先把参数压入堆栈;然后保存指令寄存器(IP)中的内容,做为返回地址(RET);第三个放入堆栈的是基址寄存器(FP);然后把当前的栈指针(SP)拷贝到FP,做为新的基地址;最后为本地变量留出一定空间,把SP减去适当的数值。
6.在函数体中定义的变量通常是在栈上,用malloc, calloc, realloc等分配内存的函数分配得到的就是在堆上。在所有函数体外定义的是全局量,加了static修饰符后不管在哪里都存放在全局区(静态区),在所有函数体外定义的static变量表示在该文件中有效,不能extern到别的文件用;在函数体内定义的static表示只在该函数体内有效。另外,函数中的"adgfdf"这样的字符串存放在常量区。
7.对比:
1)、性能
栈:栈存在于RAM中。栈是动态的,它的存储速度是第二快的。
堆:堆位于RAM中,是一个通用的内存池。所有的对象都存储在堆中。
2)、申请方式
stack【栈】: 由系统自动分配。例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间 。
heap【堆】:需要程序员自己申请,并指明大小,在c中malloc函数 如p1 = (char *)malloc(10); 在C++中用new运算符 如p2= (char *)malloc(10); 但是注意:p1、p2本身是在栈中的。
3)、申请后系统的响应
栈【stack】:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
堆【heap】:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序;另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
4)、申请大小的限制
栈【stack】:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。
堆【heap】:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
5)、申请效率的比较
栈【stack】:由系统自动分配,速度较快。但程序员是无法控制的。
堆【heap】:是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便。 另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是直接在进程的地址空间中保留一快内存,虽然用起来最不方便。但是速度快,也最灵活。
6)、堆和栈中的存储内容
栈【stack】:在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。
堆【heap】:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。
7)、存取效率的比较 char s1[] = “aaaaaaaaaaaaaaa”; char *s2 = “bbbbbbbbbbbbbbbbb”; aaaaaaaaaaa是在运行时刻赋值的; 而bbbbbbbbbbb是在编译时就确定的;但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。

二、编程验证

(一)、基于Ubuntu用Linux系统编写C程序

1.在终端命令行中创建一个 main.c 文件

gedit main.c

2.写入代码

#include <stdio.h>
#include <stdlib.h>
//定义全局变量
int init_global_a = 1;
int uninit_global_a;
static int inits_global_b = 2;
static int uninits_global_b;
void output(int a)
{printf("hello");printf("%d",a);printf("\n");
}int main( )
{   //定义局部变量int a=2;static int inits_local_c=2, uninits_local_c;int init_local_d = 1;output(a);char *p;char str[10] = "lyy";//定义常量字符串char *var1 = "1234567890";char *var2 = "qwertyuiop";//动态分配int *p1=malloc(4);int *p2=malloc(4);//释放free(p1);free(p2);printf("栈区-变量地址\n");printf("                a:%p\n", &a);printf("                init_local_d:%p\n", &init_local_d);printf("                p:%p\n", &p);printf("              str:%p\n", str);printf("\n堆区-动态申请地址\n");printf("                   %p\n", p1);printf("                   %p\n", p2);printf("\n全局区-全局变量和静态变量\n");printf("\n.bss段\n");printf("全局外部无初值 uninit_global_a:%p\n", &uninit_global_a);printf("静态外部无初值 uninits_global_b:%p\n", &uninits_global_b);printf("静态内部无初值 uninits_local_c:%p\n", &uninits_local_c);printf("\n.data段\n");printf("全局外部有初值 init_global_a:%p\n", &init_global_a);printf("静态外部有初值 inits_global_b:%p\n", &inits_global_b);printf("静态内部有初值 inits_local_c:%p\n", &inits_local_c);printf("\n文字常量区\n");printf("文字常量地址     :%p\n",var1);printf("文字常量地址     :%p\n",var2);printf("\n代码区\n");printf("程序区地址       :%p\n",&main);printf("函数地址         :%p\n",&output);return 0;
}


3.进行编译

gcc main.c -o main

4.执行程序

./main

5.运行结果

(二)、基于STM32用Keil编写C程序

1.创建工程
这里我给大家提供一个工程文件,大家自行下载即可

链接:https://pan.baidu.com/s/1IjFyR5RdYeF_CMmDgTVv9w
提取码:6666

main.c代码如下:

#include "stm32f10x.h"
#include "bsp_usart.h"  //添加 bsp_usart.h 头文件int init_global_a = 1;
int uninit_global_a;
static int inits_global_b = 2;
static int uninits_global_b;void output(int a)
{printf("hello");printf("%d",a);printf("\n");
}int main(void)
{   //定义局部变量int a=2;static int inits_local_c=2, uninits_local_c;int init_local_d = 1;char *p;char str[10] = "lyy";//定义常量字符串char *var1 = "1234567890";char *var2 = "qwertyuiop";//动态分配int *p1=malloc(4);int *p2=malloc(4);USART_Config();//串口初始化output(a);//释放free(p1);free(p2);printf("栈区-变量地址\n");printf("                a:%p\n", &a);printf("                init_local_d:%p\n", &init_local_d);printf("                p:%p\n", &p);printf("              str:%p\n", str);printf("\n堆区-动态申请地址\n");printf("                   %p\n", p1);printf("                   %p\n", p2);printf("\n全局区-全局变量和静态变量\n");printf("\n.bss段\n");printf("全局外部无初值 uninit_global_a:%p\n", &uninit_global_a);printf("静态外部无初值 uninits_global_b:%p\n", &uninits_global_b);printf("静态内部无初值 uninits_local_c:%p\n", &uninits_local_c);printf("\n.data段\n");printf("全局外部有初值 init_global_a:%p\n", &init_global_a);printf("静态外部有初值 inits_global_b:%p\n", &inits_global_b);printf("静态内部有初值 inits_local_c:%p\n", &inits_local_c);printf("\n文字常量区\n");printf("文字常量地址     :%p\n",var1);printf("文字常量地址     :%p\n",var2);printf("\n代码区\n");printf("程序区地址       :%p\n",&main);printf("函数地址         :%p\n",&output);return 0;
}

2.打开工程文件

3.编译运行

4.串口烧录

不会烧录的请参考我往期博客


5.打开串口助手,按一下 RESET 键,查看结果

三、归纳分析

对比分析基于Ubuntu(x86)系统和STM32(Keil)分别编写C程序的结果

基于Ubuntu(x86)系统:可以从上图可以得出栈区内存地址由高到低方向生长,堆区内存地址由低到高方向生长。而且整个程序的内存也是从高到低的地址进行分配的。

基于STM32(Keil):stm32的栈区的地址值是从上到下减小的,堆区则是从上到下增长的。从每个区来看,地址值是从上到下逐步减小的,即栈区的地址是高地址,代码区的地址是处于低地址。

四、总结

通过本次实验我重温全局变量、局部变量、堆、栈等概念,并亲自动手编程查看了Ubuntu、stm32下的C程序中堆、栈、全局、局部等变量的分配地址,通过对比分析了解并掌握了其相关规律。

五、参考文献

https://www.cnblogs.com/forcheryl/p/3971526.html
https://blog.csdn.net/qq_43279579/article/details/110308101
https://blog.csdn.net/ssj925319/article/details/110727925

基于Ubuntu(x86)系统和STM32(Keil)编写C程序分别进行编程、验证相关推荐

  1. linux读取sd卡文件数据,Linux系统和SD卡读写部分程序.doc

    Linux系统和SD卡读写部分程序 11.1.1 SD卡系统概念 Linux系统移植>第11章SD卡驱动移植,本章重点为SD卡协议介绍和SD卡驱动分析,后面也介绍SD卡驱动移植过程.随着SD卡存 ...

  2. 怎么编写java_程序员学编程第一步:手把手教你开发第一个Java程序

    想必大家已经对Java语言有了初步的认识,对Java的发展历程.运行原理和环境安装有了一定的了解.在本文中,我们继续来学习Java的详细语法与开发规范,教大家用Java编写出简单的程序. 2.1 开发 ...

  3. x86系统和linux系统,ARM与X86的比较

    CPU的指令集从主流的体系结构上分为精简指令集(RISC)和复杂指令集(CISC).嵌入式系统中的主流处理器--ARM处理器,所使用的就是精简指令集.而桌面领域的处理器大部分使用的是复杂指令集,比如我 ...

  4. Visual Studio 2010下基于32位操作系统和64位操作系统的SDL配置步骤

    SDL配置步骤 (以下以文件夹及项目在D盘根目录下为例:可能由于vs2010版本不同,部分图片中内容会与上机操作不一致) 孙晨杰的微博 1.将SDL2-devel-2.0.3-VC.zip解压到D盘根 ...

  5. STM32 KEIL软件设置程序烧写起始地址选择

    转自  https://blog.csdn.net/alfredseng/article/details/53021583 STM32系列的mcu,这儿以cortex-M4为例,我们在线调试时,一般会 ...

  6. keil编写的程序一直在startup_XXXX.s中等待,进不到main函数。有效的解决办法

    引言:写的485串口接收打印程序,程序烧录进去以后,Debug调试发现程序一直在startup_XXXX.s中等待,进入不到main函数,找其原因是因为printf函数的原因. 解决方法:在选项卡的T ...

  7. keil单片机C语言输入函数,keil编写C程序是不是不能在函数内定义变量啊,求大神...

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 肯定可以的:比如 程序动态显示字符 显示光标和光标闪烁打开效果 --------------------------------------------- ...

  8. keil c语言绝对值函数,keil编写C程序是不是不能在函数内定义变量啊,求大神

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 肯定可以的:比如 程序动态显示字符 显示光标和光标闪烁打开效果 --------------------------------------------- ...

  9. STM32 内部Flash读写 程序源码 [已验证]

    目录 STM32 内部Flash带缓存读写 程序源码 0 Macro 1.Flash_Erase 2. Flash_Read_Byte 3.Flash_Write_NoBuffer 4.Flash_W ...

最新文章

  1. 修改远程桌面连接3389端口号
  2. 【面试】Java基础中的那些事-One
  3. 对象不支持“abigimage”属性或方法
  4. SQL 2000 异数据库数据同步
  5. 2021年全球数据中心调查
  6. tcount在哪个文件里_在cad中tcount快速编号命令怎么用,求教
  7. 15.4.1 杠杆利用类型参数推断
  8. vscode的 jsonp 配置文件
  9. 解决vlc-android播放http视频退出问题
  10. 《移动App测试的22条军规》—App测试综合案例分析23.4节测试微信App的手势操作...
  11. 如何从零学习Python----知乎答案
  12. 终极邮件搜索群发大师 v3.47 绿色
  13. 【深度学习】写诗机器人tensorflow实现
  14. mivo tv android,MivoTV Live Streaming
  15. html5首字母大小写,css中如何设置英文首字母大写
  16. PC分享插件js - sosh.min.js
  17. iPhone微信支持更换桌面图标了,超简单
  18. Linux RPMsg框架--以及应用于iMX6 SoloX连接A9和M4 Core
  19. BZOJ_3362_[Usaco2004 Feb]Navigation Nightmare 导航噩梦_并查集
  20. qtp15/uft15 UFT最新版本UFT12.5

热门文章

  1. java打印 X XXX XXXXX
  2. web应用程序安全性测试_Web应用程序导航菜单的可访问性
  3. EF的Code First开发系列之动手写第一个Code First应用
  4. 三跨考研浙江大学计算机,“三跨”考研的焦虑 你能承受多少
  5. PayPal开发文档整理(8)——PayPal支付产品和解决方案
  6. TinkerBoard-S 上手体验
  7. 错误跳转html页面模板,404错误页面模板代码大全 - 搜外SEO问答
  8. 字节跳动 2022年春招
  9. nod-1089-最长回文子串 V2
  10. 实战:搭建高效率生鲜B2B平台八大模块及技术要求