C語言struct關鍵字

來源:互聯網
上載者:User
C語言struct關鍵字

struct 是個神奇的關鍵字,它將一些相關聯的資料打包成一個整體,方便使用。

在網路通訊協定、通訊控制、嵌入式系統、驅動開發等地方,我們經常要傳送的不是簡單的位元組流(char 型數組),而是多種資料群組合起來的一個整體,其表現形式是一個結構體。

經驗不足的開發人員往往將所有需要傳送的內容依順序儲存在char 型數組中,通過指標位移的方法傳送網路報文等資訊。這樣做編程複雜,易出錯,而且一旦控制方式及通訊協定有所變化,程式就要進行非常細緻的修改,非常容易出錯。這個時候只需要一個結構體就能搞定。平時我們要求函數的參數盡量不多於4 個,如果函數的參數多於4 個使用起來非常容易出錯(包括每個參數的意義和順序都容易弄錯),效率也會降低(與具體CPU 有關,ARM晶片對於超過4 個參數的處理就有講究,具體請參考相關資料)。這個時候,可以用結構體壓縮參數個數。

一、空結構體多大?

結構體所佔的記憶體大小是其成員所佔記憶體之和(關於結構體的記憶體對齊,請參考預先處理那章)。這點很容易理解,但是下面的這種情況呢?
struct student

{

}stu;

sizeof(stu)的值是多少呢?在Visual C++ 6.0 上測試一下。

很遺憾,
不是0,而是1。為什麼呢?你想想,如果我們把struct student 看成一個模子的話,你能造出一個沒有任何容積的模子嗎?

顯然不行。編譯器也是如此認為。編譯器認為任何一種資料類型都有其大小,用它來定義一個變數能夠分配確定大小的空間。既然如此,編譯器就理所當然的認為任何一個結構體都是有大小的,哪怕這個結構體為空白。那萬一結構體真的為空白,它的大小為什麼值比較合適呢?

假設結構體內只有一個char 型的資料成員,那其大小為1byte(這裡先不考慮記憶體對齊的情況).也就是說非空結構體類型資料最少需要佔一個位元組的空間,而空結構體類型資料總不能比最小的非空結構體類型資料所佔的空間大吧。這就麻煩了,空結構體的大小既不能為0,也不能大於1,怎麼辦?定義為0.5個byte?但是記憶體位址的最小單位是1 個byte,0.5 個byte 怎麼處理?解決這個問題的最好辦法就是折中,編譯器理所當然的認為你構造一個結構體資料類型是用來打包一些資料成員的,而最小的資料成員需要1 個byte,編譯器為每個結構體類型資料至少預留1 個byte的空間。所以,空結構體的大小就定位1 個byte。

優秀的程式設計者這樣設計傳送的報文,是一個很不錯的使用執行個體,可以借鑒使用:
struct CommuPacket
{
int iPacketType; //報文類型標誌
union //每次傳送的是三種報文中的一種,使用union
{
struct structA packetA;
struct structB packetB;
struct structC packetC;
}
};
在進行報文傳送時,直接傳送struct CommuPacket一個整體。
假設發送函數的原形如下:
//pSendData:發送位元組流的首地址,iLen:要發送的長度
Send(char * pSendData, unsigned int iLen);
發送方可以直接進行如下調用發送struct CommuPacket的一個執行個體sendCommuPacket:
Send( (char *)&sendCommuPacket , sizeof(CommuPacket) );

假設接收函數的原形如下:
//pRecvData:發送位元組流的首地址,iLen:要接收的長度
//傳回值:實際接收到的位元組數
unsigned int Recv(char * pRecvData, unsigned int iLen);
接收方可以直接進行如下調用將接收到的資料儲存在struct CommuPacket的一個執行個體recvCommuPacket中:

Recv( (char *)&recvCommuPacket , sizeof(CommuPacket) );
接著判斷報文類型進行相應處理:
switch(recvCommuPacket. iPacketType)
{
case PACKET_A:
… //A類報文處理
break;
case PACKET_B:
… //B類報文處理
break;
case PACKET_C:
… //C類報文處理
break;
}
以上程式中最值得注意的是
Send( (char *)&sendCommuPacket , sizeof(CommuPacket) );
Recv( (char *)&recvCommuPacket , sizeof(CommuPacket) );
中的強制類型轉換:(char *)&sendCommuPacket、(char *)&recvCommuPacket,先取地址,再轉化為char型指標,這樣就可以直接利用處理位元組流的函數。
利用這種強制類型轉化,我們還可以方便程式的編寫,例如要對sendCommuPacket所處記憶體初始化為0,可以這樣調用標準庫函數memset():
memset((char *)&sendCommuPacket,0, sizeof(CommuPacket));

