日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

一文了解结构体字节对齐

發布時間:2024/9/3 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 一文了解结构体字节对齐 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

結構體字節對齊詳解

表述如有不正確的地方,歡迎批評指正。

C++/C 常見的基本數據類型:

  • bool
  • short (short int)
  • int
  • long (long int)
  • long long (long long int)
  • float
  • double
  • char
  • Type* (指針類型)

注意:對于char,short,int,long,long long都有無符號類型(unsigned + type),無符號類型可以表示的數據范圍更大。

在常見系統下每種類型所占字節

在判斷結構體所占內存情況前,需要記住每種類型在一般情況下所占字節情況,具體是多少由所用平臺(平臺 ≈ CPU+OS+Compiler)決定,可以用cout<<sizeof(type)<<endl查

32位 平臺

Typebytes
bool1
short2
int4
long4
long long8
float4
double8
char1
Type*4

64位 平臺

Typebytes
bool1
short2
int4
long4
long long8
float4
double8
char1
Type*8

結構體所占內存,不是結構體內成員所占字節的簡單的相加,這涉及到字節對齊的知識(不僅僅是結構體,類也會字節對齊)

先看一個簡單的例子:

#include <iostream> using namespace std;struct Node1 {char a;int b; }; int main() {int node_size = sizeof(Node1);cout<<(node_size)<<endl;return 0; }

輸出為:

8

默認情況下結構體字節對齊方式

結論: 默認情況下:以結構體中最大基本類型為基準進行字節對齊,比如上面那個例子,就是以int 為基準進行結構體字節對齊(int : 4 bytes > char: 1bytes)。
基準的意義是 :結構體所占字節 會是 基準的整數倍數
(內存空間按順序進行連續填充,如果裝不下下一個數據成員就放到在下一行)

(下方統一用null 表示 編譯器為了實現字節對齊 而填充的內容)

1234
aanullnull
bbbb

下一個例子:

#include <iostream> using namespace std;struct Node2 {int a; // 4 bytesshort s; // 2char b; // 1long c; // 4char* d; // 64位: 8 bytes ,32位: 4 bytes }; int main() {int node_size = sizeof(Node2); //64位 : 24 ,32位: 16 cout<<(node_size)<<endl;return 0; }

64位平臺,上述結構體Node2,以8 bytes為基準, 占 24 個字節:

12345678
aaaassbnull
ccccnullnullnullnull
dddddddd

32位平臺,上述結構體Node2 ,以4 bytes為基準,占 16 個字節:

1234
aaaa
ssbnull
cccc
dddd

再看幾個個例子自己體會:
case1:

#include <iostream> using namespace std; struct Node3 {int x1; // 4char y1; // 1int x2; // 4char y2; // 1 }; struct Node4 {char y1; // 1 char y2; // 1int x1; // 4int x3; // 4 }; int main() {cout<<sizeof(Node3)<<" "<<sizeof(Node4)<<endl; // 16 12return 0; }

補充一種情況,case2:

struct Node5 {char c[7]; // 1 * 7int b; // 4 char d; // 1 }; struct Node6 {int a[2]; // 4 * 2char c; // 1 }; cout<<sizeof(Node5)<<endl; // 16 cout<<sizeof(Node6)<<endl; // 12

說明一下Node5,字節對齊基準為4 bytes:

1234
cccc
cccnull
bbbb
dnullnullnull

自己設置結構體字節對齊方式

使用#pragma pack(align_size)自定義對齊基準

struct Node7 {char a[7]; // 1 * 7int b; // 4char c; // 1float f; // 4double d; // 8 };#pragma pack(4) // 設置基準為 4 struct Node8 {char a[7]; // 1 * 7int b; // 4char c; // 1float f; // 4double d; // 8 }; #pragma pack() // 不影響后面的基準:后面的依然為默認基準cout<<sizeof(Node7)<<endl; // 8 + 8 + 8 + 8 = 32 cout<<sizeof(Node8)<<endl; // 4 + 4 + 4 + 4 + 4 + 4 + 4 = 28

詳細用法見: #pragma pack()用法詳解

struct Node9{}; class Node10{}; typedef union {char i; // 1int k[2]; // 4 * 2char c; // 1 } DATE; struct Animal {int cat; // 4DATE cow; // max base var_type:int--> 4char dog; // 1 };cout<<sizeof(Node9)<<endl; // 1 cout<<sizeof(Node10)<<endl; // 1 cout<<sizeof(DATE)<<endl; // 8 cout<<sizeof(Animal)<<endl; // 16 = 4 + 4 + 4

類 所占內存

可以通過offsetof宏來成員變量的偏移,根據偏移量可以確定類的成員變量在內存中大致的分布情況:

/* 32 位的平臺下 */ class Normal {public:int a; // 4char b[3]; // 1 * 3void print(){cout<<"Normal"<<endl;}~Normal(){cout<<"~Normal"<<endl;}}; class Base { public://void** vptr; // 4 隱含虛表指針,在最前面,也參與字節對齊int a; // 4char b[3]; // 1 * 3virtual void print(){cout<<"Base"<<endl;}virtual ~Base(){cout<<"virtual ~Base"<<endl;} }; // main 中輸出 cout<<offsetof(Normal,a)<<endl; // 0 cout<<offsetof(Normal,b)<<endl; // 4 cout<<offsetof(Base,a)<<endl; // 4 cout<<offsetof(Base,b)<<endl; // 8 // 可知虛表指針被隱式放在最前面

和結構體一樣,存在字節對齊,還有以下結論:

  • 普通成員函數不在類的內存區域內(成員函數實際上都是些全局函數,最后都會由this指針進行綁定。所以,他們不會占用類的空間);
  • static修飾的靜態變量:不在類的內存區域內(原因是編譯器將其放在全局變量區);
  • 若有虛函數,類中需要額外存儲虛表指針(它是void*指針,即void** _vfptr,指向一個指針數組),它被隱式放在 最前面 (如果存在多個虛表指針,有一個在最前面)并 參與字節對齊, 同一個類的不同實例共用同一份虛函數表;
  • 對于多繼承,可能有多個虛表指針,子類的虛函數地址放在第一個虛表指針指向的指針數組里面;
  • 子類所特有的成員變量 被放在子類成員變量后面 ,如果有多個父類,有虛函數的總是被放在最前面;

看幾個簡單的例子:

/* 32 位平臺下 */ class Normal {public:int a; // 4char b[3]; // 1 * 3void print(){cout<<"Normal"<<endl;}~Normal(){cout<<"~Normal"<<endl;}}; class Base { public://void** vptr; // 4 隱含虛表指針,在最前面,也參與字節對齊int a; // 4char b[3]; // 1 * 3virtual void print(){cout<<"Base"<<endl;}virtual ~Base(){cout<<"virtual ~Base"<<endl;} }; class Driver:public Base { public:/* Base://void** vptr; // 4 隱含虛表指針,在最前面,也參與字節對齊int a; // 4char b[3]; // 1 * 3*/double d; // 8char c; // 1virtual void print(){cout<<"Driver"<<endl;}virtual ~Driver(){cout<<"virtual ~Driver"<<endl;} }; // main 中輸出 cout<<sizeof(Normal)<<endl; // 4 + 4 = 8 cout<<sizeof(Base)<<endl; // 4 + 4 + 4 = 12 cout<<sizeof(Driver)<<endl; // 8 + 8 + 8 + 8 = 32

有關虛表指針的深度好文見:C++虛函數表,虛表指針,內存分布

放上述推薦的好文里面的兩張圖總結一下:

  • 如果子類繼承了有虛函數的基類,子類中虛表指針個數就是有虛函數的基類的個數
  • 如果基類中沒有虛函數,子類有虛函數,那么就只有一個虛表指針


有了上面兩張圖,類的字節對齊就很好推斷了

/* 32 位平臺下 */ class Base1 { public://void** vptr1; // 4 隱含虛表指針,在最前面,也參與字節對齊int a; // 4char b[3]; // 1 * 3virtual void print(){cout<<"print Base1"<<endl;}virtual ~Base1(){cout<<"virtual ~Base1"<<endl;} }; class Base2 { public://void** vptr2; // 4virtual void print(){cout<<"Base2"<<endl;}virtual ~Base2(){cout<<"virtual ~Base2"<<endl;} }; class Base3 { public://void** vptr3; // 4virtual void print3(){cout<<"print Base3"<<endl;}virtual ~Base3(){cout<<"virtual ~Base3"<<endl;} }; class Driver:public Base,public Base3,public Base2 { public:/* Base1://void** vptr1; // 4 隱含虛表指針,在最前面,也參與字節對齊int a; // 4char b[3]; // 1 * 3*//*Base3://void** vptr3; // 4*//*Base2://void** vptr2; // 4*/double d; // 8char c; // 1virtual void print(){cout<<"Driver"<<endl;}virtual ~Driver(){cout<<"virtual ~Driver"<<endl;} }; cout<<sizeof(Driver)<<endl; // 8 + 8 + 8 + 8 + 8 = 40

需要明確一點:基類指針賦值為子類對象時,是強制類型轉換(子類獨有的不能被直接訪問和調用了,但可以通過虛函數表間接訪問,所以這也存在安全問題~)

Driver d; Driver * dptr = &d; dptr->deriver1_fun1(); // ok Base1 * b1ptr = &d; b1ptr->deriver1_fun1(); // 編譯不過 Base2 *b2ptr = &d; b2ptr->deriver1_fun1(); // 編譯不過

補充

1. 關于數據類型所占內存的說明

我們熟悉的是1byte = 8bit.
但是這不是絕對的,這只是通常情況下1byte = 8 bit.
因為在美國,基本字符集通常位ASCII和EBCDIC字符集,他們都可以使用8位來容納,所以在這兩種字符集的系統中,C++中的 1byte = 8 bit.但是在國際上,可能需要使用更大的字符集。如Unicode,所以有些實現1byte = 16bit 甚至 1byte = 32bit.

我們目前只需要記住通常情況,但是對于其他情況需要有一個了解。

數據類型所占內存隨實現而異,這可能在將C++從一種環境遷移到另一種環境時引發問題。(包括在同一個系統中使用不同的編譯器

C++中的 short, int, long, long long通常使用不同數目的位來存儲,C++提供了一種靈活的標準,它確保了最小長度,如下所示:

  • short >= 16 bit (2 byte) : short至少16位
  • sizeof(int) >= sizeof(short) :int 至少與short一樣長
  • long >= 32 bit (4 byte):long 至少32位
  • sizeof(long) >= sizeof(int): long至少與int一樣長
  • long long >= 64bit (8 byte):long long 至少64位
  • sizeof(long long) >= sizeof(long): long long 至少與long 一樣長

上述標準有何用途?

我們知道 int 至少與short一樣長,short至少16bit,也就是說,極端情況下:int可以是16bit,可以表示的最大整數是
222161616 ?1=65536?1=65535-1 = 65536-1=65535?1=65536?1=65535
對于需要跨平臺的項目中,即使目前平臺上 int 是32bit ,也應該使用具有更多bit的數據類型(比如long ,long long)。這樣即使將項目移植到 int為16bit 的平臺上,項目運行時也不會因為數據溢出而出錯。

  • 為了確定某種類型所在環境所占內存情況,可以函數sizeof來確定。
  • 每種類型可表示的數據范圍等相關信息定義在頭文件<climits>中

示例:

/** @Author: tbyouth* @Date: 2020-12-17 18:49:36* @LastEditTime: 2020-12-17 19:58:58* @LastEditors: Please set LastEditors* @Description: In User Settings Edit* @FilePath: \LeetCode\.vscode\hello\limits.cpp*/ #include <iostream> #include <climits>int main() {using namespace std;int n_int = INT_MAX;short n_short = SHRT_MAX;long n_long = LONG_MAX;long long n_llong = LONG_LONG_MAX;//sizeof:sizeof(type) or sizeof(var_name) or sizeof var_namecout<<"int is "<<sizeof(int)<<" bytes"<<endl;cout<<"short is "<<sizeof(n_short)<<" bytes"<<endl;cout<<"long is "<<sizeof n_long<<" bytes"<<endl;cout<<"long long is "<<sizeof n_llong<<" bytes"<<endl;cout<<endl;// Maximum cout<<"MAX_VALUE"<<endl;cout<<"int max value is "<<n_int<<endl;cout<<"long max value is "<<n_long<<endl;cout<<"long long max value is "<<n_llong<<endl;cout<<"char max value is"<<CHAR_MAX<<"unsigned char max value is "<<UCHAR_MAX<<endl;cout<<endl;cout<<"MIN_VALUE "<<endl;cout<<"int min value is "<<INT_MIN<<endl;cout<<"Bits per byte = "<<CHAR_BIT<<endl;system("pause");return 0; }

64位編譯器下的運行結果:

2. 指針類型所占內存

先特別說明指針類型(Type*),因為指針類型指向的是一段地址空間,所以在邏輯上

對于32位的CPU的尋址空間為222 323232,即有32bit = 4 bytes。
同理對于64位的CPU,64bit = 8 bytes,對于16位的CPU就是 16bit = 2 bytes

而實際上與 平臺(CPU+OS+Compiler)相關

我在使用的不同編譯器(Compiler)的兩個不同IDE驗證與編譯器有關:

參考:
1. 32位和64位系統區別及字節對齊
2. 結構體字節對齊,C語言結構體字節對齊詳解
3. #pragma pack()用法詳解
4. C++ primer plus 第六版

與50位技術專家面對面20年技術見證,附贈技術全景圖

總結

以上是生活随笔為你收集整理的一文了解结构体字节对齐的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。