C/C++中數據大小、字節對齊、內存佔用

數據大小獲取
 
sizeof()操作符,求佔用空間,對數組而言大小爲 type大小 * 數據個數;
int a[] = {1,2,3,4,5}
sizeof(a) = 20
sizeof(&a) = 4
特殊的:在string時,由於字符串本質上就是自帶‘\0’結尾的 char[] 數組,而char 的大小有恰好爲1,
因此sizeof()就正好會是數組的長度;
在C++98中是不允許對類的非靜態成員變量使用sizeof()的,而在C++11中是合法的;
根據 C99 規定,sizeof是一個編譯時刻就起效果的運算符,運行時在其內的任何運算都沒有意義;
int i = 10;
sizeof(i++); // 4 此時i=10, 並不會自加,裏面的運算是無意義的;
int size = sizeof(Volume) / sizeof(Volume[0]); // Volume[0]並不起作用,只看其類型


strlen() 在對字符串計算時 以遇到'\0'結束符爲準,不包含末尾的'\0' 字符(或內存中的內容爲0)的長度的;
.length() (容器)字符串的長度,不包含結尾的'\0';
.size() (容器)字符串的長度'\0';

strcpy() 拷貝函數其實是從指定位置覆蓋,再加上‘\0’,而用%s輸出就到'\0'爲止。。。會在 最後自動 添加 '\0' 作爲結束符,,

--------------------------------------------------------------------------------------
例題1:
    char dog[]="wang\0miao";
    cout << sizeof(dog) << endl;// 10
    cout << strlen(dog) << endl;// 4 
例題2    
    char str[] = "ab\012\\n";
    printf("%d \n", sizeof(str) );//6
    printf("%d \n", strlen(str) );//5
解釋例題2 :
\012 \0表示8進制(後面跟 <8 的數據的話) 此處表8進制的的10,也即ASIIC符號 \n ,同時\0也可表示字符串結束;
\\ 表示 斜杆;
最終字符爲 a b \n \n \0
注:\ 表示轉義字符標誌;

例3
char c1[] ={'a', 'b', '\0', 'd', 'e'};
char c2[] ="hello";
sizeof(c1) strlen(c1); // 5 2
sizeof(c2) strlen(c2); // 6 5

例4
char *str1 ="hello";
char str2[] = "hello";
sizeof(str1) stelen(str1) // 4, 5(32位OS上,在64位OS上爲:8, 5)
區別:str1是變量,str2是char const 類型;
注:sizeof(string)問題
string的實現在各庫中可能有所不同,但是在同一庫中相同一點是,無論你的string裏放多長的字符串,它的sizeof()都是固定的,字符串所佔的空間是從堆中動態分配的,與sizeof()無關。其大小跟編譯器有關,VC6.0測試後sizeof(string)=16.  DEV C++中爲4。
 
 
 
字節對齊

需要字節對齊的根本原因在於CPU訪問數據的效率問題。不僅是便於cpu快速訪問,同時合理的利用字節對齊可以有效地節省存儲空間
 
#pragma pack(n) // 指定內存對齊方式,往後的的代碼什麼的不按自身寬度對齊而是按指定寬度對齊;
#pragma pack() //  取消用戶自定義字節對齊方式

#pragma pack的n值等於或超過所有數據成員長度的時候,這個n值的大小將不產生任何效果。

 __attribute((aligned (n))),讓所作用的結構成員對齊在n字節自然邊界上。
如果結構中有成員的長度大於n,則按照最大成員的長度來對齊。
 __attribute__ ((packed)),取消結構在編譯過程中的優化對齊,按照實際佔用字節數進行對齊。


GCC默認按4字節對齊,

對於標準數據類型,地址只要是它的長度的整數倍就行了,而非標準數據類型按下面的原則對齊:

數組:按照基本數據類型對齊,第一個對齊了後面的自然也就對齊了。

枚舉:只佔4個字節;

聯合體:大於等於其成員中最寬的成員,且是其他成員變量基本類型的整數倍;

結構體:結構體中數據成員都要對齊,且結構體整體也需要對齊(整體對齊爲數據成員最大基本類型的整數倍);

        類:看下文;

 
 
 
內存佔用

1、枚舉eunm
對於enum變量,指一個被命名的整型常數的集合,本質上是一組(int型)常數,只佔固定的 4 個字節;
定義格式:
    enum 枚舉名 {枚舉元素1, 枚舉元素2, ……};
注意:第一個枚舉成員的默認值爲整型的 0,後續枚舉成員的值在前一個成員上加 1。

enum season {spring, summer=3, autumn, winter};
沒有指定值的枚舉元素,其值爲前一元素加 1。也就說 spring 的值爲 0,summer 的值爲 3,
autumn 的值爲 4,winter 的值爲 5

 