二、柔性數組

也許你從來沒有聽說過柔性數組(flexible array)這個概念,但是它確實是存在的。

C99 中,結構中的最後一個元素允許是未知大小的數組,這就叫做柔性數群組成員,但結構中的柔性數群組成員前面必須至少一個其他成員。柔性數群組成員允許結構中包含一個大小可變的數組。sizeof 返回的這種結構大小不包括柔性數組的記憶體。包含柔性數群組成員的結構用malloc ()函數進行記憶體的動態分配,並且分配的記憶體應該大於結構的大小,以適應柔性數組的預期大小。

柔性數組到底如何使用呢?看下面例子:

typedef struct st_type

{


int i;


int a[0];

}type_a;

有些編譯器會報錯無法編譯可以改成:

typedef struct st_type

{


int i;


int a[];

}type_a;

這樣我們就可以定義一個可變長的結構體, 用sizeof(type_a) 得到的只有4 , 就是sizeof(i)=sizeof(int)。那個0 個元素的數組沒有佔用空間,而後我們可以進行變長操作了。通過如下運算式給結構體分配記憶體:
type_a *p = (type_a*)malloc(sizeof(type_a)+100*sizeof(int));


這樣我們為結構體指標p 分配了一塊記憶體。用p->item[n]就能簡單地訪問可變長元素。

但是這時候我們再用sizeof(*p)測試結構體的大小,發現仍然為4。是不是很詭異?我們不是給這個數組分配了空間嗎?
別急,先回憶一下我們前面講過的“模子”。在定義這個結構體的時候,模子的大小就已經確定不包含柔性數組的記憶體大小。柔性數組只是編外人員,不佔結構體的編製。只是說在使用柔性數組時需要把它當作結構體的一個成員,僅此而已。再說白點,柔性數組其實與結構體沒什麼關係,只是“掛羊頭賣狗肉”而已,算不得結構體的正式成員。

需要說明的是:C89 不支援這種東西,C99 把它作為一種特例加入了標準。但是,C99所支援的是incomplete type,而不是zero array,形同int item[0];這種形式是非法的,C99 支援的形式是形同int item[];只不過有些編譯器把int item[0];作為非標準擴充來支援,而且在C99 發布之前已經有了這種非標準擴充了,C99 發布之後,有些編譯器把兩者合而為一了。

當然,上面既然用malloc 函數分配了記憶體,肯定就需要用free 函數來釋放記憶體:
free(p);


經過上面的講解,相信你已經掌握了這個看起來似乎很神秘的東西。不過實在要是沒掌握也無所謂,這個東西實在很少用。

三、struct 與class 的區別

在C++裡struct 關鍵字與class 關鍵字一般可以通用,只有一個很小的區別。struct 的成員預設情況下屬性是public 的,而class 成員卻是private 的。很多人覺得不好記,其實很容易。你平時用結構體時用public 修飾它的成員了嗎?既然struct 關鍵字與class 關鍵字可以通用,你也不要認為結構體內不能放函數了。許多文獻寫到這裡就認為已經給出了C++中struct和class的全部區別,實則不然,另外一點需要注意的是:
C++中的struct保持了對C中struct的全面相容(這符合C++的初衷——“a better c”),因而,下面的操作是合法的:
struct structA
{
char a;
char b;
int c;
};
structA a = {'a' , 'a' ,1}; // 定義時直接賦初值
即struct可以在定義的時候直接以{ }對其成員變數賦初值,而class則不能,在經典書目《thinking C++ 2nd edition》中作者對此點進行了強調。

四、位元組序

說到struct,就不得不說說位元組序的事了,先看個執行個體吧,看看怎麼樣?

 

