C++类对象数组中的内存分配问题

6

我有一个类似这样的类:

class Object {
public: 
    unsigned char data[8];
    // other variables
    // functions etc...
 };

问题是 - 对象成员是否都相对于对象存储在同一内存位置?如果我有一个数组:Object array[3],给定一个 char 指针 char* data_ptr = array[0].data,那么 data_ptr + (sizeof(Object)) 是否总是指向 array[1].data?(我读过几个关于类和结构体数据成员之间可能存在填充的问答,但我认为它们没有回答我的问题。)谢谢!Ben
5个回答

4
sizeof Object已经包含了类Object的所有内部填充,包括其末尾的任何填充。数组不允许任何额外的填充。因此,data_ptr + sizeof Object将具有array[1].data的地址。
然而,我不确定这是否被允许。也就是说,编译器可能会假设您从不将大于8(成员数组data的大小)的值添加到array[0].data中,并因此应用失败的优化。也就是说,您的代码实际上可能表现出未定义的行为(这是“编译器在这种情况下可以做任何事情”的标准术语)。
然而,由于您正在使用指向char的指针,对于它有更宽松的规则(您可以使用char*执行许多不能使用通用类型执行的操作),因此实际上可能是定义行为。

虽然char*可以用于通用数据,但是unsigned char*uint8_t是原始字节访问的首选方法吗? - MartyE
1
确实,为了访问原始字节,unsigned char+uint8_t是更好的选择(因为在非二进制补码机器上,有两种零的表示方法)。但是更宽松的规则也适用于char。在这种特殊情况下,不会发生原始字节访问;指针最终指向的数组再次成为char数组。 - celtschk
我不理解你的第二段话(为什么是8?)(抱歉 - 我对C++很新!)。 目前它对我来说似乎可以工作(我正在使用它将无符号字符值从一个数组复制到另一个数组),但我显然希望确保未来不会出现问题! - user1483596
@user1483596:8 是因为你将成员 data 定义为 char data[8]。也就是说,将 8 添加到 array[0].data 将给出该成员数组的一过尾指针,这当然是允许的。添加任何更大的值意味着你离开了数组,这可能是未定义的行为(这意味着编译器在这种情况下可以做任何它想做的事情,因此如果你这样做,优化可能会出错)。 - celtschk
作为一个经验法则,你可以将“未定义行为”翻译为“不要这样做”。 - celtschk

2

如果你的问题目的是为了理解内存中的情况,那么这是可以接受的。

但如果你想要真正做到这一点:你想要做的实际上是违法的,这对你所有的同事都是有害的。

在C++中遍历集合的正确方法不是使用数组,而是使用std::vector或其他std集合(如果真的需要)。我们不应再使用C算术,而是通过迭代器访问向量集合的项。这就是C++标准库的原因 :-)


我会研究迭代器 - 我以前从未使用过它们(我是c++的新手)。我选择这种指针方法的原因是因为我需要将成千上万个对象的数据元素复制到另一个数组中,然后使用opengl进行绘制。 - user1483596
你也可以在C数组上实例化一个std::vector:我将在我的答案下面发布代码,仅供参考以展示如何操作。我建议您学习如何使用std库。缓慢/按自己的节奏进行没有关系,欢迎来到C++ ;-) - Stephane Rolland
太糟糕了,我说话太早了...虽然从std::vector很容易得到一个标准的c数组(指向它的第一个元素,并考虑它的大小),但相反地,我没有找到一种方法来获得一个新的std::vector指向已经存在的数组(这是你在opengl中提供的,我理解的):不进行数组内容复制是不可能的,而对于像图像这样的大数组来说,这可能是低效的。 - Stephane Rolland
没问题 - 我会自己调查。我已经有一个Array类作为纯数组的包装器,我会看看如何轻松地将其更改为std::vector的包装器。 - user1483596

1
我认为答案是“可能”,这意味着你不应该把赌注压在这上面。最好的方法是在你的IDE调试器上玩耍,查找内存地址。当你引入编译器可以优化的成员和方法时,你会发现这很容易被扰乱。例如任何常量或一个不访问任何可能是静态的成员的方法。例如:
void Object::doSomething() {std::cout << "something\n" << std::endl;}

我相信这实际上会被优化为静态分配,因为我最近学到这个((Object)NULL).doSomething();在没有为Object引入成员变量的情况下可以正常工作而不会导致段错误。


有趣的是,两个具有与我问题中相同数据数组的相同类,一个带有打印函数,另一个没有,大小相同。 但指针算术仍然似乎按预期工作... - user1483596

1

决定C++类对象大小的因素有很多,包括: - 所有非静态数据成员的大小 - 数据成员的顺序 - 字节对齐或字节填充 - 直接基类的大小 - 存在虚函数(使用虚函数的动态多态性)。 - 使用的编译器 - 继承模式(虚继承)

请查看这里:http://www.cprogramming.com/tutorial/size_of_class_object.html


虽然对象的大小可以由相当复杂的规则确定,但在这种特殊情况下,这些规则是无关紧要的,因为数组包含完整的对象(您不能定义不完整对象的数组),因此sizeof Object已经包括任何填充、隐藏成员或其他内容。 - celtschk

1

是的,相对于对象的基地址的布局将是恒定的。这是ABI的要求。任何对象和数组的间距或填充也由ABI指定。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接