淺談C51內存優化

admin @ 2014-03-25 , reply:0

概述
   對51單片機內存的認識,很多人有誤解,最常見的是以下兩種 超過變數128后必須使用compact模式編譯  實際的情況是只要內存佔用量……

    對 51 單片機內存的認識,很多人有誤解,最常見的是以下兩種

  1.  超過變數128后必須使用compact模式編譯
       實際的情況是只要內存佔用量不超過 256.0 就可以用 small 模式編譯
  2.  128以上的某些地址為特殊寄存器使用,不能給程序用
       與 PC 機不同,51 單片機不使用線性編址,特殊寄存器與 RAM 使用重複的重複的地址。但訪問時採用不同的指令,所以並不會佔用 RAM 空間。

    由於內存比較小,一般要進行內存優化,盡量提高內存的使用效率。

    以 Keil C 編譯器為例,small 模式下未指存儲類型的變數默認為data型,即直接定址,只能訪問低 128 個位元組,但這  128 個位元組也不是全為我們的程序所用,寄存器 R0-R7必須映射到低RAM,要佔去 8 個位元組,如果使用寄存組切換,佔用的更多。

    所以可以使用 data 區最大為 120 位元組,超出 120 個位元組則必須用 idata 顯式的指定為間接定址,另外堆棧至少要佔用一個位元組,所以極限情況下可以定義的變數可占 247 個位元組。當然,實際應用中堆棧為一個位元組肯定是不夠用的,但如果嵌套調用層數不深,有十幾個位元組也夠有了。


為了驗上面的觀點,寫了個例子

#define LEN 120
data UCHAR tt1[LEN];
idata UCHAR tt2[127];

void main()
{
    UCHAR i,j;

    for(i = 0;  i < LEN; ++i )
    {
        j = i;
        tt1[j] = 0x55;
    }
}

可以計算 R0-7(8) + tt1(120) + tt2(127) + SP(1) 總共 256 個位元組

keil 編譯的結果如下:
Program Size: data=256.0 xdata=0 code=30
creating hex file from ".\Debug\Test"...
".\Debug\Test" - 0 Error(s), 0 Warning(s).
(測試環境為 XP + Keil C 7.5)

    這段代碼已經達到了內存分配的極限,再定義任何全局變數或將數組加大,編譯都會報錯 107

    這裡要引出一個問題:為什麼變數 i、j 不計算在內?
    這是因為 i、j 是局部變數,編譯器會試著將其優化到寄存器 Rx 或棧。問題也就在這了,如果局部變數過多或定義了局部數組,編譯器無法將其優化,就必須使用 RAM 空間,雖然全局變數的分配經過精心計算沒有超出使用範圍,仍會產生內存溢出的錯誤!

    而編譯器是否能成功的優化變數是根據代碼來的
    上面的代碼中,循環是臃腫的,變數 j 完全不必要,那麼將代碼改成

UCHAR i;
UCHAR j;

for(i = 0;  i < LEN; ++i )
{
    tt1[i] = 0x55;
}

再編譯看看,出錯了吧!
因為編譯器不知道該如何使用 j,所以沒能優化,j 須占 RAM 空間,RAM 就溢出了。
(智能一點的編譯器會自動將這個無用的變數去掉,但這個不在討論之列了)

另外,對 idata 的定義的變數最好放在 data 變數之後

對於這一種定義


uchar c1;
idata uchar c2;
uchar c3;
變數 c2 肯定會以間接定址,但它有可能落在 data 區域,就浪費了一個可直接定址的空間


變數優化一般要注意幾點:

    ①讓儘可能多的變數使用直接定址,提高速度
      假如有兩個單位元組的變數,一個長119的字元型數組
      因為總長超過 120 位元組,不可能都定義在 data 區
      按這條原則,定義的方式如下:

      data UCHAR tab[119];
      data UCAHR c1;
      idata UCHaR c2;
      但也不是絕的,如果 c1, c2 需要以極高的頻率訪問,而 tab 訪問不那麼頻繁
      則應該讓訪問量大的變數使用直接定址:

      data UCAHR c1;
      data UCHaR c2;
      idata UCHAR tab[119];
      這個是要根據具體項目需求來確定的

    ②提高內存的重複利用率
      就是儘可能的利用局部變數,局部變數還有個好處是訪問速度比較快
      由前面的例子可以看出,局部變數 i, j 是沒有單獨佔用內存的
      子程序中使用內存數目不大的變數盡量定義為局部變數

    ③對於指針數組的定義,儘可能指明存儲類型
       盡量使用無符號類型變數
      一般指針需要一個位元組額外的位元組指明存儲類型
     8051 系列本身不支持符號數,需要外加庫來處理符號數,一是大大降低程序運行效率,二是需要額外的內存

    ④避免出現內存空洞
      可以通過查看編譯器輸出符號表文件(.M51)查看
      對前面的代碼,M51文件中關於內存一節如下:

* * * * * * *   D A T A   M E M O R Y   * * * * * * *
REG     0000H     0008H     ABSOLUTE     "REG BANK 0"
DATA    0008H     0078H     UNIT         ?DT?TEST
IDATA   0080H     007FH     UNIT         ?ID?TEST
IDATA   00FFH     0001H     UNIT         ?STACK

第一行顯示寄存器組0從地址0000H開始,佔用0008H個位元組
第二行顯示DATA區變數從0008H開始,佔用0078H個位元組
第三行顯示IDATA區變數從0080H開始,佔用007F個位元組
第四行顯示堆棧從00FFH開始,佔0001H個位元組

由於前面代碼中變數定義比較簡單,且連續用完了所有空間,所以這裡顯示比較簡單
變數定義較多時,這裡會有很多行

如果全局變數與局部變數分配不合理,就有可能出現類似下面的行

0010H     0012H                  *** GAP ***
      該行表示從0010H開始連續0012H個位元組未充分利用或根本未用到
出現這種情況最常見的原因是局變數太多、多個子程序中的局部變數數目差異太大、使用了寄存器切換但未充分利用




[admin via 研發互助社區 ] 淺談C51內存優化已經有2444次圍觀

http://cocdig.com/docs/show-post-42948.html