標籤:

為什麼一個空的class的大小是1個位元組?

為何如此呢?

按照書的說法,是編譯器安插了一個1 byte 的char, 用於賦予不同X instance的地址。這點我完全不能理解。。。

按照這個說法,那麼就一共可以生成128個不同的X instance?

這不是扯淡嗎。

還是,為了allocate memory,必須存有一個毫無意義的數據?

在c++11下,這樣的現象還存在嗎?


你理解錯了,不是用那一個char來區分不同的X實例。為每一個X實例分配1位元組空間,這樣就可以讓不同的X實例地址不同了,以此區分不同的X實例。如果大小為0,那麼很多個X實例放在一起的時候他們的地址是相同的,無法分辨。

隨便反對一下大小為0就沒有地址的說法。申請大小為0的內存空間是沒有問題的,也會被分配一個地址。


你想像一下如果創建一個空類的數組,那怎麼讓每個元素有不重複的地址呢?

X* myarray = new X[N];

==============================================

@Misaka Mikoto 你可能有點誤解,分配空間大小為0的地址段沒啥問題,但是數組的所有元素指向同一個地址就有點要命了,你想一下使用iterator循環

for(X* i = myarray[0]; i != myarray[N]; ++i){
...
}

這個循環是STL當中許多通用演算法的基礎,而如果元素大小為0,那麼myarray[0]和myarray[N]地址就相等了,這個循環直接就進不去了。

在你的回答下面評論的是另一個問題,就是new int[0]這樣的語句分配到的究竟是怎樣的內存段的問題。new底層是用malloc實現的,首先從實現上來說,無論如何都要真實分配出去至少一位元組,來把這個內存占著,否則以後某一次malloc的時候就會把這個地址再分配出去一次,那就糟糕了,當你使用free(XXX)的時候,會把錯誤的內存釋放掉。

其次是new int[0],C++分配數組的時候,實際分配的內存大小通常會比這麼多元素占的空間要多一點,用來存儲分配數組的大小,因為按照規定:

delete[] myptr;

的時候,會依次對數組中所有的元素調用析構函數,那麼就必須知道這個數組中元素的數量。所以C++當中new object和new object[]是不同的,也有兩種對應的delete object和delete[] object的語法。

一種實現當中大致是這樣的,假設分配4個位元組來存儲數組中元素數量:

  1. 首先調用malloc,指定大小是sizeof(X) * N + 4。假設返回了0x00aa0000
  2. 在返回地址的開頭4個位元組填入數組元素個數,也就是0x00aa0000 - 0x00aa0003當中,填入N
  3. 將malloc返回的地址+4個位元組偏移,然後返回,也就是返回0x00aa0004

這樣即便是new int[0],也會額外分配一些位元組來存儲元素數量(也就是0)。

實際的分配演算法其實要更複雜一些,要處理一些跟對齊有關的問題。

=================================================

試了一下x64版本的gcc,gcc只有在這個類有析構函數的時候,才會額外分配8個位元組存儲數組長度,否則就不存儲了。


class Empty{};

int main()
{
Empty arr[20];
std::vector& vec(20);
}

如果空類是0,你覺得這個數組和vector會怎樣

順便說C標準不允許struct沒有成員


#define array_size(x) (sizeof(x)/sizeof((x)[0]))

很多地方都有這個代碼,如果空類型結構體為0,必然會導致很多代碼出錯。


題目的描述不是很嚴謹,就好像計算機對於除數為 0 不能給確定結果一樣,就像對於資料庫中的 NULL 也是不確定和未知一樣,這個問題也因為有些無意義而無從爭論。此外,我想解釋的是(主觀臆測,因此編譯器開發者更有話語權),沒有任何信息需要存儲的對象實例,實現時會進行有實際內存分配,給予佔位意義的內存空間,這種做法的考慮。

sizeof (X) = 1 也許只是暗示編譯器行為,對象實例存在內存分配,對象實例會有明確地址。但是其對象所在內存空間的數據(就是這 1 byte)無實際意義(除了用於佔位)。

