关于动态内存不足问题求教-Arduino中文社区 - Powered by Discuz!

Arduino中文社区

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 5696|回复: 7

[已解决] 关于动态内存不足问题求教

[复制链接]
发表于 2019-7-14 18:08 | 显示全部楼层 |阅读模式
本帖最后由 白头翁 于 2019-7-15 17:54 编辑

我在使用Arduino1.8.4环境下,编译时提示“全程变量太多,动态没存超75%,可能出现不稳定”,便终止编译。
可是让我不解的是:我用的全程变量并不多,大部分都是在函数中定义的局部变量。


这是部分代码:
#include <arduino.h>
#include <avr/pgmspace.h>
#include <SPI.h>
#include "LCDdrive.h"
#include "MainFun.h"
#include "SignalFun.h"

#define uchar unsigned char
#define uint unsigned int
#define ulong unsigned long

unsigned char ControlParameter[4];
unsigned char showData[5];
unsigned long ShowTime;   

void setup() {
  analogReference(INTERNAL) ;
  Serial.begin(9600);  
  while(Serial.read()>= 0){}
  SetKeyROW();  
  SET_Pin() ;   
  ......
  }
以上红字就是我定义的全程变量,其它变量都是在其它功能函数中定义的局部变量,在此省略了...
问题来了,通过摸索试验发现:
    好像编译时系统将所有分配过的变量都统计为全局变量。
例:
第一次编译提示:
            项目使用了 17034 字节,占用了 (55%) 程序存储空间。最大为 30720 字节。
            全局变量使用了1503字节,(73%)的动态内存,余留545字节局部变量。最大为2048字节。
然后屏蔽掉2个子函数中的串口输出语句:
Serial.println("cc1101 connect success");  
Serial.println("cc1101 connect failure");  
再次编译提示:
           项目使用了 16968 字节,占用了 (55%) 程序存储空间。最大为 30720 字节。
           全局变量使用了1457字节,(71%)的动态内存,余留591字节局部变量。最大为2048字节。

比第一次编译省下46个全局变量。显然第一次编译时把Serial.println的输出字符都统计成了全局变量。
不是说局部变量在函数退出后,分配的内存就释放了吗,实在是搞不懂了。


在此求教:哪位明白的老师帮忙,说说产生的原因和解决思路。在此感谢!

声明:本人刚刚注册,这是第一帖。如有不当之处还请原谅!

发表于 2019-7-14 23:45 | 显示全部楼层
不去看看这三个文件么?

#include "LCDdrive.h"
#include "MainFun.h"
#include "SignalFun.h"
 楼主| 发表于 2019-7-15 10:58 | 显示全部楼层
t3486784401 发表于 2019-7-14 23:45
不去看看这三个文件么?

#include "LCDdrive.h"

你看到的那三个头文件都是生明各个需要调用的子函数,没有定义变量的语句。
但是在相对应的cpp文件中有定义变量,但都是定义内部变量和声明使用外部全局变量的。
所以以上这些都不是问题。
我所不解的是,为什么子函数内定义的内部变量在编译的时候都作为全局变量统计了。
发表于 2019-7-15 12:12 | 显示全部楼层
白头翁 发表于 2019-7-15 10:58
你看到的那三个头文件都是生明各个需要调用的子函数,没有定义变量的语句。
但是在相对应的cpp文件中有定 ...

我仔细看了你 1L 的问题,应该是纠结 println 字串为什么占用了 RAM 不释放对吧?

2019-07-15_115739.png

这个涉及 AVR 单片机的哈佛结构(自行搜索).
C/C++ 语言当中默认处理器是冯诺依曼结构的,即程序/数据在同一个地址空间,指针可以通用。
然而 AVR 偏偏是哈佛结构,程序/数据在不同的地址空间(相互重叠),指针不能直接通用。

举例说明:访问 0x20 地址的内容,C 语言可以用指针:*(uint8_t*)0x20.
在冯诺依曼结构下,该地址只能是程序(例如函数体)或数据(例如字串)二选一,对应底层汇编就一种 MOV 指令;
而在哈佛结构下,该地址有可能同时存在程序、数据,底层通过不同指令加以区分,例如AVR的 LPM 访问程序, LDS 访问数据。

回到最开始的问题,println 在传入字串指针的时候,由于 AVR 是哈佛结构,底层究竟用“程序指针LPM”还是“数据指针LDS”?
一般编译器(例如GCC)都会这种方案:统一使用数据指针(LDS/STS,访问RAM),这样你 C 里边就可以支持向指针地址写入内容。
这样就需要在 RAM 当中,为每个字串开一个副本空间,并且回收这些空间的代价(记录哪些回收了,哪些没回收)大于回收所得,

所以一般字串都是不回收空间的,结果就是跟全局变量一个鬼样。
除非你指定字串强制存储 FLASH,不允许内存副本(PROGMEM关键字),才能避免这部分 RAM 消耗。
但这样一来所有的函数都要换掉(默认函数里的 C 指针翻译成数据指针),换成有程序指针的函数

 楼主| 发表于 2019-7-15 17:52 | 显示全部楼层
t3486784401 发表于 2019-7-15 12:12
我仔细看了你 1L 的问题,应该是纠结 println 字串为什么占用了 RAM 不释放对吧?

多谢多谢!受益匪浅。
我在调试期间用了大量的串口输出观察执行状态,后来内存不够用了,把不再用的输出句子就删除了,结果释放了大量的空间,也就能继续往下写了。
因为这些细节始终搞不懂挺纠结的,多谢指点!胜读两年书
发表于 2019-7-15 20:57 | 显示全部楼层
我去翻了翻 Print 类的源码,发现这个类是可以打印只存储于 FLASH 区的字串的。
该函原型为:

  1. size_t Print::print(const __FlashStringHelper *);
复制代码


很显然这个  const __FlashStringHelper * 类型就是专门存储于 FLASH 区的字串。
接下来就是怎么把字串写进 FLASH 区而强制不加载 RAM 副本。

于是查到了 F( ) 宏,定义如下:

  1. #define F(string_literal) (reinterpret_cast<const __FlashStringHelper *>(PSTR(string_literal)))
复制代码


试着写了写果然好用:字串 "Hello" 升级为 F("Hello") 后,可以被 Print 正确重载。

---------------------------------------------------------------------------------------------------------------------

以下图片测试了 F( ) 宏的作用。

只调用一次常规 println:
2019-07-15_204117.png

新增 5 次 println 调用,字串各不相同(防止优化),每个字串长度含结尾 16B,内存消耗增加 80B:
2019-07-15_204206.png

新增的 5 次调用均改为 F( ) 宏格式,果然 80B 又空出来了,
但是程序区 FLASH 用量有所增长,说明程序区加入了新版本的 Print 函数用于重载:
2019-07-15_204359.png


---------------------------------------------------------------------------------------------------------------------

话说回来这么牛B闪闪的 F( ) 宏在 Arduino 的参考手册上居然没写,看样子只针对高端,
常规用户还是乖乖去买 DUE 升级系统吧


 楼主| 发表于 2019-7-16 15:28 | 显示全部楼层
很感激你耗时去探究!
这就好办了,可以放心的写下去了。再次表示感谢!
发表于 2022-4-9 12:28 | 显示全部楼层
非常非常感谢!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|Archiver|手机版|Arduino中文社区

GMT+8, 2024-11-28 09:35 , Processed in 0.099618 second(s), 18 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表