2、聯合體union
所有成員相對於基地址均爲0,共享一段內存,並且同一時間只能儲存其中一個成員變量的值(即後面的賦值將覆蓋前面的賦值)空間需足夠寬(大於等於其成員中最寬的成員)且大小能被其包含的所有基本數據類型的大小所整除。
// GCC 默認 4字節對齊
union test{
    char s[9];      // 9
    int  a;         // 4
    double b;       // 8
};
sizeof( test );   //>=最寬數據成員9, 且同時是其他類型char(1)、(int)4和double(8)的整數倍, 因此爲16

union test{
    char s[8];      // 8
    int  a;         // 4
    double b;       // 8
};
printf("%d\n", sizeof( test ) ); // >=最寬數據成員8, 且同時是其他類型char(1)、
(int)4和double(8)的整數倍, 因此爲8

 

 
 
3、結構體struct
結構體各成員根據定義依次申請內存空間,其中包含數據成員自身對齊和結構體本身的對齊整體對齊爲數據成員中最大基本類型的整數倍);
struct struct_test_1{
    char a;        // 1
    int b;         // 4
    double c;      // 8
}test1;
sizeof( test1 );   // 16 

struct struct_test_2{
    char a;          // 1
    double     b;    // 8
    int c;           // 4
    static int d;    // 存放在靜態數據區,sizeof() 不計static所佔用空間
}test2;
sizeof( test2 )      // 24
對struct_test_2分析:
    首先char a 申請一個字節空間,開闢首地址,之後double b存入; 它會認爲內存是以自己的大小(double = 8 byte)來劃分,
因此元素放置在自身寬度的整數倍開始(原則一),故b不從偏移1開始,因爲不是整數倍,故中間填充7字節,此時消耗16字節,
再開闢int c,4 字節,是從4字節的整數倍開始,故消耗了20字節。此時存儲單元不是最寬元素(8bytes)的整數倍,
故按最寬元素整數倍對齊(原則二);一共消耗24 字節。

struct Data
{
    int a;          // 4
    float b;        // 4 
    double c;       // 8
    char d[21];     // 21
}data;    
printf("%d\n", sizeof(data) ); // 40 整體對齊爲  數據成員最大基本類型(double)的整數倍



有些數據在存儲時並不需要佔用一個完整的字節,只需要佔用一個或幾個二進制位即可。
例如開關只有通電和斷電兩種狀態,用 0 和 1 表示足以,也就是用一個二進位。
正是基於這種考慮,C語言又提供了一種叫做 位域 的數據結構。位域本質上是一種結構類型,
不過其成員是按二進制位分配的。並且其類型必須是int型,這就包括有符號和無符號的。

在結構體或是聯合體定義時,我們可以指定某個成員變量所佔的用的二進制(bit)位數,這就是位域。
位域的使用和結構體成員的使用相同,其一般形式爲:
變量類型 位域變量名:位域  其中(位域變量名爲可選項,)
    struct bs{
        int a:8;
        int b:2;
        int c:5;
    };
    printf("%d ", sizeof(struct bs) ); // 4
位域使用規則如下:
1、位域的寬度不能超過它所依附的數據類型的長度, 比如:unsigned int,長度爲 4 個字節,數字就不能超過 32
2、位域可以無位域名,這時它只用來作填充或調整成員位置。因爲沒有名稱,無名的位域是不能使用的。

位域存儲規則如下:
1、依然遵循struct的 對齊規則;

2、當相鄰成員的類型相同時,如果它們的位寬之和小於類型的 sizeof 大小,那麼後面的成員緊鄰前一個成員存儲,
直到不能容納爲止;如果它們的位寬之和大於類型的 sizeof 大小,那麼後面的成員將從新的存儲單元開始,其偏移量爲類型大小的整數倍。
    struct bs{
        int a:26;
        int b:2;
        int c:5;
    };
    printf("%d ", sizeof(struct bs) ); // 8
3、如果成員之間穿插着非位域成員,那麼不會進行壓縮。
  struct bs{
        int a:26;
        int b;
        int c:5;
    };
    printf("%d ", sizeof(struct bs) ); // 12
4、當相鄰成員的類型不同時,不同的編譯器有不同的實現方案,GCC 會壓縮存儲,而 VC/VS 不會。

 

 
實例1
struct A{
  int a;//4
  short b;//2+2
  int c;//4
  char d;//1+3
}; 
struct B{
  int a; // 4
  short b; // 2
  char d;// 1 + 1
  int c; // 4
};

sizeof(A) = 16, sizeof(B) = 12

實例2

