嵌入式系统C语言编程小心使用局部变量

来源:互联网 发布:图片放去噪算法 编辑:程序博客网 时间:2024/05/19 22:50

问题:

今天同事在写一个STM32上的程序时,总是遇到内存溢出的错误。结果发现是因为使用了一个局部变量导致的。

因为C语言的局部变量被编译器自动放到栈区的空间(全局变量需要手动申请并释放空间)。嵌入式系统的栈区本来就很小,而且要放进去的变量是一个结构体类型,非常庞大(大数组也会导致相同错误)。所以直接栈区溢出了(或是地址重叠错误)。而且这样的错误在编译的过程中不会有任何错误,只有跑起来才出现……

在这一点上,嵌入式C语言程序有别于其他系统上的。

在我之前的经验中,也曾经犯过类似的错误。在写OS161作业的时候,参数列表中有一个结构体类型的参数,结果在极限测试中,调小了内存后(即:模拟成一个嵌入式系统)出现栈溢出异常。也是编译没有问题,跑用来测试的用户程序时出现错误了。因为函数参数列表中得内容也是放在栈区的。


因为作为一个一般的程序员,其实得到的大部分意见是要避免使用全局变量的。原因很多,摘录了网上的一些说法,基本都涵盖了。

http://blog.sina.com.cn/s/blog_45a7cd600101dp0u.html

实际上在之前写项目的时候,对于一些共用的变量和常量还是喜欢以全局的方式来处理。例如当前浏览器的类型、浏览器的窗口宽度、高度等等。这些东西用全局的方式约定好了毕竟还是比较方便的。但是从安全性和项目管理的便利性来说,也许作为局部变脸(类似JQueryYUI的方式)有可能会好些。

6.1  全局变量带来的问题

作者列举了全局变量带来的几个问题,包括命名冲突、代码脆弱性、测试困难等。因此,个人建议,对于小型项目而言,可以适量地使用一些全局变量。对于大型项目而言,处于项目管理的需要,建议还是用单全局变量比较好,把绝大多数全局变量修改为局部变量,用类、命名空间等来进行封闭,防止变量的污染。

 

6.2  意外的全局变量

在这里还是强调使用变量一定要预先声明,避免Javascript在处理未声明的变量时直接将其作为全局变量处理。使用严格模式可以有效地避免这一状况。因此在调试和开发脚本时,建议采用IE10等更新版本支持严格模式的Web浏览器,并采用严格模式来开发,对每一个第一次使用的变量都用var关键字尽心声明。

 

6.3 单全局变量方式

但全局变量模式是目前作者和笔者都推荐的一种模式。在这种模式下,整个项目的Javascript脚本都必须被封装成为一个单独的类,项目的每个模块都将成为这个类的子类(或称命名空间)下的一个成员。严格和合理地规范各种变量、属性和方法,从而防止混乱的全局变量污染。

既然采用了单全局变量方式(假设开发的是一个非常复杂而庞大的项目),那么就必须引入命名空间和模块的概念,通过合理地划分代码,实现更高效的项目管理。

6.3.1  命名空间

Javascript本身是不存在类、命名空间的说法的。其所有的类和命名空间其实都是函数这一概念衍伸而出的虚拟概念(或伪概念)。在实际开发中,为了提高代码的维护性,使代码更符合面向对象的方式,需要强制性人为的创造这些概念。

作者提供了一个自定义的namespace方法,其属于单全局变量的一个成员。例如,定义单全局变量名称为sc,那么,就可以编写如下的namespace方法。

var sc = {

    namespace : function (ns) {

 

        var parts = ns.split ( '.' ) ;

        var object = this ;

        var I ;

        var len ;

       

        for ( i=0 , len = parts.length ; I < len ; i++ ) {

       

            if ( !object [ parts [ I ] ] ) {

   

                object [ parts [ I ] ] = { } ;

            }

            object = object [ parts [ I ] ] ;

        }

       

        return object;

    }

};

 

