我有一个看起来像这样的结构:
struct rtok {
char type;
std::string val;
bool term;
};
我正在写一个简单的解释器,这个“rtok”结构就是我代表一个标记的方式。我有一个“rtoks”向量,我迭代生成解析树。
我的问题是,如果我的结构中有3个成员且我只给1个成员一个值,那么其他成员是否还会占用记忆?
我的意思是,如果我将“val”设置为“test”,我的令牌只占用4个字节还是会占用6个字节? (“val”为4个字节,类型为1个字节,术语为1个字节)
答案 0 :(得分:5)
假设您没有其他成员或虚拟功能,您的结构将始终占用sizeof(char) + sizeof(string) + sizeof(bool) + possible padding
。 string
部分为自己分配了一块内存,它在销毁时解除分配。但是,此内存在技术上不是为struct
分配的内存的一部分。
因此,无论您为成员提供(或省略)值,结构总是具有相同的大小。
答案 1 :(得分:2)
别担心,这需要比你想象的要多得多。
有两个因素:数据对齐和内部类型实现。
首先,关于数据对齐:结构中的所有类型都自然地对齐,这意味着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个字节。
如果你把所有东西都加起来,你就会发现,即使你打算只使用两个字符加上类型,消耗的内存量也要大得多。
答案 2 :(得分:1)
给定类型的struct
总是具有相同的大小。这是标准的保证。当你定义一个struct
时,你会说“我有一个这个大小的对象(成员大小的总和+每个成员对齐的可能填充),它们将在内存中的此顺序(包含struct
定义的成员定义的顺序相同)“:
(N4296) 9.2
/ 12 [示例:类定义的一个简单示例是
struct tnode {
char tword[20];
int count;
tnode* left;
tnode* right;
};
包含一个由二十个字符组成的数组,一个整数和两个指向同一类型对象的指针。 [...] - 示例
/ 13分配具有相同访问控制(第11条)的(非联合)类的非静态数据成员,以便后面的成员在类对象中具有更高的地址。具有不同访问控制的非静态数据成员的分配顺序未指定(第11条)。实施对齐要求 可能导致两个相邻成员不能立即分配;所以可能需要空间来管理虚函数(10.3)和虚基类(10.1)。
请注意“具有相同的访问控制”限定符。如果您的结构混合了具有不同访问说明符的数据成员,那么布局可能不是您所期望的,除了给出类似的保证:
public:
some_type public_1;
private:
some_type private_1;
public:
some_type public_2;
public_2
的地址高于public_1
。除此之外 - 未说明。 private_1
可能位于较低或较高的地址。
关于您的其他问题(在评论中提出):
那么使用类而不是结构会更好吗?
在C ++中,struct
和class
基本相同,唯一的区别是struct
的成员(和继承)默认为public
,而使用class
默认为private
。在标准的注释和示例中,这一点更加清晰:
§3.1声明和定义[basic.def]
/ 3 [注意:在某些情况下,C ++ 实现隐式定义默认构造函数(12.1),copy 构造函数(12.8),移动构造函数(12.8),复制赋值运算符(12.8),移动赋值运算符(12.8)或析构函数(12.4)成员函数。 - 注意] [示例:给出
#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;
}
实现将隐式定义函数以使C的定义等效于
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() { }
};
- 示例]
请注意,标准中的示例使用struct
而不是class
来说明非POD structs
的这一点。当您考虑标准中struct
的定义在第9节 - “类”中时,这一点就更加清晰了。
答案 3 :(得分:-1)
如前所述,struct
始终是固定大小的。
有几种方法可以克服这个限制:
char[1]
作为最后一个成员,并在堆上为struct
本身分配内存。union
为重叠成员节省一些空间。