變數、指標和關鍵字
兩個口訣:
- 變數變數,能變,就是能讀能寫,必定在記憶體(RAM)裡
- 指標指標,儲存的是地址,32 位處理器中的地址都是 32 位的,無論是什麼型別的指標變數,都是 4 位元組
指標
- 對於 32 位處理器裡面,地址是 32 位的,所以指標的大小為 4 位元組,
sizeof(p) = 4
,sizeof(*p) = 指標所指向的型別所佔的空間
變數
- 只讀的常量一般放在 flash 中,所以只讀的變數加上
const
可以節省記憶體,但有時候為了最佳化也可能會放在記憶體裡
extern 關鍵字
- 如果想在
a.c
中引用b.c
中的全域性變數int b
,需要在a.c
中加入:extern int b
- 包含標頭檔案
#include "b.h"
,然後在標頭檔案中寫extern int b
注意 extern int b 不能被賦值!!!這個是宣告,表示 b 是什麼
不建議使用 extern,可以使用函式來進行值傳遞
static 關鍵字
- 對於全域性變數,如果不加
static
,全域性變數的作用域為整個程式,加上 static 作用域就變為該檔案了(函式前加static
也是同樣的作用) - 對於在函式內定義的變數,加上
static
的變數僅會初始化一次,再次呼叫該函式時,仍為上一次函式呼叫的結果,不會再次初始化
volatile 關鍵字
-
不能自作主張最佳化變數,直接存取原始記憶體地址
-
應用場景:
- 中斷服務程式中修改的供其他程式檢測的變數,需要用
volitile
- 多工環境中個任務間共享的標誌加
volitile
- 儲存器對映的硬體儲存器要加
volitile
後面兩個場景還沒用到,等用到再具體補充
- 中斷服務程式中修改的供其他程式檢測的變數,需要用
結構體
- 結構體是不佔記憶體的,是一種型別,用結構體定義了變數之後(例項化)才會分配記憶體空間
結構體對齊
為什麼會有記憶體對齊
- 平臺原因:不是所有的硬體平臺都能訪問任意記憶體地址上的任意資料,某些硬體平臺只能在某些地址處取某些特定型別的資料,否則丟擲硬體異常。為了同一個程式可以在多平臺執行,需要記憶體對齊。
- 硬體原因:經過記憶體對齊後,CPU 訪問記憶體的速度大大提升。
對齊規則
- 結構體每個成員相對結構體首地址的偏移量是對齊引數的整數倍,如有需要會在成員之間填充位元組。編譯器在為結構體成員開闢空間時,首先檢查預開闢空間的地址相對於結構體首地址的偏移量是否為對齊引數的整數倍,若是,則存放該成員,若不是,則填充若干位元組,以達到整數倍的要求。
這裡的對齊引數取每個變數自身對齊引數和系統預設引數#pragma pack(n)(一般為 8)中較小的那個
- 結構體變數所佔空間的大小是對齊引數大小的整數倍。如有需要會在最後一個成員末尾填充若干位元組使得所佔空間大小是對齊引數大小的整數倍。
這裡的對齊引數取結構體中所有變數對齊引數的最大值和系統預設引數對比取較小的
- 即結構體變數所佔的空間大小需要經過兩次對齊
圖解
| char | | | | 4位元組
| int | int | int | int | 4位元組
| short|short| | | 4位元組
| char | |short|short| 4位元組
| int | int | int | int | 4位元組
例項
- 例項 1:
typedef struct
{
char c;
short d;
static int a;
}A;
| char | | 2位元組
| short|short| 2位元組
易錯點:對於結構體中的 static int a
,靜態資料成員存放位置與結構體例項的儲存地址無關,不算在裡面
只有 C++ 結構體中才有 static,C 語言中不允許有
- 例項 2:
typedef struct
{
double b;
int c;
}D;
typedef struct
{
bool a; // bool為1位元組
D d;
double b;
int c;
}E;
|bool|-----------------------------------| 8位元組
|--------------------D-------------------| 8位元組
|--------------------D-------------------| 8位元組
|------------------double----------------| 8位元組
|---------int--------|-------------------| 8位元組
易錯點:D 與預設的 8 比,8 小,取 8 為對齊引數
變數賦值
a = 123
等價於
p = &a;
*p = 123; // 將a的值變為123
A.age = 20
等價於(A 為結構體)
pt = &A;
pt->age = 20; // pt為指標,取成員用"->",結構體用"."
*pt = A2; // 將A變成A2的值
結構體指標
typedef struct student{
char *name;
int age;
struct student *classmate; // 結構體中只能用指標,長度為4個位元組(連結串列)
}student, *pstudent;
student zhangsan = {"zhangshan", 10, NULL};
student lili = {"lili", 20, NULL};
zhangsan.classmate = &lili; // 構成連結串列
name = zhangsan.classmate->name; // zhangsan為結構體,用"."取值,classmate為指標,用"->"取值
函式指標
void (*add){}; // 函式指標,變數,佔4位元組
typedef struct student{
char *name;
int age;
void (*good_work)(void); // 函式指標
struct student *classmate;
}student, *pstudent;
// add為函式指標,也可以寫成&add
// 函式指標是變數,所以可以賦值為地址,函式不是變數,只能被呼叫
student ss[2] = {{"zhangshan", 10, add, NULL}, {"lili", 20, add, NULL}}; // 結構體陣列
ss[1].good_work(); // 結構體呼叫函式指標,用"."
pstudent get(void){
int type = 1;
return &ss[type]; // 返回結構體指標
} // 應儘量避免使用全域性變數,可以將變數封裝在函式里
pstudent a;
a = get();
a->good_work(); //這裡a為結構體指標,用"->"
全域性變數與區域性變數
-
全域性變數:斷電時無,執行時有,初值來自 Flash
- 有值時初始化:類似於
memcpy
,把 Flash 資料段整體複製到記憶體 - 初始值為 0/沒有初始化的:這些變數在記憶體裡都放到了 ZI 段,類似於
memset
,把 ZI 段全部清零
- 有值時初始化:類似於
-
區域性變數:在棧裡
參考資料
https://www.bilibili.com/video/BV1VM4y137Pm/?spm_id_from=333.999.0.0
https://blog.csdn.net/qq_41068271/article/details/83446623?spm=1001.2014.3001.5502