字節對齊
結構體內成員對齊規則:
1、我們的結構體變量本身就是在4字節對齊的位置,編譯器幫我們做的事。
2、第一個成員,就從結構體開始的地址處,存放。這個元素,具體佔多少字節,由緊挨着下個元素決定。
3、整個成員變量自身都對齊 了,還沒有結束。
4、整個結構體還要是默認字節對齊的最小整數倍。
結構體默認的字節對齊:成員變量最大的那個類型所佔字節
#include<stdio.h>
typedef struct data
{
int a; // 4
char b; // 1 + 1(填充)
short c; // 2 最後整個結構體自身還要對齊
}D;
int main(void)
{
printf("sizeof(D)=%d.\n",sizeof(D));
return 0;
}
結果爲8byte
注意:
struct data
{
int a;
struct data s; // 1.此時結構體類型(定義)不完整 2.C語言不允許
}s;
int main(void)
{
unsigned short a = 0x1234;
unsigned char * p1 = (unsigned char *)&a, i = 8;
//*p1 << i,就產生了臨時變量,內存中你不知道地址,但是有一個默認類型(int)
printf("sizeof(*p1 << i) = %d.\n", sizeof(*p1 << i));
printf("0x%x.\n", *p1 <<= i); // printf("%d.\n", a *= 2);
printf("0x%x.\n", *(p1+1));
}
結果如下:sizeof(*p1 << i)=4
0x0
0x12
#include<stdio.h>
struct data
{
int a; // 4 + 4(padding)
struct data *p_next; //64bit機器指針永遠永遠佔8字節
}S;
int main(void)
{
printf("sizeof(S)=%d.\n",sizeof(S));
return 0;
}
結果爲16byte
結構體的嵌套
#include<stdio.h>
struct data1
{
int a;
short b;
int c;
double e;
};//24
typedef struct data
{
char a;//2
short b;//2
int c;//4
struct data1 s;//24
char ch;//8
}S;
int main(void)
{
printf("sizeof(S)=%d.\n",sizeof(S));
return 0;
}
結果爲40
字段對齊
位字段:專用於結構體,結構體成員的類型必須是:int || unsigned int
有時侯,結構體成員表示的數據很小,就用幾個位來表示。
0字段,不可訪問,只是佔位整個字中剩下的位,全部寫0
無名字段,不可訪問,只是佔位
字(word)=32bit
下一個字段不夠在剩餘的字存放時,必須另起一個字。字段不能跨字。
超出字段表示的數據範圍,結果不可預期
若最後一個字沒有填滿則也將填滿後返回
字段不可取地址
#include <stdio.h>
struct data1
{
unsigned a : 1; // 1就是一個bit,範圍:0~1
int : 31; // 無名字段,不可訪問,只是佔位
unsigned b : 2; // 2就是2個bit,範圍:0~3
unsigned c : 2; // 28位
}s1;
int main(void)
{
printf("szieof(s1) = %d.\n", sizeof(s1));
return 0;
}
結果爲8byte,同時也是2word
特別的是:
struct
{
int a:1;//此時a只有一位且僅爲符號位,取值範圍爲(-1~0)
};
對齊指令
#pragma pack(n) (1、2、4、8、.....)
#pragma pack()
這兩個配合使用,表示一個區間,只有這個區間內的結構體享受這個待遇。
設置 對齊。
如果將n設置爲1,就是不對齊
1、充分利用內存空間,犧牲了速度,降低了訪問效率。
2、提高效率、性能,犧牲了內存空間。
總結:你指定的對齊方式和結構體自身默認對齊方式,倆者取最小的。
#include <stdio.h>
#include <stdlib.h>
#pragma pack(2)
struct data // 16
{
int a;
char b;
double c;
}s;
struct data1
{
char ch;
}s1;
#pragma pack()
檢查系統錯誤的宏
一旦發生了,系統錯誤就會產生一個錯誤數字(errno),對應相應的錯誤字符串。
C標準定義了兩個值 EXIT_SUCCESS 和 EXIT_FAILURE,可以作爲exit()的參數,來分別指示是否爲成功退出。
exit(參數)傳遞給的是父進程,或者shell終端
#define handle_error(msg) do{perror(msg); exit(EXIT_FAILURE);}while(0)
Linux內置宏
linux內核裏的兩個宏:在驅動應用中很廣泛。
off_set_of(type, member)計算結構體內元素的偏移量
containe_of(ptr, type, member),ptr是結構體裏成員的指針,通過調用這個宏計算出結構體的首地址.包含兩句代碼(表達式),必須要加{}.
這兩個宏:內核雙鏈表。
分析:
#define off_set_of(type, member) ((long)&(((type *)0)->member))
1、(type *)0指向結構體零地址
2、((type *)0)->member得到了結構體某個成元變量名
3、給這成員變量名,取地址(相對於零地址),此時&(((type *)0)->member)表示是指針類型
4、強制類型轉換成(long)
#define container_of(ptr, type, member) ({typeof(((type *)0)->member) *_mptr = ptr;(type *)((char *)_mptr-off_set_of(type, member));})
1、得到結構體成員變量的類型
2、指針賦值(得到真實成員變量的地址值)
3、減去偏移量得到一個數字,該數字和結構體本身首地址在數值上一樣
4、最後強制類型轉換爲結構體指針類型
舉個栗子:
#include <stdio.h>
#define off_set_of(type, member) ((long)&(((type *)0)->member))
#define container_of(ptr, type, member) ({typeof(((type *)0)->member) *_mptr = ptr;(type *)((char *)_mptr-off_set_of(type, member));})
struct da
{
int a;
short b;
int c;
double e;
}; // 16
struct data
{
char a;
short b;
int c; // 8
struct da s; // 16
char ch;
}s = {1, 3, 10, 3.14, 1.41};
//1、不會因爲有結構體成員,而影響你的基本類型,決定默認對齊
//2、裏面的結構體對齊方式,已經在外面決定了(遍歷整個完整結構體)
int main(void)
{
printf("sizeof(s) = %d.\n", sizeof(s));
//結構體自身的首地址
printf("&s = %p.\n", &s);
struct da *p = container_of(&s.s.b, struct da, b);
printf("p = %p.\n", p);
printf("&s.s = %p.\n", &s.s);
return 0;
}
p->a == (struct data *)0->a;
/*
//讓你明白強制轉換0地址
struct data *p;
unsigned long a = 0;
p = (struct data *)a;
p->a == (struct data *)0->a;
*/
關於typeof
typeof(),()裏面的參數可以是變量名或者表達式。
typeof(int) p
int *p, a;
typeof(p) p_a = &a;//將p_a的類型轉換爲p的類型,即指針類型