Skip to content

Commit c21c7d7

Browse files
authored
Merge pull request #4142 from corob-msft/docs/corob/empty_bases
TC1652913 __declspec(empty_bases) docs
2 parents a80aad6 + 90d5cc5 commit c21c7d7

3 files changed

Lines changed: 231 additions & 8 deletions

File tree

docs/cpp/declspec.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
---
22
description: "Learn more about: `__declspec`"
33
title: "__declspec"
4-
ms.date: "03/21/2019"
4+
ms.date: 03/01/2022
55
f1_keywords: ["__declspec_cpp", "__declspec", "_declspec"]
66
helpviewer_keywords: ["__declspec keyword [C++]"]
77
---
88
# `__declspec`
99

1010
**Microsoft Specific**
1111

12-
The extended attribute syntax for specifying storage-class information uses the **`__declspec`** keyword, which specifies that an instance of a given type is to be stored with a Microsoft-specific storage-class attribute listed below. Examples of other storage-class modifiers include the **`static`** and **`extern`** keywords. However, these keywords are part of the ANSI specification of the C and C++ languages, and as such are not covered by extended attribute syntax. The extended attribute syntax simplifies and standardizes Microsoft-specific extensions to the C and C++ languages.
12+
The extended attribute syntax for specifying storage-class information uses the **`__declspec`** keyword, which specifies that an instance of a given type is to be stored with a Microsoft-specific storage-class attribute listed below. Examples of other storage-class modifiers include the **`static`** and **`extern`** keywords. However, these keywords are part of the ANSI specification of the C and C++ languages, and as such aren't covered by extended attribute syntax. The extended attribute syntax simplifies and standardizes Microsoft-specific extensions to the C and C++ languages.
1313

1414
## Grammar
1515

@@ -29,6 +29,7 @@ The extended attribute syntax for specifying storage-class information uses the
2929
 **`deprecated`**\
3030
 **`dllimport`**\
3131
 **`dllexport`**\
32+
 **`empty_bases`**\
3233
 **`jitintrinsic`**\
3334
 **`naked`**\
3435
 **`noalias`**\
@@ -48,11 +49,11 @@ The extended attribute syntax for specifying storage-class information uses the
4849

4950
White space separates the declaration modifier sequence. Examples appear in later sections.
5051

51-
Extended attribute grammar supports these Microsoft-specific storage-class attributes: [`align`](../cpp/align-cpp.md), [`allocate`](../cpp/allocate.md), [`allocator`](../cpp/allocator.md), [`appdomain`](../cpp/appdomain.md), [`code_seg`](../cpp/code-seg-declspec.md), [`deprecated`](../cpp/deprecated-cpp.md), [`dllexport`](../cpp/dllexport-dllimport.md), [`dllimport`](../cpp/dllexport-dllimport.md), [`jitintrinsic`](../cpp/jitintrinsic.md), [`naked`](../cpp/naked-cpp.md), [`noalias`](../cpp/noalias.md), [`noinline`](../cpp/noinline.md), [`noreturn`](../cpp/noreturn.md), [`nothrow`](../cpp/nothrow-cpp.md), [`novtable`](../cpp/novtable.md), [`no_sanitize_address`](../cpp/no-sanitize-address.md),[`process`](../cpp/process.md), [`restrict`](../cpp/restrict.md), [`safebuffers`](../cpp/safebuffers.md), [`selectany`](../cpp/selectany.md), [`spectre`](../cpp/spectre.md), and [`thread`](../cpp/thread.md). It also supports these COM-object attributes: [`property`](../cpp/property-cpp.md) and [`uuid`](../cpp/uuid-cpp.md).
52+
Extended attribute grammar supports these Microsoft-specific storage-class attributes: [`align`](../cpp/align-cpp.md), [`allocate`](../cpp/allocate.md), [`allocator`](../cpp/allocator.md), [`appdomain`](../cpp/appdomain.md), [`code_seg`](../cpp/code-seg-declspec.md), [`deprecated`](../cpp/deprecated-cpp.md), [`dllexport`](../cpp/dllexport-dllimport.md), [`dllimport`](../cpp/dllexport-dllimport.md), [`empty_bases`](../cpp/empty-bases.md), [`jitintrinsic`](../cpp/jitintrinsic.md), [`naked`](../cpp/naked-cpp.md), [`noalias`](../cpp/noalias.md), [`noinline`](../cpp/noinline.md), [`noreturn`](../cpp/noreturn.md), [`nothrow`](../cpp/nothrow-cpp.md), [`novtable`](../cpp/novtable.md), [`no_sanitize_address`](../cpp/no-sanitize-address.md),[`process`](../cpp/process.md), [`restrict`](../cpp/restrict.md), [`safebuffers`](../cpp/safebuffers.md), [`selectany`](../cpp/selectany.md), [`spectre`](../cpp/spectre.md), and [`thread`](../cpp/thread.md). It also supports these COM-object attributes: [`property`](../cpp/property-cpp.md) and [`uuid`](../cpp/uuid-cpp.md).
5253

