完整教程下載地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980
第9章 STM32H7重要知識點數據類型,變量和堆棧
本章教程為大家介紹數據類型,變量和堆棧的相關知識。
9.1 初學者重要提示
9.2 數據類型
9.3 局部變量和全局變量
9.4 堆棧
9.5 局部變量,全局變量和堆棧實例
9.6 總結
9.1 初學者重要提示
1、 如果對C語言不熟練的話,可以閱讀C語言書:C Primer Plus(第五版)中文版.pdf
論壇下載:http://www.armbbs.cn/forum.php?mod=viewthread&tid=91219。
2、 為了更好的學習本章知識點,可以看之前做的視頻教程第10章,針對H7也將在今年發布視頻教程:
http://www.armbbs.cn/forum.php?mod=viewthread&tid=15408 。
9.2 數據類型
了解數據類型之前要對ANSI C和ISO C的發展史有個了解,特別是C89,C99和C11的由來。
9.2.1 ANSI C和ISO C歷史
- 1983年,美國國家標准協會(ANSI)組成了一個委員會來創立C語言的標准。因為這個標准是1989年發布的,所以一般簡稱C89標准。有些人也把C89標准叫做ANSI C。
- 在1990年,ANSI C89標准被國際標准化組織(ISO)和國際電工委員會(IEC)采納為國際標准,名叫ISO/IEC 9899:1990 - Programming languages C,有些人簡稱C90標准。因此,C89和C90通常指同一個標准,一般更常用C89這種說法。
- 在2000年3月,國際標准化組織(ISO)和國際電工委員會(IEC)采納了第二個C語言標准,名叫ISO/IEC 9899:1999 - Programming languages -- C,簡稱C99標准。
- 在2011年12月,國際標准化組織(ISO)和國際電工委員會(IEC)采納了第三個C語言標准,名叫ISO/IEC 9899:2011 - Information technology -- Programming languages -- C,簡稱C11標准。它是C程序語言的最新標准。
對於我們常用的編譯器MDK和IAR而已,C89,C99和C11均支持。
9.2.2 ARM架構(含Cortex-M系列)數據類型
ARM架構(含Cortex-M系列)的數據類型定義如下:
9.2.3 頭文件stdint.h對數據類型的定義
stdint.h是C99中引進的一個標准C庫的頭文件。目前大部分單片機C編譯器均支持,IAR和MDK都支持。下面是部分內容(來自MDK)。
/* exact-width signed integer types */ typedef signed char int8_t; typedef signed short int int16_t; typedef signed int int32_t; typedef signed __INT64 int64_t; /* exact-width unsigned integer types */ typedef unsigned char uint8_t; typedef unsigned short int uint16_t; typedef unsigned int uint32_t; typedef unsigned __INT64 uint64_t; /* 7.18.1.2 */ /* smallest type of at least n bits */ /* minimum-width signed integer types */ typedef signed char int_least8_t; typedef signed short int int_least16_t; typedef signed int int_least32_t; typedef signed __INT64 int_least64_t; /* minimum-width unsigned integer types */ typedef unsigned char uint_least8_t; typedef unsigned short int uint_least16_t; typedef unsigned int uint_least32_t; typedef unsigned __INT64 uint_least64_t; /* 7.18.1.3 */ /* fastest minimum-width signed integer types */ typedef signed int int_fast8_t; typedef signed int int_fast16_t; typedef signed int int_fast32_t; typedef signed __INT64 int_fast64_t; /* fastest minimum-width unsigned integer types */ typedef unsigned int uint_fast8_t; typedef unsigned int uint_fast16_t; typedef unsigned int uint_fast32_t; typedef unsigned __INT64 uint_fast64_t;
以MDK5.26為例,stdint.h位於如下路徑:
\Keil_v526\ARM\ARMCC\include
以IAR8.X為,stdint.h位於如下路徑:
\IAR Systems\Embedded Workbench 8.1\arm\inc\c
9.2.4 程序中推薦的變量命名方式
看程序的時候,經常會看到各種各樣的變量命名方式,例如聲明一個8位無符號整數,常見的有如下幾種寫法:
u8 err;
U8 err;
INT8U err;
UINT8 err;
uint8 err;
uint8_t err;
當大家閱讀別人寫的程序時,往往會看到風格各異的定義方式,移植部分程序時也不知道采用哪種方式更合適。
我們推薦大家采用最后一種定義方式,這種方法符合C99規范,ST的固件庫都是采用的這種類型定義方式。像我們開發板配套的STM32例程,從2009年最初的版本開始就一直沿用C99的標准寫法來定義整數。
- 知識點拓展
針對變量聲明問題,之前專門發過一個詳細的帖子:
http://www.armbbs.cn/forum.php?mod=viewthread&tid=501
9.3 局部變量和全局變量
9.3.1 局部變量
在一個函數內部定義的變量是內部變量,它只在本函數范圍內有效,也就是說只有在本函數內才能使用它們,在此函數以外是不能使用這些變量的,這稱為局部變量。
使用局部變量注意以下問題:
- 不同函數中可以使用相同名字的變量,它們代表不同的對象,互不干擾。
- 形式參數也是局部變量。
- 局部變量的作用域在函數內部。
9.3.2 全局變量
在函數內部定義的變量是局部變量,而在函數之外定義的變量稱為外部變量,也就是全局變量。使用全局變量的注意事項:
- 全局變量可以為本文件中其他函數所共用。它的有效范圍為從定義變量的位置開始到本源文件結束。
- 設置全局變量的作用是增加了函數間數據聯系的渠道。
- 如果在同一個源文件中,外部變量和局部變量同名,則在局部變量的作用范圍內,外部變量被“屏蔽”,即外部變量將不起作用。
9.3.3 使用全局變量的缺點
程序設計中,建議不要創建太多的全局變量,主要是出於以下三點考慮:
- 全局變量在程序的執行過程中都占用存儲單元,而不是僅在需要時才占用存儲單元。
- 函數的通用性降低了,因為函數在執行時要依賴於其所在的外部變量。如果將一個函數移植到另一個文件中,還要將有關的外部變量及其值一起移植過去。
- 使用全局變量過多,會降低程序的清晰性,特別是多個函數都調用此變量時。
9.3.4 變量的存儲類別
從變量的作用域來分,可以分為全局變量和局部變量,而從變量值存在的時間來看,可以分為靜態存儲方式和動態存儲方式。
- 靜態存儲方式:指在程序運行期間由系統分配固定的存儲空間方式。
- 動態存儲方式:在程序運行期間根據需要進行動態的分配存儲空間方式。
全局變量存儲在靜態存儲區中,動態存儲區可以存放以下數據:
- 函數形式參數,在調用函數時給形參分配存儲空間。
- 局部變量(未加static聲明的局部變量)。
- 函數調用時的現場保護和返回地址等。
9.3.5 用static聲明局部或者全局變量
有時候希望函數中的局部變量的值在函數調用結束后不消失而保留原值,即占用的存儲單元不釋放,在下一次該函數調用時,該變量已有值,就是上一次函數調用結束時的值。這時可以使用關鍵字static進行聲明。
用static聲明一個變量的作用:
- 對局部變量用static聲明,則使用該變量在整個程序執行期間不釋放,為其分配的的空間始終存在。
- 全局變量用static聲明,則該變量的作用域只限於本文件模塊(即被聲明的文件中)。
9.4 堆棧
9.4.1 堆棧作用
棧(stack)空間,用於局部變量,函數調時現場保護和返回地址,函數的形參等。
堆(heap)空間,主要用於動態內存分配,也就是說用 malloc,calloc, realloc等函數分配的變量空間是在堆上。以STM32H7為例,堆棧是在startup_stm32h743xx.s文件里面設置:
9.4.2 寄存器組(堆棧指針寄存器)
Cortex – M7/M4/M3 處理器擁有R0-R15的通用寄存器組。其中R13作為堆棧指針SP。SP有兩個,但在同一時刻只能有一個可以用。
- 主堆棧指針(MSP):這是缺省的堆棧指針,它由OS內核、異常服務例程以及所有需要特權訪問的應用程序代碼來使用。
- 進程堆棧指針(PSP):用於常規的應用程序代碼(不處於異常服務例程中時)。
另外以下兩點要注意:
- 大多數情況下的應用,只需使用指針MSP,而PSP多用於 RTOS 中。
- R13 的最低兩位被硬線連接到0,並且總是讀出0,這意味着堆棧總是4字節對齊的。
9.4.3 Cortex-M7/M4/M3向下生長的滿棧
這個知識點在以后用H7移植RTOS時,非常有用。
9.4.4 堆棧的基本操作
這里對入棧和出棧做個簡單的介紹。PUSH入棧操作:SP先自減 4,再存入新的數值:
POP出棧操作:先從SP指針處讀出上一次被壓入的值,再把SP指針自增 4:
9.5 局部變量,全局變量和堆棧實例
通過下面的實例可以對局部變量,全局變量和堆棧有個感性的認識:
uint32_t a = 0; //全局初始化區, 可以被其他c文件 extern 引用 static uint32_t ss = 0; //靜態變量,只允許在本文件使用 uint8_t *p1; //全局未初始化區 int main(void) { uint32_t b; //棧 uint8_t s[] = "abc"; //棧 uint8_t *p2; //棧 uint8_t *p3 = "123456"; //123456\0在常量區,p3在棧上。 static uint32_t c =0; //全局(靜態)初始化區 p1 = (uint8_t *)malloc(10); //在堆區申請了10個字節空間 p2 = (uint8_t *)malloc(20); //在堆區申請了20個字節空間 strcpy(p1, "123456"); /* 123456字符串(結束符號是0(\0),總長度7)放在常量區, 編譯器可能會將它與p3所指向的"123456"優化成一個地方 */ }
通過查看MAP文件,可以看全局變量在RAM中的位置:
Symbol Name Value Ov Type Size Object(Section) a 0x20000000 Data 4 main.o(.data) p1 0x2000000c Data 4 main.o(.data) ss 0x20000004 Data 4 main.o(.data)
而局部變量要調整狀態進入main函數里面查看:
9.6 總結
C語言的基礎知識點要掌握牢靠,對於后面學習HAL庫源碼大有裨益。