#include <iostream.h>#pragma pack(8)struct example1{short a;long b;};struct example2{char c;example1 struct1;short e;    };#pragma pack()int main(int argc, char* argv[]){example2 struct2;cout << sizeof(example1) << endl;cout << sizeof(example2) << endl;cout << (unsigned int)(&struct2.struct1) - (unsigned int)(&struct2) << endl;return 0;}

輸出結果是8,16,4,怎麼樣,你答對了麼,下面就著手來學習位元組序的內容

 

1、自然對界
struct是一種複合資料型別,其構成元素既可以是基礎資料型別 (Elementary Data Type)(如int、long、float等)的變數,也可以是一些複合資料型別(如array、struct、union等)的資料單元。對於結構體,編譯器會自動進行成員變數的對齊,以提高運算效率。預設情況下,編譯器為結構體的每個成員按其自然對界(natural alignment)條件分配空間。各個成員按照它們被聲明的順序在記憶體中順序儲存,第一個成員的地址和整個結構的地址相同。自然對界(natural alignment)即預設對齊,是指按結構體的成員中size最大的成員對齊。
例如:
struct naturalalign
{
char a;
short b;
char c;
};
在上述結構體中,size最大的是short,其長度為2位元組,因而結構體中的char成員a、c都以2為單位對齊,sizeof(naturalalign)的結果等於6;
如果改為:
struct naturalalign
{
char a;
int b;
char c;
};
size最大的是int,其長度為4位元組,因而結構體中的char成員a、c都以4為單位對齊,sizeof(naturalalign)的結果等於12;

2、指定對界
一般地,可以通過下面的方法來改變預設的對界條件:
1)使用偽指令#pragma pack (n),編譯器將按照n個位元組對齊;
2)使用偽指令#pragma pack (),取消自訂位元組對齊。

注意:如果#pragma pack (n)中指定的n大於結構體中最大成員的size,則其不起作用,結構體仍然按照size最大的成員進行對界。
例如:
#pragma pack (n)
struct naturalalign
{
char a;
int b;
char c;
};
#pragma pack ()
當n為4、8、16時,其對齊均一樣,sizeof(naturalalign)的結果都等於12。而當n為2時,其發揮了作用,使得sizeof(naturalalign)的結果為8。
在VC++ 6.0編譯器中,我們可以指定其對界方式,其操作方式為依次選擇projetct > setting > C/C++菜單,在struct member alignment中指定你要的對界方式。
另外,通過__attribute((aligned (n)))也可以讓所作用的結構體成員對齊在n位元組邊界上,但是它較少被使用,因而不作詳細講解。

到這裡上面剛剛說的執行個體是不是也該迎刃而解了,還是分析一下吧,現在會了不代表一直會了,好記性不讓爛筆頭,不對,是指頭(碼字不容易啊)O(∩_∩)O~

程式中第2行#pragma pack (8)雖然指定了對界為8,但是由於struct example1中的成員最大size為4(long變數size為4),故struct example1仍然按4位元組對界,struct example1的size為8
struct example2中包含了struct example1,其本身包含的簡單資料成員的最大size為2(short變數e),但是因為其包含了struct example1,而struct example1中的最大成員size為4,struct example2也應以4對界,#pragma pack (8)中指定的對界對struct example2也不起作用,故輸出結果為16;
由於struct example2中的成員以4為單位對界,故其char變數c後應補充3個空,其後才是成員struct1的記憶體空間,20行的輸出結果為4。

到這裡再看一個小程式,結束這一小節

 

#include <iostream.h>struct structA{int iMember;char *cMember;};int main(int argc, char* argv[]){structA instant1,instant2;char c = 'a';instant1.iMember = 1;instant1.cMember = &c; instant2 = instant1;cout << *(instant1.cMember) << endl;*(instant2.cMember) = 'b';cout << *(instant1.cMember) << endl;return 0;}

輸出結果:a   b
tell me why,原因在於instant2 = instant1指派陳述式採用的是變數逐個拷貝,這使得instant1和instant2中的cMember指向了同一片記憶體,因而對instant2的修改也是對instant1的修改。

在C語言中,當結構體中存在指標型成員時,一定要注意在採用指派陳述式時是否將2個執行個體中的指標型成員指向了同一片記憶體。

 

在C++語言中,當結構體中存在指標型成員時,我們需要重寫struct的拷貝建構函式並進行“=”操作符重載。

待續。。。。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.