53-
The **`code_seg`**, **`dllexport`**, **`dllimport`**, **`naked`**, **`noalias`**, **`nothrow`**, **`no_sanitize_address`**, **`property`**, **`restrict`**, **`selectany`**, **`thread`**, and **`uuid`** storage-class attributes are properties only of the declaration of the object or function to which they are applied. The **`thread`** attribute affects data and objects only. The **`naked`** and **`spectre`** attributes affect functions only. The **`dllimport`** and **`dllexport`** attributes affect functions, data, and objects. The **`property`**, **`selectany`**, and **`uuid`** attributes affect COM objects.
54+
The **`code_seg`**, **`dllexport`**, **`dllimport`**, **`empty_bases`**, **`naked`**, **`noalias`**, **`nothrow`**, **`no_sanitize_address`**, **`property`**, **`restrict`**, **`selectany`**, **`thread`**, and **`uuid`** storage-class attributes are properties only of the declaration of the object or function to which they're applied. The **`thread`** attribute affects data and objects only. The **`naked`** and **`spectre`** attributes affect functions only. The **`dllimport`** and **`dllexport`** attributes affect functions, data, and objects. The **`property`**, **`selectany`**, and **`uuid`** attributes affect COM objects.
5455

55-
For compatibility with previous versions, **`_declspec`** is a synonym for **`__declspec`** unless compiler option [/Za \(Disable language extensions)](../build/reference/za-ze-disable-language-extensions.md) is specified.
56+
For compatibility with previous versions, **`_declspec`** is a synonym for **`__declspec`** unless compiler option [`/Za` (Disable language extensions)](../build/reference/za-ze-disable-language-extensions.md) is specified.
5657

5758
The **`__declspec`** keywords should be placed at the beginning of a simple declaration. The compiler ignores, without warning, any **`__declspec`** keywords placed after * or & and in front of the variable identifier in a declaration.
5859

@@ -72,9 +73,9 @@ In this case, the attribute applies to `X`.
7273

7374
The general guideline for using the **`__declspec`** attribute for simple declarations is as follows:
7475

75-
*decl-specifier-seq* *init-declarator-list*;
76+
*`decl-specifier-seq`* *`init-declarator-list`* `;`
7677

77-
The *decl-specifier-seq* should contain, among other things, a base type (e.g. **`int`**, **`float`**, a **`typedef`**, or a class name), a storage class (e.g. **`static`**, **`extern`**), or the **`__declspec`** extension. The *init-declarator-list* should contain, among other things, the pointer part of declarations. For example:
78+
The *`decl-specifier-seq`* should contain, among other things, a base type (for example, **`int`**, **`float`**, a **`typedef`**, or a class name), a storage class (for example, **`static`**, **`extern`**), or the **`__declspec`** extension. The *`init-declarator-list`* should contain, among other things, the pointer part of declarations. For example:
7879