struct foo{
    char a; // 1 + 3
    int b[10]; // 4 * 10
    char c;// +1
};// 48
void main_2()
{
    struct foo f={'X', {10,20,30,40,50,60,70,80,90}, 'F'};
    
    cout << "單個成員變量值" << endl;
    printf("%c %d %c \n", f.a, f.b[0], f.c);
    // cout << f.a<< " " << f.b[0]<< " " << f.c<< endl; // f.b[0] 輸出是有錯誤的
        
    cout << "相對偏移量" << endl;
    printf ("sizeof() = %d\n", sizeof(foo) );
    printf ("offsetof(struct foo,a) is %d\n",(int)offsetof(struct foo,a));//輸出 0
    printf ("offsetof(struct foo,b) is %d\n",(int)offsetof(struct foo,b));//輸出 1
    printf ("offsetof(struct foo,c) is %d\n",(int)offsetof(struct foo,c));//輸出 11
    
    cout << "相對地址" << endl;
    printf("struct          address:0x%x\n",&f);
    printf("struct member a address:0x%x\n",&(f.a) );
    printf("struct member b address:0x%x\n",&(f.b) );
    printf("struct member c address:0x%x\n",&(f.c) );
    
    cout << "\n用地址操作單個成員變量值" << endl;
    char *p = (char *)(&f);
    // 由於存在 字節對齊問題  
    printf("地址:%#x 內容:%c \n", p, *p);
    printf("地址:%#x 內容:%d \n", p+4, *(p+4) );
    printf("地址:%#x 內容:%d \n", p+8, *(p+8) );
    printf("地址:%#x 內容:%c \n", p+44, *(p+44) );
}
 
 
 
 
4、C++類需考慮內存對齊問題,和struct類似
 
a、空類佔一個字節
class A{
    
};
cout << "sizeof(A) "<< sizeof(A) << endl;// 1
 
b、數據成員所佔內存按struct的計算方式計算(訪問權限屬性不影響存儲,考慮到包含其他如struct、emun、union等複雜結構仍需先按自身類型對齊,在按class的算),其中static數據不是對象屬性,而是類屬性,其存儲在數據區,sizeof 不計算其空間;
class A{
    int a;
    double c;
    char b;
    union{
        int aa;
        char bb[9];
        double cc;
    }test;
};
cout << "sizeof(A) "<< sizeof(A) << endl;// 40 = 4 + 4 + 8 + 1 + 7 + 16 
 
c、不管是static還是非static成員函數,非虛成員函數均不佔空間
class B{
public:
    void func1(){}
    void func2(){}
};
cout << "sizeof(A) "<< sizeof(A) << endl;// 1
 
d、若存在虛函數,在類開闢的空間開始位置插入虛函數表(虛函數vptr指針),
  所有同一個類的虛函數共享一個虛函數表,故一共佔用4個字節
class B{
public:
    void func1(){}
    void func2(){}
     virtual void func3() {}
     virtual void func4() {}
};
cout << "sizeof(A) "<< sizeof(A) << endl;// 4
 
e、繼承時,在內存中,派生類所在位置在基類前面;
非虛繼情況下,不管基類存不存在虛函數,派生類會和基類均會共同使用一個虛函數表,
虛繼情況下,基類存在不存在虛函數,派生類的虛函數會共同使用派生類自己的虛函數表
class B     
{  
private:
    char ch;     
    virtual void func0()  {  }   
};
class BB: public B  
{     
public:
    int e;     
    virtual void func0()  {  }   
    virtual void func1()  {  }  
};
class BB1: virtual public B  // 虛繼承
{     
public:
    int e;     
    virtual void func0()  {  }   
    virtual void func1()  {  }  
};

      cout<< "sizeof(B)" << sizeof(B) <<endl; // 8
//    B的內存分佈
//    4字節 虛表指針
//    1字節 ch
//    3字節 補齊,因爲最大的字段(虛表長度)size爲4,所以結構體補全成4的倍數
    
    cout<< "sizeof(BB)"<< sizeof(BB) <<endl; // 12
    cout<< "sizeof(BB1)"<< sizeof(BB1) <<endl; // 16
 
 
總結1:
1、結構體或聯合體的數據成員對齊規則:
第一個數據成員放在ofset爲0的地方,以後每個數據成員的對齊按照#pragmapack指定的數值和這個數據成員自身長度中,比較小的那個進行。
2、結構體或聯合體的整體對齊規則:
在數據成員完成各自對齊之後,結構或聯合本身也要進行對齊,對齊將按照#pragmapack指定的數值和結構或聯合最大數據成員長度中,比較小的那個進行。
 
 
總結2:
1、基類對象的存儲空間 = 非static數據成員大小 + 4字節虛函數表空間(若存在虛函數);
 
2、派生類對象大小 = 基類對象大小 + 派生類獨有的非static數據成員大小(注意複雜結構的字節對齊) (普通繼承而非虛繼承,派生類會與基類共享虛函數表);
 
3、虛繼承(在繼承方式中加關鍵字virtual)的存儲空間 = 基類對象大小 + 派生類獨有的非static數據成員大小(注意複雜結構的字節對齊) + 每個類的虛函數存儲空間;
 
 
 

 
 
虛繼承
 
在多繼承時,如果一個派生類從多個基類派生,而這些基類又有一個共同的基類,則在對該基類中聲明的名字進行訪問時,可能產生二義性。或者, 如果在多條繼承路徑上有一個公共的基類,那麼在繼承路徑的某處 匯合點,這個公共基類就會在派生類的對象中產生多個基類子對象。
 要使這個公共基類在派生類中只產生一個子對象,必須對這個基類 聲明爲虛繼承,使這個基類成爲虛基類。
 
使用虛繼承之後
 
 
 
 
 
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章