2011-01-07 60 views
13

在C++中,大部分優化是從as-if規則派生的。也就是說,只要程序的行爲如果沒有發生優化,那麼它們就是有效的。空數據會員優化:有可能嗎?

的空基地優化是一個這樣的特技:在某些條件下,如果基類爲空(沒有任何非靜態數據成員),則編譯器可其的Elid內存中的表示。

顯然,這似乎是標準禁止在數據成員這種優化,也就是即使一個數據成員是空的,但它仍然必須採取的地方至少有一個字節值:從n3225,[類]

4 - 類類型的完整對象和成員子對象的大小不爲零。

注:這導致使用私有繼承的政策設計,以有EBO踢在適當的時候

我在想,如果使用AS-if規則,一個可能仍然是能夠執行此優化。


編輯:以下一些答案和意見,並作出更清楚什麼,我想知道。

首先,讓我舉一個例子:

struct Empty {}; 

struct Foo { Empty e; int i; }; 

我的問題是,爲什麼sizeof(Foo) != sizeof(int)?特別是,除非你指定了一些包裝,否則可能是由於對齊問題,Foo將會是int的兩倍,這似乎是可笑的膨脹。

注:我的問題不是爲什麼sizeof(Foo) != 0,這實際上並非EBO要麼

需要根據C++,這是因爲沒有子對象可以具有零大小。然而鹼被授權具有零大小(EBO)因此:

struct Bar: Empty { int i; }; 

可能(由於EBO)服從sizeof(Bar) == sizeof(int)

Steve Jessop似乎認爲這是因爲沒有兩個子對象會有相同的地址。我想過這個問題,但它實際上並沒有阻止在大多數情況下,優化:

如果你有「未使用」的內存,那麼它很簡單:

struct UnusedPadding { Empty e; Empty f; double d; int i; }; 
// chances are that the layout will leave some memory after int 

但事實上,它甚至「雪上加霜「比那,因爲Empty空間永遠不會被寫入(你最好不要,如果EBO踢... ...),因此你實際上可以將其放置在一個被佔領的地方,是不是另一個對象的地址:

struct Virtual { virtual ~Virtual() {} Empty e; Empty f; int i; }; 
// most compilers will reserve some space for a virtual pointer! 

或者,即使在我們最初的情況:如果我們所有

struct Foo { Empty e; int i; }; // deja vu! 

人能(char*)foo.e == (char*)foo.i + 1想要的是不同的地址。

+1

查看Boost的[Compressed Pair](http://www.boost.org/doc/libs/1_45_0/libs/utility/compressed_pa​​ir.htm)庫,瞭解如何獲得此優化。 – GManNickG 2011-01-07 09:58:03

+0

@GMan:他們巧妙地使用EBO。但實際上,這種EBO的使用正是促使我的問題開始的原因。 – 2011-01-07 10:07:58

+0

請參閱:[什麼時候程序員使用空基優化(EBO)](http://stackoverflow.com/questions/4325144/scenario-when-do-programmers-use-empty-base-optimization-ebo) – Nawaz 2011-01-07 10:33:57

回答

6

下AS-if規則:

struct A { 
    EmptyThing x; 
    int y; 
}; 

A a; 
assert((void*)&(a.x) != (void*)&(a.y)); 

斷言不能被觸發。所以我沒有看到祕密製作x的大小爲0的任何好處,當你只需要在結構中添加填充。

我想在理論上,編譯器可以跟蹤指針是否會被帶到成員,並且只有在他們確實沒有時才進行優化。這將有限的用途,因爲將有兩個不同版本的結構具有不同的佈局:一個用於優化的情況,一個用於通用代碼。但是,例如,如果您在堆棧上創建了A實例,並且對它進行了完全內聯(或者對優化器可見的其他操作),那麼可以完全省略部分結構。但這不是特定於空對象的 - 空對象只是存儲器未被訪問的對象的特例,因此在某些情況下可能永遠不會被分配。

2

由於技術上的原因,C++強制要求空類應該具有非零大小。
這是爲了強制不同的對象有不同的內存地址。所以編譯器默默地將一個字節插入「空」對象。
此約束不適用於派生類的基類部分,因爲它們不是獨立的。

-1

鑑於struct Empty { };考慮如果sizeof(Empty) == 0會發生什麼情況。爲Empty對象分配堆的通用代碼很容易表現不同,例如 - realloc(p, n * sizeof(T)),其中TEmpty,則相當於free(p)。如果sizeof(Empty) != 0那麼諸如memset/memcpy等的東西會嘗試在沒有被Empty對象使用的內存區域上工作。因此,編譯器需要根據最終的值使用sizeof(Empty)這樣的東西 - 這聽起來幾乎不可能。

另外,在當前的C++規則下,確保每個成員都有一個獨特的地址,這意味着你可以使用這些地址來編碼關於這些字段的某些狀態 - 例如,文本字段名稱,是否應該訪問字段對象的某些成員函數等。如果地址突然重合,則依賴這些密鑰的任何現有代碼都可能中斷。

1

由於Empty是POD型,可以使用memcpy覆蓋它的「表示」,因此它最好不要與另一C++對象或有用的數據共享。