變數、指針和關鍵字 兩個口訣: 變數變數,能變,就是能讀能寫,必定在記憶體(RAM)里 指針指針,保存的是地址,32 位處理器中的地址都是 32 位的,無論是什麼類型的指針變數,都是 4 位元組 指針 對於 32 位處理器裡面,地址是 32 位的,所以指針的大小為 4 位元組,sizeof(p) = 4 ...
變數、指針和關鍵字
兩個口訣:
- 變數變數,能變,就是能讀能寫,必定在記憶體(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