7980
```cpp
8081
__declspec(selectany) int * pi1 = 0; //Recommended, selectany & int both part of decl-specifier
@@ -94,4 +95,4 @@ __declspec( thread ) int tls_i = 1;
9495
## See also
9596

9697
[Keywords](../cpp/keywords-cpp.md)\
97-
[C Extended Storage-Class Attributes](../c-language/c-extended-storage-class-attributes.md)
98+
[C extended storage-class attributes](../c-language/c-extended-storage-class-attributes.md)

docs/cpp/empty-bases.md

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
---
2+
description: "Learn more about: empty_bases"
3+
title: "empty_bases"
4+
ms.date: 03/01/2022
5+
f1_keywords: ["empty_bases_cpp", "empty_bases"]
6+
helpviewer_keywords: ["__declspec keyword (C++), empty_bases", "empty_bases __declspec keyword"]
7+
---
8+
# `empty_bases`
9+
10+
**Microsoft Specific**
11+
12+
The C++ Standard requires that a most-derived object must have a non-zero size and must occupy one or more bytes of storage. Because the requirement only extends to most-derived objects, base class subobjects aren't subject to this constraint. The Empty Base Class Optimization (EBCO) takes advantage of this liberty. It results in reduced memory consumption, which can improve performance. The Microsoft Visual C++ compiler has historically had limited support for EBCO. In Visual Studio 2015 Update 3 and later versions, we've added a new `__declspec(empty_bases)` attribute for class types that takes full advantage of this optimization.
13+
14+
> [!IMPORTANT]
15+
> Use of `__declspec(empty_bases)` can cause an ABI-breaking change in structure and class layout where it's applied. Make sure that all client code uses the same definitions for structures and classes as your code when you make use of this storage class attribute.
16+
17+
## Syntax
18+
19+
> **`__declspec( empty_bases )`**
20+
21+
## Remarks
22+
23+
In Visual Studio, absent any `__declspec(align())` or `alignas()` specifications, an empty class is 1 byte in size:
24+
25+
```cpp
26+
struct Empty1 {};
27+
static_assert(sizeof(Empty1) == 1, "Empty1 should be 1 byte");
28+
```
29+
30+
A class with a single non-static data member of type `char` is also 1 byte in size:
31+
32+
```cpp
33+
struct Struct1
34+
{
35+
char c;
36+
};
37+
static_assert(sizeof(Struct1) == 1, "Struct1 should be 1 byte");
38+
```
39+
40+
Combining these classes in a class hierarchy also results in a class that's 1 byte in size:
41+
42+
```cpp
43+
struct Derived1 : Empty1
44+
{
45+
char c;
46+
};
47+
static_assert(sizeof(Derived1) == 1, "Derived1 should be 1 byte");
48+
```
49+
50+
This result is the Empty Base Class Optimization at work, as without it `Derived1` would be 2 bytes in size: 1 byte for `Empty1` and 1 byte for `Derived1::c`. The class layout is also optimal when there's a chain of empty classes:
51+
52+
```cpp
53+
struct Empty2 : Empty1 {};
54+
struct Derived2 : Empty2
55+
{
56+
char c;
57+
};
58+
static_assert(sizeof(Derived2) == 1, "Derived2 should be 1 byte");
59+
```
60+
61+
However, the default class layout in Visual Studio doesn't take advantage of EBCO in multiple inheritance scenarios:
62+
63+
```cpp
64+
struct Empty3 {};
65+
struct Derived3 : Empty2, Empty3
66+
{
67+
char c;
68+
};
69+
static_assert(sizeof(Derived3) == 1, "Derived3 should be 1 byte"); // Error
70+
```
71+
72+
Although `Derived3` could be 1 byte in size, the default class layout results in it being 2 bytes in size. The class layout algorithm is adding 1 byte of padding between any two consecutive empty base classes, effectively resulting in `Empty2` consuming an extra byte within `Derived3`:
73+
74+
````
75+
class Derived3 size(2):
76+
+---
77+
0 | +--- (base class Empty2)
78+
0 | | +--- (base class Empty1)
79+
| | +---
80+
| +---
81+
1 | +--- (base class Empty3)
82+
| +---
83+
1 | c
84+
+---
85+
````
86+
87+
The effects of this suboptimal layout are compounded when the alignment requirements of a later base class or member subobject force extra padding:
88+
89+
```cpp
90+
struct Derived4 : Empty2, Empty3
91+
{
92+
int i;
93+
};
94+
static_assert(sizeof(Derived4) == 4, "Derived4 should be 4 bytes"); // Error
95+
```
96+
97+
The natural alignment for an object of type `int` is 4 bytes, so 3 bytes of extra padding must be added after `Empty3` to correctly align `Derived4::i`:
98+
99+
````
100+
class Derived4 size(8):
101+
+---
102+
0 | +--- (base class Empty2)
103+
0 | | +--- (base class Empty1)
104+
| | +---
105+
| +---
106+
1 | +--- (base class Empty3)
107+
| +---
108+
| <alignment member> (size=3)
109+
4 | i
110+
+---
111+
````
112+
113+
Another issue with the default class layout is that an empty base class may be laid out at an offset past the end of the class:
114+
115+
```cpp
116+
struct Struct2 : Struct1, Empty1
117+
{
118+
};
119+
static_assert(sizeof(Struct2) == 1, "Struct2 should be 1 byte");
120+
```
121+
122+
````
123+
class Struct2 size(1):
124+
+---
125+
0 | +--- (base class Struct1)
126+
0 | | c
127+
| +---
128+
1 | +--- (base class Empty1)
129+
| +---
130+
+---
131+
````
132+
133+
Although `Struct2` is the optimal size, `Empty1` is laid out at offset 1 within `Struct2` but the size of `Struct2` isn't increased to account for it. As a result, for an array `A` of `Struct2` objects, the address of the `Empty1` subobject of `A[0]` will be the same as the address of `A[1]`, which shouldn't be the case. This issue wouldn't occur if `Empty1` were laid out at offset 0 within `Struct2`, thereby overlapping the `Struct1` subobject.
134+
135+
The default layout algorithm hasn't been modified to address these limitations and fully take advantage of EBCO. Such a change would break binary compatibility. If the default layout for a class changed as a result of EBCO, every object file and library that contains the class definition would need to be recompiled so they all agree on the class layout. This requirement would also extend to libraries obtained from external sources. The developers of such libraries would have to provide independent versions compiled both with and without the EBCO layout to support customers who use different versions of the compiler. Although we can't change the default layout, we can provide a means to change the layout on a per-class basis with the addition of the `__declspec(empty_bases)` class attribute. A class defined with this attribute can make full use of EBCO.
136+
137+
```cpp
138+
struct __declspec(empty_bases) Derived3 : Empty2, Empty3
139+
{
140+
char c;
141+
};
142+
static_assert(sizeof(Derived3) == 1, "Derived3 should be 1 byte"); // No Error
143+
```
144+
145+
````
146+
class Derived3 size(1):
147+
+---
148+
0 | +--- (base class Empty2)
149+
0 | | +--- (base class Empty1)
150+
| | +---
151+
| +---
152+
0 | +--- (base class Empty3)
153+
| +---
154+
0 | c
155+
+---
156+
````
157+
158+
All of the subobjects of `Derived3` are laid out at offset 0, and its size is the optimal 1 byte. One important point to remember is that `__declspec(empty_bases)` only affects the layout of the class to which it's applied. It isn't applied recursively to base classes:
159+
160+
```cpp
161+
struct __declspec(empty_bases) Derived5 : Derived4
162+
{
163+
};
164+
static_assert(sizeof(Derived5) == 4, "Derived5 should be 4 bytes"); // Error
165+
```
166+
167+
````
168+
class Derived5 size(8):
169+
+---
170+
0 | +--- (base class Derived4)
171+
0 | | +--- (base class Empty2)
172+
0 | | | +--- (base class Empty1)
173+
| | | +---
174+
| | +---
175+
1 | | +--- (base class Empty3)
176+
| | +---
177+
| | <alignment member> (size=3)
178+
4 | | i
179+
| +---
180+
+---
181+
````
182+
183+
Although `__declspec(empty_bases)` is applied to `Derived5`, it isn't eligible for EBCO because it doesn't have any direct empty base classes, so it has no effect. However, if instead it's applied to the `Derived4` base class, which is eligible for EBCO, both `Derived4` and `Derived5` will have optimal layout:
184+
185+
```cpp
186+
struct __declspec(empty_bases) Derived4 : Empty2, Empty3
187+
{
188+
int i;
189+
};
190+
static_assert(sizeof(Derived4) == 4, "Derived4 should be 4 bytes"); // No Error
191+
192+
struct Derived5 : Derived4
193+
{
194+
};
195+
static_assert(sizeof(Derived5) == 4, "Derived5 should be 4 bytes"); // No Error
196+
```
197+
198+
````
199+
class Derived5 size(4):
200+
+---
201+
0 | +--- (base class Derived4)
202+
0 | | +--- (base class Empty2)
203+
0 | | | +--- (base class Empty1)
204+
| | | +---
205+
| | +---
206+
0 | | +--- (base class Empty3)
207+
| | +---
208+
0 | | i
209+
| +---
210+
+---
211+
````
212+
213+
Because of the requirement that all object files and libraries agree on the class layout, `__declspec(empty_bases)` can only be applied to classes that you control. It can't be applied to classes in the standard library, or to classes included in libraries that aren't also recompiled with the EBCO layout.
214+
215+
**END Microsoft Specific**
216+
217+
## See also
218+
219+
[`__declspec`](../cpp/declspec.md)\
220+
[Keywords](../cpp/keywords-cpp.md)

docs/cpp/toc.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,8 @@ items:
690690
href: ../cpp/general-rules-and-limitations.md
691691
- name: Using dllimport and dllexport in C++ classes
692692
href: ../cpp/using-dllimport-and-dllexport-in-cpp-classes.md
693+
- name: empty_bases
694+
href: ../cpp/empty-bases.md
693695
- name: jitintrinsic
694696
href: ../cpp/jitintrinsic.md
695697
- name: naked

0 commit comments

Comments
 (0)