當然,其他朋友的評論中提到的是另一個問題,就是動態內存分配對於特殊參數(malloc(0))的考慮。這種情況下,返回的地址是無法訪問的(至少在邏輯上是不可以的)。然而 sizeof (X) = 1 的情況不是,因為它相當於 malloc (1) ,其內存是絕對可以訪問的。

--

class / struct 的實例可以作為函數參數,從調用方傳到函數,拷貝構造。

例如 STL 中的 functor,例如 find_if 的參數。

如果 class / struct 沒有任何數據成員,也沒有虛函數,那它實際上不需要分配存儲空間。但是在這個情況下,實例作為參數時,依然會在棧上佔據一個基本粒度(例如 4 bytes)。當然,這塊內存的數據實際上並沒有人去動它。(當然,這裡我說的是有具體所指,也就是 VC 編譯器。)

至於為什麼,也許是為了實現時形式上的統一吧。因為構造對象實例時,需要調用構造函數,構造函數需要對象實例的被分配內存的起始地址。

如果對於這樣的對象實例,若不給它分配一個實際存在的地址,那麼在這裡就會需要特殊處理,也就是形式上就會不統一。

換句話說,對於

class X { void foo() { }; };

X x;

要對 x 不分配內存,特殊對待嗎(還是讓他實際在內存中存在呢)?

我想還是沒必要的。因為這樣只會讓你自己陷入更多麻煩吧。


(為什麼佔1位元組很多人說了,不再贅述)佔1位元組就1位元組嘛,有什麼關係。捨不得多佔用內存?利用空基類優化,很多地方都可以讓空類不佔用額外空間。

template&::value,
bool = std::is_empty&::value&>
class Pair: Left {
Right r;
public:
Left left() { return *this; }
Right right() { return this-&>r; }
};

template&
class Pair&: Right {
Left l;
public:
Left left() { return this-&>l; }
Right right() { return *this; }
};

template&
class Pair& {
Left l;
Right r;
public:
Left left() { return this-&>l; }
Right right() { return this-&>r; }
};

可以測試一下編譯器有沒有做空基類優化。

struct Empty {};
auto empty_lambda = [](int x) {return x; };
static_assert(sizeof(Pair&) == sizeof(int), "no empty base optimization");
static_assert(sizeof(Pair&) == sizeof(int), "no empty base optimization");


實際只是用了1個位元組大小的空間而已,並沒有規定裡面放的是啥,為什麼這麼做呢,如果空 Class 的大小是0,那麼就是沒有為它分配任何內存,於是

X x;
ptrdiff_t ptr = x; // 這個 ptr 的值是多少呢?

==================== update 2016-07-04 16:15:44 =============

經過測試 new int[0] 和 malloc(0) 都可以分配到地址,每次的地址也都不相同,所以這個問題我想我還是沒搞明白

========== update 2016-07-04 16:33:55============

@靈劍 你說的我測試了一下,new int[0]的長度的確是0,前面的數據只是寫入溢出檢測數據而已,看起來並不是放置的大小


0也不會有任何問題 swift空結構體大小就是0

1可能是歷史遺留的 因為考慮兼容性 所以也不會改了


使用數據的過程里,空struct佔不佔位元組是無所謂的,因為本來就沒有數據。

問題出在指針運算里。C/C++的指針除了單純的指向,還兼任了數組管理工具,而sizeof()==0以後我們就沒辦法判斷一個指針指向數組裡的哪個元素了,這會破壞很多代碼。


推薦閱讀:

C++非同步回調如何更優雅?
[C++] 能否設計一個一般的計時函數?
unity項目越大編譯速度越慢 ue4用藍圖秒編譯 背後分別的原因是什麼?
c++的強制類型轉換?
對於 計算機圖形/界面/可視化/遊戲畫面/遊戲引擎 的疑惑?

TAG:C | CC |