# C static、const、volatile ###### tags: `C` * 常見的內容涵蓋 const, static, volatile, call by value, call by reference, bitwise operation, pointer, stack * struct、enum、typedef ## <span class="red">define</span> <style> .red { color: red; } .blue { color: blue; } .green { color: green; } .yellow { color: yellow; } </style> * #define 是給 preprocessor 看的prepprocessor 看完執行完 #define 及置換後, 會把這些 #define 的東西拿掉, 所以 C 編譯器是看不到這些 * #define 的 * #define 的作用範圍是整個檔案 (從 #define 開始一直到 #undef或檔案結束) * typedef * typedef 是給 C 編譯器看的. * typedef 的作用範圍: 和變數宣告遵守相同的規則 ## <font class="red">typedef</font> * typedef 關鍵字用於命名使用者定義的物件。在程式碼中經常需要多次宣告結構。 * 如果不使用 typedef 來定義它們,每次宣告都需要以 struct/enum 關鍵字開始,這就使得程式碼的可讀性變得很重。 * typedef 是 type definition的縮寫,型態的定義。 * typedef 已經有的資料型態重新定義其他名稱,也就是說定義自己的資料型態。 * typedef 資料型態 識別字; ## <span class="red">static</span> * c 語言中static 關鍵字有兩個作用,一是檔案作用域,二是函式作用域。 1. 檔案作用域關鍵字static 的作用是,以static 申明的全域性變數、函式不得被其他檔案所引用。 2. 函式內部靜態變數,只會被初始化一次,而且變數儲存在全域性資料段中而不是函式棧中,所以其生命期會一直持續到程式退出。 * 舉個回撥函式的栗子:雖然counting()函式多次回撥自身,但是靜態整型變數 count 並沒有每次都被初始化,而是隻初始化了一次,達到了計數目的。 ## <span class="red">inline</span> * inline只是建議編譯器要做行內編譯,實際上編譯器不一定會執行。通常要 -O1 以上才有效果 * inline : 宣告可被引用的函式 (為C++語言時代新的語法,會有編譯器限制) * #define在程式行數多的時候比較不好解讀,inline 則反之 ## <span class="red">static inline</span> * 所以說當inline沒被compiler接受的時候.... * inline -> 變成普通函式 -> 外部可用extern呼叫 * static inline -> 變成普通static函式 -> 外部不可用extern * extern inline -> 變成普通函式 -> 外部可用extern呼叫 * 當inline被compiler接受時: * inline -> 變成inline -> 外部不可用extern * static inline -> 變成inline -> 外部不可用extern * extern inline -> 變成inline+普通函式 -> 外部可用extern呼叫 ## <span class="red">volatile</span> * volatile英文意思是可變的。在C語言中,一個定義為volatile 的變數是說這變數很可能會被意想不到地改變,因此需要小心對待。 * 也就是說,優化器在用到這個變數時必須每次重新從虛擬記憶體中讀取這個變數的值,而不是使用儲存在暫存器裡的備份。 ## <span class="red">const</span> * 資料型別被const修飾的變數在 " 初始化 " 之外不能再被賦值 * 只可以讀,不能寫 * 可以看待成一個唯讀(read-only)的屬性 * 只要const在左邊常數在左邊內容物都不可變動 * 除非使用指標,指向的內容是可改變的 * 一般範例 ```= int a = 3; const int b =5; //唯讀 數值不可改 const int c; //編譯可過,但沒有初數 a = 4; b = 6; //編譯失敗 ``` * 矩陣範例 ```= int a[3] = {3 , 4, 5}; const int b[3] = {5 , 6, 7}; a [0] = 4; b [0] = 6; //編譯失敗,因為const整數是不能被修改 ``` * 指標範例 ```= const chat *str = "text"; srt[0] = 0; //編譯失敗 ``` * 綜合範例 ```= const int * a = &b; // 指標指向的內容不可改變 int const * a = &b; // 同上 int * const a = &b; // 常數指標,即指標本身的值是不可改變的,但指向的內容是可改變的 const int * const a = &b; // 指向常數的常數指標,即指標本身與指向的內容都是不可改變的 ``` ### <span class="red">const static and static const</span> * 此方式 可以在 .h 使用 ``` = static const uint8_t gpio_port[] = {0, 2, 2, 2, 2, 2, 2, 5, 5, 5, 2, 2, 2, 4, 2, 2, 0, 0, 0, 0, 5, 5, 5, 1, 5, 5, 1, 2, 2, 2, 3, 3, 3, 0, 5, 5, 3, 3, 3, 2, 3, 3, 3, 3, 3, 5, 0}; static const uint8_t gpio_bit[] = {12, 1, 2, 3, 4, 5, 14, 12, 13, 14, 9, 10, 11, 11, 12, 13, 11, 2, 3, 13, 2, 5, 6, 10, 8, 9, 15, 6, 15, 7, 1, 4, 0, 5, 15, 16, 5, 6, 7, 8, 9, 10, 11, 12, 15, 18, 15}; ``` ```= static GPIO_TypeDef * const gpio_port[] = {GPIOA, GPIOA, GPIOA, GPIOA, GPIOA, GPIOA, GPIOA, GPIOA, GPIOA, GPIOB, GPIOB, GPIOB, GPIOB, GPIOB, GPIOB, GPIOB, GPIOB, GPIOB, GPIOB, GPIOB}; static const uint16_t gpio_pin[] = {GPIO_PIN_0, GPIO_PIN_1, GPIO_PIN_2, GPIO_PIN_3, GPIO_PIN_4, GPIO_PIN_8, GPIO_PIN_11, GPIO_PIN_12, GPIO_PIN_15, GPIO_PIN_1, GPIO_PIN_2, GPIO_PIN_3, GPIO_PIN_4, GPIO_PIN_5, GPIO_PIN_6, GPIO_PIN_7, GPIO_PIN_12, GPIO_PIN_13, GPIO_PIN_14, GPIO_PIN_15}; ``` ## <font color="#f00">enum</font> ![](https://i.imgur.com/0JTU4lB.png) * enum 是一種特殊的常數定義方式,enum基本上只是整數值,其名稱為變數(用於在數值上)。 * 藉由enum型態宣告,取代較不易記憶的一組整數常數 * 第一種是給常數名稱 * 第二種是給數字然後轉換成列舉的型別 ```= enum 型態名稱 { 常數 1 , 常數 2 , 常數 3 , 常數 n }; ``` ```= enum 型態名稱 常數 1 , 常數 2, 常數 3, 常數 n; ``` ### <span class="red">typedef enum</span> * 使用 typedef enum 定義包含命名整數常量的物件的定製型別 * typedef enum 大部分會應再用statemachine(如範例二、三). * 底下使用void and Enum開頭兩個差異在void不會回傳值如return,Enum會有回傳值需要再最後加入return。 * void Bootloader_StateMachine(EnumBootloaderStateMachine choose) * EnumBootloaderStateMachine Bootloader_StateMachine(EnumBootloaderStateMachine choose) #### <span class="red">typedef enum 方法 一 </span> * 抓取固定數值 ```= typedef enum { read = 0, blule = 1, gread = 2, yeall = 3 }MACHINE_STATE; ``` ```= int main(void) { int input1; MACHINE_STATE state; state = input1; switch(state) { case read: printf("read: read \r\n"); break; case blule: printf("blule: blule \r\n"); break; case gread: printf("gread: gread \r\n"); break; case yeall: printf("yeall: yeall \r\n"); break; } } ``` #### <span class="red">typedef enum 方法 二 </span> * 這個結構是為了實現一個系統開機狀態機 <span class="red"> (State Machine),它利用一個全域靜態 (volatile static) 的列舉變數 system_mode 來儲存當前狀態</span>。 - 使用 inline 函式<span class="red"> get_system_mode()</span> 來讀取這個狀態,目的是提供一個高效且一致的狀態存取介面,同時避免直接暴露變數。 ------------------------------------------ * 全域狀態變數 (system_mode): - 被聲明為 volatile static。 - <span class="red">volatile: 提醒編譯器該變數的值可能隨時被主程式之外的因素(例如中斷服務程式 Interrupt Service Routines, ISRs)改變</span>。 - static: 將變數的作用域限制在其定義的檔案內,實現資訊隱藏。 - 初始化為 SYSTEM_MODE_IS_OFF,作為系統的初始/預設狀態。 - 狀態讀取函式 (get_system_mode): - 聲明為 inline 的無參數函式,僅用於回傳當前 system_mode 的值。 - 作用是為外部程式碼提供一個受控且高效地讀取狀態的介面,避免直接操作全域變數。 ```= volatile static SYSTEM_MODE_T system_mode = SYSTEM_MODE_IS_OFF; inline SYSTEM_MODE_T get_system_mode(void){ return system_mode; } ``` * 使用 typedef enum 定義所有可能的系統狀態(如關機、延遲、開啟、低電量等),提供了清晰的離散狀態集。 ```= typedef enum { SYSTEM_MODE_NONE = -1, SYSTEM_MODE_IS_OFF = 0, ON_Delay = 1, SYSTEM_MODE_IS_ON = 2, OFF_EVENT_DELAY = 3, HARD_OFF_DELAY = 4, SYSTEM_Batt_low_status = 5, LOW_HARD_DELAY = 6, SYSTEM_MODE_HARD_OFF_DELAY = 7, SYSTEM_MODE_POWER_BUTTON = 8, SYSTEM_Batt_Low_Power = 9, SYSTEM_MODE_IGN_WAIT = 10, } SYSTEM_MODE_T; ``` ```=+ volatile static SYSTEM_MODE_T system_mode = SYSTEM_MODE_IS_OFF; /** intialize system_mode to SYSTEM_IS_OFF */ inline SYSTEM_MODE_T get_system_mode(void){ return system_mode; } ``` ```=+ SYSTEM_MODE_T sys_mode; sys_mode = get_system_mode(); ignotion_enable = gpio_read(IGN_ENABLE); if ( ignotion_enable == 1) { while(1) { switch(mode) { case SYSTEM_MODE_IS_OFF : sys_mode = ON_Delay; break; case ON_Delay: sys_mode = SYSTEM_MODE_IS_ON; break; case SYSTEM_MODE_IS_ON: if (low_power) { sys_mode = SYSTEM_Batt_Low_Power; break; } if (gpio_read(PG_DCIN) == GPIO_PIN_SET) { _system_shutdown(); sys_mode = SYSTEM_MODE_IGN_WAIT; break; } if (gpio_read(V12_SB_D_PG) == GPIO_PIN_SET) { _system_shutdown(); sys_mode = SYSTEM_MODE_IGN_WAIT; break; } if (gpio_read(PG_MAIN_PWR) == GPIO_PIN_RESET) { _system_shutdown(); sys_mode = SYSTEM_MODE_IGN_WAIT; break; } break; case SYSTEM_MODE_IGN_WAIT: sys_mode = SYSTEM_MODE_IS_OFF; break; case SYSTEM_MODE_HARD_OFF_DELAY: for (i = 0; i < vpm_reg.hard_off_dly ; i++) { /* Maximum HARD_OFF_DELAY */ if (gpio_read(PG_MAIN_PWR) == GPIO_PIN_RESET) { _system_shutdown(); break; } HAL_Delay(1000); } sys_mode = SYSTEM_MODE_IGN_WAIT; break; case SYSTEM_Batt_low_status: if (_Batt_Low_power_status()) { HAL_Delay(1000); } else { sys_mode = ON_Delay; } break; case LOW_HARD_DELAY: sys_mode = SYSTEM_MODE_IS_OFF; break; case OFF_EVENT_DELAY: sys_mode = SYSTEM_MODE_IS_OFF; break; case HARD_OFF_DELAY: sys_mode = SYSTEM_MODE_IS_OFF; break; } } } ``` #### <span class="red">typedef enum 方法 三 </span> * <span class="red">ENUM_POWER:</span>作為狀態機的狀態集,定義了電源啟動流程中的各個階段。 - <span class="red">EnumPowerStateMachine:</span>這是狀態遷移函式。它接收當前狀態 (PowerStage),執行該狀態的任務,並回傳下一個狀態。 - <span class="red">回傳機制 (return PowerStage;):</span>實現單步狀態推進。在主迴圈 (main) 中,不斷以 <span class="red">InitPower = EnumPowerStateMachine(InitPower); </span>的方式呼叫,使系統狀態能夠依序推進並停駐在最終狀態,直到條件滿足或重新啟動。 - InitPower:追蹤系統的當前狀態,初始值設為啟動的起點 POWER_DBG。 ----------------------------- ```= typedef enum { POWER_DBG =0, POWER_DBG1 =1, POWER_DBG2 =2, POWER_DBG3 =3, POWER_DBG4 =4, POWER_DBG5 =5, }ENUM_POWER; ENUM_POWER EnumPowerStateMachine(ENUM_POWER PowerStage); ENUM_POWER InitPower = POWER_DBG; ``` * 狀態定義 (ENUM_POWER): - 使用 typedef enum 定義所有電源啟動狀態,確保狀態是離散且清晰的。 - 例如:<span class="red">POWER_DBG (初始)、POWER_DBG1、...、POWER_DBG5 (完成/待機)。</span> * 狀態機函式 (EnumPowerStateMachine): - 函式原型:<span class="red">ENUM_POWER EnumPowerStateMachine(ENUM_POWER PowerStage);</span> - 核心機制: 函式接受當前狀態 (PowerStage) 作為輸入參數,並在執行該狀態的邏輯後,回傳 (return) 系統應該進入的下一個狀態。 - "回傳機制"的用途: 這種設計允許外部呼叫者 (通常是主迴圈 main 或作業系統排程) 在每次呼叫後,獲取並更新系統的當前狀態。 * 狀態變數 (InitPower): - ENUM_POWER InitPower = POWER_DBG; - 用於追蹤和儲存狀態機的當前狀態。它被初始化為狀態機的起始點 POWER_DBG。 ```= ENUM_POWER EnumPowerStateMachine(ENUM_POWER PowerStage) { switch (PowerStage) { case POWER_DBG: { // 執行所需要的程式代碼,條件成立後會到下一步POWER_DBG1 PowerStage = POWER_DBG1; break; } case POWER_DBG1: { // 執行所需要的程式代碼,條件成立後會到下一步POWER_DBG2 PowerStage = POWER_DBG2; break; } case POWER_DBG2: { // 執行所需要的程式代碼,條件成立後會到下一步POWER_DBG3 PowerStage = POWER_DBG3; break; } case POWER_DBG3: { // 執行所需要的程式代碼,條件成立後會到下一步POWER_DBG4 PowerStage = POWER_DBG4; break; } case POWER_DBG4: { // 執行所需要的程式代碼,條件成立後會到下一步POWER_DBG5 PowerStage = POWER_DBG5; break; } case POWER_DBG5: { // 執行所需要的程式代碼,如果您需要在最後的開關回到初始狀態就讓它結束後跳轉到PowerStage = POWER_DBG; // 若不需要,PowerStage就不用寫任何狀態,卡在POWER_DBG5內即可 break; } default: // None { break; } } return PowerStage; } ``` * 迴圈狀態的實現: 狀態機在一個無限迴圈 (while(1)) 中不斷被呼叫。 * 狀態更新: 每次呼叫時,InitPower (當前狀態) 被傳入 EnumPowerStateMachine,函式回傳下一個狀態,並重新賦值給 InitPower。 * 狀態停駐: - 如果某個狀態 (如 POWER_DBG5) 完成其任務後,沒有指定下一個狀態 (即沒有修改 PowerStage,直接 return PowerStage;),則狀態機將停駐 (Stuck) 在該狀態,直到外部條件或程式碼邏輯改變其狀態。 - 若要從 POWER_DBG5 重新啟動整個流程,則可將 PowerStage 設為 POWER_DBG。 ```= int main() { while(1) { InitPower = EnumPowerStateMachine(InitPower) // 括號內使用 InitPower or POWER_DBG 都可行 } } ``` #### <span class="red">typedef enum 方法 四 </span> * 透過enum宣告init為一個function ```= EnumInit Init(EnumInit Stage, I2cMasterObj* choseX, DateBuff* databuff); ``` ## <span class="red">Struct</span> ![](https://i.imgur.com/R99iUKD.png) * 以下定義結構資料型態 * struct 結構名稱 變數1 變數2 變數3 * struct 結構型態 結構陣列名稱[元素] * struct 結構型態 *結構指標名稱 * 最常見的使用struct方式是 * 列很多資料型態 * 當成buffer使用 ### <span class="red">Struct 型態</span> ```= typedef union { unsigned char byte[4]; unsigned long word; }StringWordType; typedef stuct { unsigned short data1; unsigned short data2; unsigned char data3; unsigned char data4; unsigned char data5; unsigned long data6; unsigned long data7; unsigned short data8; StringWordType data9; }DataValue; ``` ```= typedef struct cur_type { int cur_state; //current state int old_state; //old state }cur_type_t; cur_type_t get_cur = { .cur_state = SYSTEM_MODE_NONE, .old_state = SYSTEM_MODE_NONE }; ``` #### <span class="red">typedef struct 範例1</span> * 本範例將function用成struct + static ```= typedef struct BOARD_VDATA { float Pertial_Pressure_12V_24V; float Pertial_Pressure_37V_48V; float voltages_12V_24V; float voltages_37V_48V; } BOARD_VDATA_T; BOARD_VDATA_T *read_voltages(void); static BOARD_VDATA_T adc_vdata; ``` ```= float get_adc_12V_Pertial_Pressure(void) { float value_12V_24V = read_adc_value(ADC_CHANNEL_6); float D4_value = (float) value_12V_24V * ADC_Partial_Pressure; return D4_value; } inline float adc_read_12v() { return get_adc_12V_24V(); } BOARD_VDATA_T *read_voltages(void) { adc_vdata.Pertial_Pressure_12V_24V = get_adc_12V_Pertial_Pressure(); adc_vdata.Pertial_Pressure_37V_48V = get_adc_37V_Pertial_Pressure(); adc_vdata.voltages_12V_24V = get_adc_12V_24V(); adc_vdata.voltages_37V_48V = get_adc_37V_48V(); return &adc_vdata; } ``` ```= BOARD_VDATA_T *pVdata = read_voltages(); dbg_printf("adc12 :%.2fv \r\n", pVdata->voltages_12V_24V); dbg_printf("adc37 :%.2fv \r\n", pVdata->voltages_37V_48V); ``` #### <span class="red">typedef struct 範例2</span> ```= typedef struct BOARD_VDATA { float supply_48V; float supply_12V; float supply_3P3; float supply_5V; float cam1_48V; float cam2_48V; float cam3_48V; float cam4_48V; } BOARD_VDATA_T; void NTC7904_update_voltages(void); BOARD_VDATA_T* NTC7904_read_voltages(void); ``` ```= static BOARD_VDATA_T vdata; ``` ```= void NTC7904_update_voltages(void) { vdata.supply_48V = get_ntc7904_voltage48v(); vdata.supply_12V = get_ntc7904_voltage12v(); vdata.supply_3P3 = get_ntc7904_voltage3_3v(); vdata.supply_5V = get_ntc7904_voltage5v(); vdata.cam1_48V = get_ntc7904_voltage48v_cam1(); vdata.cam2_48V = get_ntc7904_voltage48v_cam2(); vdata.cam3_48V = get_ntc7904_voltage48v_cam3(); vdata.cam4_48V = get_ntc7904_voltage48v_cam4(); } BOARD_VDATA_T* NTC7904_read_voltages(void) { return &vdata; } ``` ## <span class="red">Unions</span> ![](https://i.imgur.com/vEe5qkO.png) ### <span class="red">Code Function</span> ``` #include <stdio.h> // Declaration of union is same as structures union test { int x, y; }; int main() { // A union variable t union test t; t.x = 2; // t.y also gets value 2 printf("After making x = 2:\n x = %d, y = %d\n\n", t.x, t.y); t.y = 10; // t.x is also updated to 10 printf("After making y = 10:\n x = %d, y = %d\n\n", t.x, t.y); return 0; } ``` ## <span class="red">函數</span> ### <span class="red">strcpy</span> * strcpy提供了字符串的複製。即strcpy只用於字符串複製,並且它不僅複製字符串內容之外,還會複製字符串的結束符。 ``` char *strcpy(char *dest, const char *src) ``` ### <span class="red">memcpy</span> ``` void *memcpy(void *str1, const void *str2, size_t n) ``` * memcpy提供了一般內存的複製。即memcpy對於需要複製的內容沒有限制,因此用途更廣。 ``` = #include <stdio.h> #include <string.h> int main () { const char *src = "http://www.gitbook.net/html"; char dest[50]; printf("Before memcpy dest = %s ", dest); memcpy(dest, src, strlen(src)+1); printf("After memcpy dest = %s ", dest); return(0); } ``` ### <span class="red">memset</span> ``` void *memset(void *str, int c, size_t n) ``` * memset:作用是在一段記憶體塊中填充某個給定的值,它是對較大的結構體或陣列進行清零操作的一種最快方法。 ```= #include <stdio.h> #include <string.h> int main () { char str[50]; strcpy(str,"This is string.h library function"); puts(str); memset(str,'$',7); puts(str); return(0); } ``` ## 參考 * [Enumeration (or enum) in C](https://www.geeksforgeeks.org/enumeration-enum-c/?ref=leftbar-rightbar) * [Structures in C](https://www.geeksforgeeks.org/structures-c/?ref=lbp) * [Union in C](https://www.geeksforgeeks.org/union-c/?ref=lbp) * [C語言 memset()用法及代碼示例](https://vimsky.com/zh-tw/examples/usage/memset-c-example.html)