然后,即可使用该方法通过表示命名空间对象的字符串来创建命名空间了。例如如下代码

sc.namespace( 'sc.String' ) ;

基于以上方法,可以让项目组成员放心地在每个文件中调用namespace()方法快速创建命名空间,且不会对已有的命名空间造成任何破坏,将项目成员解放出来。

在这里需要注意的是,JS中的命名空间实际上也是一个对象,因此建议从命名法则中将其与其他的对象区分开来,例如前缀添加一个大写或小写NS开头等,或者采用大驼峰等。具体的方式可由项目内部商讨决定。

6.3.2  模块

模块是实现项目中一个局部功能的代码片段集合。Javascript本身是不存在模块的概念的。引入模块的好处是可以提高项目各组成功能代码的独立性,更好地实现松耦合。通常情况下,笔者认为项目的结构应该是单全局变量下包含若干命名空间,命名空间下再包含模块等。模块和模块可以通过继承关系来实现依赖,当然,实际情况应该根据项目组的需求来定。作者提供了两种定义模块的方式,即YUI方式和AMD方式(异步模块定义)。

 

6.4  零全局变量

零全局变量实际上是为了适应一小段封闭代码而采取的一种局部变量处理方式,只适合在一些特殊场景中使用。最常见的就是一些不会被其他脚本访问到的完全独立的脚本。

使用零全局变量的方式需要采用立即执行函数,用法如下。

( function ( win ) {

 

    'use strict' ;

    var doc = win.document ;

    //在此定义其他的变量并书写代码

} )


========================                     华丽的分割线                      ==========================

当然也有嵌入式工程师遇到同样的问题。转载两个帖子做参考。

1. http://www.amobbs.com/thread-2020755-1-1.html

很简单,栈溢出了,所以覆盖全局变量了。

嵌入式编程不同于PC机编程,PC上给栈预留的空间是1M,所以在局部变量一般定义个大数组没事,在嵌入系统中,一般给栈的空间也就256/512字节,所以尽量不要定义大的数组。&nbsp;如果确实要定义,那就给栈留出足够的空间。&nbsp;在KEIL中,可以编辑scat文件,在IAR中,可以编辑icf文件&nbsp;(options->Linker->Config->Linker&nbsp;Configuration&nbsp;file)

在嵌入式编程中,如果遇到神奇的,感觉无法解释的现象,首先考虑栈是不是不够了。


2. http://bbs.ednchina.com/BLOG_ARTICLE_3012331.HTM

关于产生异常中断的定位问题,在《自由编程》Mindows的作者的新浪博客里已经有了详细的描述,根据处理器内核的不同可以通过仿真查看寄存器定位异常发生的位置。值得注意的是cortex内核中在发生异常或中断时 硬件自动把 R0-R3,R12,LR(也就是返回地址),PC,XPSR八个寄存器压入堆栈中,原因嘛还没研究,估计是这几个寄存器有什么特别的作用,其他内核异常的定位问题大抵如此。

说说今天调试遇到的问题:

我在程序中定义了一个调度器任务结构体(最后才发现就是这个结构体定位问题导致的程序错误),错误原因就是程序在执行到结构体内的一个函数指针时直接跳到了HardFault_Handler中,通过一步一步DEBUG发现,是函数运行过程中堆栈空间不足,堆栈和任务结构体的内存重合,导致堆栈指针篡改了任务调度结构体数据。

这类问题的解决办法有两个:

1、调度器结构体绝对定位。在文件中包含#include "absacc.h"  然后使用__at(ADDR) 定位结构体去安全区域。这种方式本身存在隐患。堆栈空间不足

2、改变堆栈空间大小,让编译器来定位调度器结构体的位置。

注意:局部变量存储在堆栈区,所以在定义大的数组的时候尽量定义为全局变量。防止堆栈溢出问题的发生,才是上上策。

debug.jpg


0 0