C 語言變長陣列 struct 中 char data[0] 的用法
1、結構體記憶體佈局(padding)
為了讓CPU能夠更舒服地訪問到變數,struct中的各成員變數的儲存地址有一套對齊的機制。這個機制概括起來有兩點:第一,每個成員變數的首地址,必須是它的型別的對齊值的整數倍,如果不滿足,它與前一個成員變數之間要填充(padding)一些無意義的位元組來滿足;第二,整個struct的大小,必須是該struct中所有成員的型別中對齊值最大者的整數倍,如果不滿足,在最後一個成員後面填充。
The following typical alignments are valid for compilers from Microsoft, Borland,
and
- A char (one byte) will be 1-byte aligned.
- A short (two bytes) will be 2-byte aligned.
- An int (four bytes) will be 4-byte aligned.
- A float (four bytes) will be 4-byte aligned.
- A double (eight bytes) will be 8-byte aligned on Windows and 4-byte aligned on Linux.
- A long double
- Any pointer (four bytes) will be 4-byte aligned on Linux. (eg: char*, int*)
The only notable difference in alignment for a 64-bit linux system when compared to a 32 bit is:
- A double (eight bytes) will be 8-byte aligned.
- A long double (Sixteen bytes) will be 16-byte aligned.
- Any pointer (eight bytes) will be 8-byte aligned.
案例一:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct s1
{
char ch,*ptr;
union
{
short a,b;
unsigned int c:2,d:1;
};
struct s1 *next;
};
int main()
{
printf("%d\n",sizeof(struct s1));
return 0;
}
struct s1
{
char ch,*ptr; //ch和*ptr各佔4bit,共8bit
union //按照最長的計算,union佔4bit
{
short a,b;
unsigned int c:2,d:1;
}
struct s1 *next; //4bit
}
案例二:
struct s2{
char ch;
char *ptr;
union {
short a,b;
unsigned int c:2,d:1;
};
int x;
double y;
struct s2 *next;
};
struct s2{
char ch;
char *ptr; //ch和*ptr各佔4bit,共8bit
union { //按照最長的計算,union佔4bit
short a,b;
unsigned int c:2,d:1;
};
int x; //int佔4bit
double y;//double佔8bit
struct s2 *next;//padding 到8bit
};//32 bit
案例三:
struct s3{
char ch;
char *ptr;
union {
short a,b;
unsigned int c:2,d:1;
};
int x;
double y;
struct s2 p;
};
struct s3{
char ch;
char *ptr; //ch和*ptr各佔4bit,共8bit
union { //按照最長的計算,union佔4bit
short a,b;
unsigned int c:2,d:1;
};
int x; //int佔4bit
double y;//double佔8bit
struct s2 p;//32
};//24+32=56 bit
變長陣列
摘要:在實際的程式設計中,我們經常需要使用變長陣列,但是C語言並不支援變長的陣列。此時,我們可以使用結構體的方法實現C語言變長陣列。
struct MyData
{
int nLen;
char data[0];
};
在結構中,data是一個數組名;但該陣列沒有元素;該陣列的真實地址緊隨結構體MyData之後,而這個地址就是結構體後面資料的地址(如果給這個結構體分配的內容大於這個結構體實際大小,後面多餘的部分就是這個data的內容);這種宣告方法可以巧妙的實現C語言裡的陣列擴充套件。
實際用時採取這樣:
struct MyData *p = (struct MyData *)malloc(sizeof(struct MyData )+strlen(str))
這樣就可以通過p->data 來操作這個str。
這樣就可以通過p->data 來操作這個str。
程式例項:
struct MyData
{
int nLen;
char data[0];
};
int main()
{
int nLen = 10;
char str[10] = "123456789";
cout << "Size of MyData: " <<sizeof(MyData) << endl;
MyData *myData = (MyData*)malloc(sizeof(MyData) +10);
memcpy(myData->data, str, 10);
cout << "myData's Data is: " << myData->data << endl;
free(myData);
return 0;
}
輸出:
Size of MyData:
4
myData"s Data is: 123456789
我想舉一個自己最近在專案中犯的錯誤來說明要踏踏實實做人,不要做裝B青年
在程式碼中,我需要在一個library和一個daemon之間通過socket傳送資料包,包的格式定義如下(為了簡化,我就用最簡單的資料型別舉例):
-
typedef struct {
-
int head;
-
int size; //指明整個包的長度
-
char reply;
-
char data[0];
-
} packet;
-
packet* cmd = malloc (sizeof(packet) + 20);
- memcpy (packet->data, some_data, 20);
daemon將上面分配的cmd包傳送給library,library接收到包後,需要將data欄位中的資料取出來。size指明瞭整個包的長度,但沒有欄位指明資料的長度。我需要這麼一個指明資料長度的欄位嗎?作為一個裝B青年,我認為當然不需要,於是我這樣來計算資料的長度:
-
#define offsetof(type, element) ((int)&((type *)0)->element)
-
static inline size_t packet_data_len(packet* cmd) {
-
assert(cmd);
-
return cmd->size - offsetof(packet, data);
-
}
- memcpy (buffer_to_receive_data, cmd->data, packet_data_len (cmd));
於是乎,這段程式成功的給我帶來了無數的bug,莫名奇妙的segfault,奇怪的資料錯誤,還是有部分時間的正常工作。當然,最終我還是找到了問題:
sizeof (packet) == 12;
這是合理的,char reply被padding成了4個位元組,而char data[0]位元組為0。
但,offsetof(packet, data) == 9,在計算偏移時,char reply為一個位元組,沒有padding。
所以packet_data_len每次都會返回比真實的資料多3個位元組 ……
最後我還是老老實實加了個data_len欄位指明資料的長度。