For the most part, C code that defines and uses structures behaves the same when compiled and executed as C++. However, C++ generalizes structures into classes. A C++ class can have elements that a C structure cannot, such as access specifiers, member functions, static data members, and base classes. In one of my blogs, I explained how some of these elements can alter the physical layout of class objects.1 This month, I'll expand my discussion of alignment, padding and member ordering to include access control.
Access specifiers
C++ class members can have different levels of accessibility:
- A public member of a class is accessible anywhere in the program where the class itself is visible and accessible.
- A private member of a class is accessible only to members and friends of that class.
- A protected member of a class is accessible only to members and friends of that class or to members and friends of classes derived from that class.
For example, a class widget defined as:
class widget
{
public:
widget();
protected:
~widget();
private:
char m1;
int m2;
char m3;
static int k;
};
has one public member function (a default constructor), one protected member function (a destructor), and four private data members (three non-static and one static).
Each access-specifier can appear more than once, and in any order. For example, you could rewrite the previous definition for widget as:
class widget
{
protected:
~widget();
private:
char m1;
int m2;
private:
char m3;
static int k;
public:
widget();
};
Here the keyword private appears twice and the public member is at the end (rather than the beginning) of the class definition. These changes aren't likely to alter the size or alignment of widget objects.
Member allocation order
Within a given C structure, the members have offsets that increase in the order in which they are declared. In C++, a structure is just a class in which the members (and base classes) are public by default. Thus, in a class with only public members, such as:
class widget
{
public:
widget();
~widget();
char m1;
int m2;
char m3;
static int k;
};
compilers allocate storage within each widget object for the non-static data members m1, m2, and m3 in that order. As I explained bfore, the constructor, destructor and static data member k add no storage to each widget object.
What about a class with private or protected members? The relevant words in the draft of the next C++ Standard differ somewhat from the words in the current (2003) C++ Standard. I believe the draft's words are a better description of current practice. The draft states, "Non-static data members of a ... class with the same access control are allocated so that later members have higher addresses within a class object."2
Storage allocation for a class in which all non-static data members have the same access—all public, or all private, or all protected—behaves just like storage allocation in a C structure. For example, the storage layout for widget objects when widget is defined as:
class widget
{
public:
widget();
~widget();
static int k;
private:
char m1;
int m2;
char m3;
};
is the same as when widget is defined as:
class widget
{
public:
widget();
~widget();
char m1;
int m2;
char m3;
static int k;
};
which is the same as when widget is defined as:
struct widget
{
char m1;
int m2;
char m3;
};
These different class definitions aren't functionally equivalent, but they yield objects with the same size, alignment and member arrangement.
Freedom to rearrange things
The draft Standard also states that "The order of allocation of non-static data members with different access control is unspecified." This means that, if it wishes, a compiler can allocate members with different accessibility to reduce or eliminate the need for padding.
For example, suppose class widget is defined as:
class widget
{
public:
char m1;
protected:
int m2;
private:
char m3;
};
When compiled for a target machine in which each int occupies four bytes aligned to an address that's a multiple of four, the compiler can allocate storage for the members in the order in which they're declared and insert padding as if the class were defined as:
class widget
{
public:
char m1;
char padding_after_m1[3];
protected:
int m2;
private:
char m3;
char padding_after_m3[3];
};
This layout requires six bytes of padding.
On the other hand, the compiler can reorder the members to reduce the need for padding bytes, as either:
class widget
{
public:
char m1;
private:
char m3;
char padding[2];
protected:
int m2;
};
or as:
class widget
{
protected:
int m2;
public:
char m1;
private:
char m3;
char padding[2];
};
Using either rearrangement eliminates four padding bytes and shrinks the size of each widget object by that much.
A C++ compiler can reorder members with different access specifiers, but can't reorder members within the same access specifier. For example, given:
class widget
{
public:
char m1;
int m2;
private:
char m3;
};
the compiler can't reorder the members so that m2 is allocated before m1, as in:
class widget
{
public:
int m2;
char m1;
private:
char m3;
};
However, it can reorder the members so that m3 is before both m1 and m2, as in:
class widget
{
private:
char m3;
public:
char m1;
char padding[2];
int m2;
};
None of the compilers that I have appear to rearrange members this aggressively. At least, I haven't been able to discover compiler options that will trigger such optimizations. If you have a compiler that will do this, please let me know which compiler(s) and compile option(s) you used to obtain this optimization.
Endnotes:
1. Saks, Dan. "Classes are structure, and then some," Embeddeddesignindia.com, March 2011.
2. Becker, Pete. Working Draft, Standard for Programming Language C++. ISO/IEC, February 2009.
文章评论(0条评论)
登录后参与讨论