C++结构成员内存分配
C++ struct member memory allocation
我有一个看起来像这样的结构:
struct rtok {
char type;
std::string val;
bool term;
};
我正在编写一个简单的解释器,这个 "rtok" 结构是我表示令牌的方式。我有一个 "rtoks" 的向量,我迭代它以生成解析树。
我的问题是,如果我的结构中有3个成员,我只给1个成员赋值,其他成员是否还会占用内存?
我的意思是,如果我设置 "val" 等于 "test",我的令牌会占用 4 个字节还是占用 6 个字节? (4 个字节用于 "val",1 个字节用于类型,1 个字节用于术语)
假设您没有额外的成员或虚函数,您的结构将始终占用 sizeof(char) + sizeof(string) + sizeof(bool) + possible padding
。 string
部分为自己分配了一块内存,并在销毁时将其释放。但是,从技术上讲,此内存并不是为 struct
分配的内存的一部分。
因此无论您为成员提供(或省略)任何值,该结构将始终具有相同的大小。
别担心,这比您想象的要多得多。
有两个因素:数据对齐和内部类型实现。
首先,关于数据对齐:你的结构中的所有类型都是自然对齐的,这意味着char
可以在任何地址,但是void*
可能需要对齐4 或 8,具体取决于体系结构。
所以,如果我们猜测,std::string 在内部简单地使用 char*
来让你串起来 x32 上的布局将是:
struct rtok {
char type;
char* val; // here char * for simplicity
bool term;
};
sizeof(rtok)
运算符将给出 12 个字节,而不是 6 个,内存占用量如下所示:
00: type (one byte)
01: padding
02: padding
03: padding
04-07: char * (4 bytes)
08: term (one byte)
09-0a: padding (3 bytes)
现在,如果我们将 char*
替换为 std::string
,我们会发现结构大小增加了,因为 sizeof(std::string)
通常大于 4 个字节。
但是,我们还没有计算字符串值本身...这里我们进入堆管理和分配领域。
存储值的内存分配在堆上,代码通常请求多少就请求多少,所以对于 10 个字符的字符串,它将是 11 个字节(10 个字符加上空终止符 1 个字节)。
并且堆具有自己的复杂结构,包括小块堆等。在实践中,这意味着最小消耗量大约为 16 字节或更多。这个数量不是你能用的,这个数量是用来管理堆内部结构的,唯一可以使用的数量只有1个字节。
如果你把所有东西加起来,你会发现即使你打算只使用两个字符加上类型,消耗的内存量也会大得多。
如前所述,struct
始终是固定大小的。
有几种方法可以克服这个限制:
- 存储指针并为其分配堆内存。
- 使用
char[1]
的"unbound"数组作为最后一个成员,并在堆上为struct
本身分配内存。
- 使用
union
为重叠成员节省一些 space。
给定类型的 struct
始终具有相同的大小。这是标准的保证。当您定义 struct
时,您说的是 "I have an object of this size (sum of sizes of members + possible padding for alignment for each member), and they are to be in this order in memory (same order of member definitions in containing struct
definition)":
(N4296)
9.2
/12 [ Example: A simple example of a class definition is
struct tnode {
char tword[20];
int count;
tnode* left;
tnode* right;
};
其中包含一个包含二十个字符的数组、一个整数和两个指向相同类型对象的指针。 [...] -结束示例
/13 Nonstatic data members of a (non-union) class with the same access control (Clause 11) are allocated so that later members have higher addresses within a class object. The order of allocation of non-static data members with different access control is unspecified (Clause 11). Implementation alignment requirements
might cause two adjacent members not to be allocated immediately after each other; so might requirements for space for managing virtual functions (10.3) and virtual base classes (10.1).
注意“具有相同的访问控制”限定符。如果您的结构混合了具有不同访问说明符的数据成员,则布局可能不是您所期望的,除了给出如下内容的保证:
public:
some_type public_1;
private:
some_type private_1;
public:
some_type public_2;
public_2
将位于比 public_1
更高的地址。除此之外 - 未指定。 private_1
可能位于较低或较高的地址。
关于您的其他问题(在评论中提出):
Would it be better to use a class instead of a struct then?
在 C++ 中,struct
和 class
本质上是相同的,唯一的区别是 struct
的成员(和继承)默认为 public
,而对于 class
,它们默认为 private
。这在标准的注释和示例中更加清楚:
§3.1 Declarations and definitions [basic.def]
/3 [ Note: In some circumstances, C ++
implementations implicitly define the default constructor (12.1), copy
constructor (12.8), move constructor (12.8), copy assignment operator (12.8), move assignment operator (12.8), or destructor (12.4) member functions. —end note ] [ Example: given
#include <string>
struct C {
std::string s; // std::string is the standard library class (Clause 21)
};
int main() {
C a;
C b = a;
b = a;
}
the implementation will implicitly define functions to make the definition of C equivalent to
struct C {
std::string s;
C() : s() { }
C(const C& x): s(x.s) { }
C(C&& x): s(static_cast<std::string&&>(x.s)) { }
// : s(std::move(x.s)) { }
C& operator=(const C& x) { s = x.s; return *this; }
C& operator=(C&& x) { s = static_cast<std::string&&>(x.s); return *this; }
// { s = std::move(x.s); return *this; }
~C() { }
};
—end example ]
请注意,标准中的示例使用 struct
而不是 class
来说明非 POD structs
的这一点。当您考虑到标准中 struct
的定义在第 9 节 - "Classes."
时,这一点就更加清楚了
我有一个看起来像这样的结构:
struct rtok {
char type;
std::string val;
bool term;
};
我正在编写一个简单的解释器,这个 "rtok" 结构是我表示令牌的方式。我有一个 "rtoks" 的向量,我迭代它以生成解析树。
我的问题是,如果我的结构中有3个成员,我只给1个成员赋值,其他成员是否还会占用内存?
我的意思是,如果我设置 "val" 等于 "test",我的令牌会占用 4 个字节还是占用 6 个字节? (4 个字节用于 "val",1 个字节用于类型,1 个字节用于术语)
假设您没有额外的成员或虚函数,您的结构将始终占用 sizeof(char) + sizeof(string) + sizeof(bool) + possible padding
。 string
部分为自己分配了一块内存,并在销毁时将其释放。但是,从技术上讲,此内存并不是为 struct
分配的内存的一部分。
因此无论您为成员提供(或省略)任何值,该结构将始终具有相同的大小。
别担心,这比您想象的要多得多。
有两个因素:数据对齐和内部类型实现。
首先,关于数据对齐:你的结构中的所有类型都是自然对齐的,这意味着char
可以在任何地址,但是void*
可能需要对齐4 或 8,具体取决于体系结构。
所以,如果我们猜测,std::string 在内部简单地使用 char*
来让你串起来 x32 上的布局将是:
struct rtok {
char type;
char* val; // here char * for simplicity
bool term;
};
sizeof(rtok)
运算符将给出 12 个字节,而不是 6 个,内存占用量如下所示:
00: type (one byte)
01: padding
02: padding
03: padding
04-07: char * (4 bytes)
08: term (one byte)
09-0a: padding (3 bytes)
现在,如果我们将 char*
替换为 std::string
,我们会发现结构大小增加了,因为 sizeof(std::string)
通常大于 4 个字节。
但是,我们还没有计算字符串值本身...这里我们进入堆管理和分配领域。
存储值的内存分配在堆上,代码通常请求多少就请求多少,所以对于 10 个字符的字符串,它将是 11 个字节(10 个字符加上空终止符 1 个字节)。
并且堆具有自己的复杂结构,包括小块堆等。在实践中,这意味着最小消耗量大约为 16 字节或更多。这个数量不是你能用的,这个数量是用来管理堆内部结构的,唯一可以使用的数量只有1个字节。
如果你把所有东西加起来,你会发现即使你打算只使用两个字符加上类型,消耗的内存量也会大得多。
如前所述,struct
始终是固定大小的。
有几种方法可以克服这个限制:
- 存储指针并为其分配堆内存。
- 使用
char[1]
的"unbound"数组作为最后一个成员,并在堆上为struct
本身分配内存。 - 使用
union
为重叠成员节省一些 space。
给定类型的 struct
始终具有相同的大小。这是标准的保证。当您定义 struct
时,您说的是 "I have an object of this size (sum of sizes of members + possible padding for alignment for each member), and they are to be in this order in memory (same order of member definitions in containing struct
definition)":
(N4296) 9.2
/12 [ Example: A simple example of a class definition is
struct tnode {
char tword[20];
int count;
tnode* left;
tnode* right;
};
其中包含一个包含二十个字符的数组、一个整数和两个指向相同类型对象的指针。 [...] -结束示例
/13 Nonstatic data members of a (non-union) class with the same access control (Clause 11) are allocated so that later members have higher addresses within a class object. The order of allocation of non-static data members with different access control is unspecified (Clause 11). Implementation alignment requirements might cause two adjacent members not to be allocated immediately after each other; so might requirements for space for managing virtual functions (10.3) and virtual base classes (10.1).
注意“具有相同的访问控制”限定符。如果您的结构混合了具有不同访问说明符的数据成员,则布局可能不是您所期望的,除了给出如下内容的保证:
public:
some_type public_1;
private:
some_type private_1;
public:
some_type public_2;
public_2
将位于比 public_1
更高的地址。除此之外 - 未指定。 private_1
可能位于较低或较高的地址。
关于您的其他问题(在评论中提出):
Would it be better to use a class instead of a struct then?
在 C++ 中,struct
和 class
本质上是相同的,唯一的区别是 struct
的成员(和继承)默认为 public
,而对于 class
,它们默认为 private
。这在标准的注释和示例中更加清楚:
§3.1 Declarations and definitions [basic.def]
/3 [ Note: In some circumstances, C ++ implementations implicitly define the default constructor (12.1), copy constructor (12.8), move constructor (12.8), copy assignment operator (12.8), move assignment operator (12.8), or destructor (12.4) member functions. —end note ] [ Example: given
#include <string>
struct C {
std::string s; // std::string is the standard library class (Clause 21)
};
int main() {
C a;
C b = a;
b = a;
}
the implementation will implicitly define functions to make the definition of C equivalent to
struct C {
std::string s;
C() : s() { }
C(const C& x): s(x.s) { }
C(C&& x): s(static_cast<std::string&&>(x.s)) { }
// : s(std::move(x.s)) { }
C& operator=(const C& x) { s = x.s; return *this; }
C& operator=(C&& x) { s = static_cast<std::string&&>(x.s); return *this; }
// { s = std::move(x.s); return *this; }
~C() { }
};
—end example ]
请注意,标准中的示例使用 struct
而不是 class
来说明非 POD structs
的这一点。当您考虑到标准中 struct
的定义在第 9 节 - "Classes."