程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> C語言柔性數組

C語言柔性數組

編輯:C++入門知識

  結構中最後一個元素允許是未知大小的數組,這個數組就是柔性數組。但結構中的柔性數組前面必須至少一個其他成員,柔性數組成員允許結構中包含一個大小可變的數組,sizeof返回的這種結構大小不包括柔性數組的內存。包含柔數組成員的結構用malloc函數進行內存的動態分配,且分配的內存應該大於結構的大小以適應柔性數組的預期大小。柔性數組到底如何使用?

本文地址:http://www.cnblogs.com/archimedes/p/c-flexible-array.html,轉載請注明源地址。

不完整類型

C和C++對於不完整類型的定義是一樣的,不完整類型是這樣一種類型,它缺乏足夠的信息例如長度去描述一個完整的對象。

不完整類型舉例:
前向聲明就是一種常用的不完整類型

struct test; //test 只給出了聲明,沒有給出定義

不完整數據類型必須通過某種方式補充完整,才能使它們進行實例化。否則只能用於定義指針或引用,因為此時實例化的是指針或引用本身,不是base和test對象

一個未知長度的數組也屬於不完整類型:

extern int a[];

extern 關鍵字不能去掉,因為數組的長度未知,不能作為定義出現。不完整類型的數組需要補充完整才能使用。不完整類型的數組可以通過幾種方式補充完整,大括號形式的初始化就是其中的一種方式:

int a[] = { 10,20 };

結構體

首先,我們需要知道——所謂變量,其實是內存地址的一個抽像名字罷了。在靜態編譯的程序中,所有的變量名都會在編譯時被轉成內存地址。機器是不知道我們取的名字的,只知道地址。

所以有了——棧內存區,堆內存區,靜態內存區,常量內存區,我們代碼中的所有變量都會被編譯器預先放到這些內存區中。

有了上面這個基礎,我們來看一下結構體中的成員的地址是什麼?我們先簡單化一下代碼:

struct test{
    int i;
    char *p;
};

上面代碼中,test結構中i和p指針,在C的編譯器中保存的是相對地址——也就是說,他們的地址是相對於struct test的實例的。如果我們有這樣的代碼:

struct test t;

下面做個實驗:

#include<stdio.h>
struct test{
    int i;
    char *p;
};
int main(void)
{
    struct test t;
    printf("%p\n", &t);
    printf("%p\n", &(t.i));
    printf("%p\n", &(t.p));
    return 0;
}

運行結果:

我們可以看到,t.i的地址和t的地址是一樣的,t.p的址址相對於t的地址多了個8。說白了,t.i 其實就是(&t + 0×0)t.p 的其實就是 (&t + 0×8)。0×0和0×8這個偏移地址就是成員i和p在編譯時就被編譯器給hard code了的地址。於是,你就知道,不管結構體的實例是什麼——訪問其成員其實就是加成員的偏移量

下面再來做個實驗:

#include<stdio.h>
struct test{
    int i;
    short c;
    char *p;
};
int main(void)
{
    struct test *pt=NULL;
    printf("%p\n", &(pt->i));
    printf("%p\n", &(pt->c));
    printf("%p\n", &(pt->p));
    return 0;
}

運行結果:

 注意:上面的pt->p的偏移之所以是0×8而不是0×6,是因為內存對齊了(我在64位系統上)。關於內存對齊,可參看《C語言內存對齊詳解》一文。

柔性數組

  柔性數組成員(flexible array member)也叫伸縮性數組成員,這種代碼結構產生於對動態結構體的需求。在日常的編程中,有時候需要在結構體中存放一個長度動態的字符串,一般的做法,是在結構體中定義一個指針成員,這個指針成員指向該字符串所在的動態內存空間,例如:

struct s_test
{
    int a;
    double b;
    char* p;
};

p指向字符串,這種方法造成字符串與結構體是分離的,不利於操作。把字符串和結構體連在一起的話,效果會更好,可以修改如下:

char a[] = "Hello world";
struct s_test *ptest = (struct s_test*)malloc(sizeof(s_test)+streln(a)+1);
strcpy(ptest+1,a);

這樣一來,(char*)(ptestt + 1)就是字符串“hello world”的地址。這時候p成了多余的東西,可以去掉。但是,又產生了另外一個問題:老是使用(char*)(ptestt + 1)不方便。如果能夠找出一種方法,既能直接引用該字符串,又不占用結構體的空間,就完美了,符合這種條件的代碼結構應該是一個非對象的符號地址,在結構體的尾部放置一個0長度的數組是一個絕妙的解決方案。不過,C/C++標准規定不能定義長度為0的數組,因此,有些編譯器就把0長度的數組成員作為自己的非標准擴展,例如:

struct s_test2
{
    int a;
    double b;
    char c[0];
};

c就叫柔性數組成員,如果把ptest指向的動態分配內存看作一個整體,c就是一個長度可以動態變化的結構體成員,柔性一詞來源於此。c的長度為0,因此它不占用test的空間,同時ptest->c就是“hello world”的首地址,不需要再使用(char*)(ptestt + 1)這麼丑陋的語法了。

鑒於這種代碼結構所產生的重要作用,C99甚至把它收入了標准中:

As a special case, the last element of a structure with more than one named member may have an incomplete array type; this is called a flexible array member.
C99使用不完整類型實現柔性數組成員,標准形式是這樣的:

struct s_test
{
  int a;
  double b;
  char c[];
};

c同樣不占用test的空間,只作為一個符號地址存在,而且必須是結構體的最後一個成員。柔性數組成員不僅可以用於字符數組,還可以是元素為其它類型的數組,例如:

struct s_test
{
    int a;
    double b;
    float[];
};

首先,我們要知道,0長度的數組在ISO C和C++的規格說明書中是不允許的。這也就是為什麼在VC++2012下編譯你會得到一個警告:“arning C4200: 使用了非標准擴展 : 結構/聯合中的零大小數組”。

那麼為什麼gcc可以通過而連一個警告都沒有?那是因為gcc 為了預先支持C99的這種玩法,所以,讓“零長度數組”這種玩法合法了。關於GCC對於這個事的文檔在這裡:“Arrays of Length Zero”,文檔中給了一個例子,完整代碼如下:

#include <stdlib.h>
#include <string.h>
struct line {
   int length;
   char contents[0]; // C99的玩法是:char contents[]; 沒有指定數組長度
};
int main(){
    int this_length=10;
    struct line *thisline = (struct line *)
                     malloc (sizeof (struct line) + this_length);
    thisline->length = this_length;
    memset(thisline->contents, 'a', this_length);
    return 0;
}

上面這段代碼的意思是:我想分配一個不定長的數組,於是我有一個結構體,其中有兩個成員,一個是length,代表數組的長度,一個是contents,代碼數組的內容。後面代碼裡的 this_length(長度是10)代表是想分配的數據的長度。

參考資料

酷殼--C語言結構體裡的成員數組和指針

GCC文檔--Arrays of Length Zero

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved