删除了C标签,因为这引起了一些混淆(它不应该在那里开始;抱歉给那里带来任何不便。但C答案仍然很受欢迎:)
在我做过的一些事情中,我发现需要创建具有动态大小和静态大小的对象,其中静态部分是基本对象成员,但动态部分是数组/缓冲区直接附加到类上,保持内存连续,从而减少所需的分配量(这些是不可重新分配的对象),并减少碎片(虽然作为一个缺点,可能更难找到足够大的块但是,如果它甚至应该发生,那么这就比堆碎片更加罕见。这对于内存非常重要的嵌入式设备也很有用(但我目前对嵌入式设备没有任何作用),以及像std :: string一样需要避免,或者不能像琐碎的联合一样使用。
一般来说,我的方法是(ab)使用malloc(std :: string不是故意使用的,并且由于各种原因):
struct TextCache
{
uint_32 fFlags;
uint_16 nXpos;
uint_16 nYpos;
TextCache* pNext;
char pBuffer[0];
};
TextCache* pCache = (TextCache*)malloc(sizeof(TextCache) + (sizeof(char) * nLength));
然而,这对我来说并不是很好,因为首先我想使用new,因此在C ++环境中这样做,其次,它看起来很可怕:P
下一步是模板化的C ++变量:
template <const size_t nSize> struct TextCache
{
uint_32 fFlags;
uint_16 nXpos;
uint_16 nYpos;
TextCache<nSize>* pNext;
char pBuffer[nSize];
};
然而,存在这样的问题:存储指向可变大小对象的指针变得“不可能”,因此下一步解决:
class DynamicObject {};
template <const size_t nSize> struct TextCache : DynamicObject {...};
然而,这仍然需要进行转换,并且当更多的动态大小的对象从它派生时,遍布整个地方的指向动态对象变得模棱两可(它看起来也很糟糕并且可能遭受强制空类仍然具有大小的错误,虽然这可能是一个古老的,灭绝的错误......)。
然后有这个:
class DynamicObject
{
void* operator new(size_t nSize, size_t nLength)
{
return malloc(nSize + nLength);
}
};
struct TextCache : DynamicObject {...};
看起来好多了,但会干扰已经有新的重载的对象(甚至可能会影响新的放置......)。
最后我提出了新的滥用行为:
inline TextCache* CreateTextCache(size_t nLength)
{
char* pNew = new char[sizeof(TextCache) + nLength];
return new(pNew) TextCache;
}
这可能是迄今为止最糟糕的想法,原因有很多。
那么还有更好的方法吗?或者上述版本之一会更好,还是至少可以改进?是否甚至考虑过安全和/或糟糕的编程习惯?
正如我上面所说,我试图避免双重分配,因为这不需要2次分配,因此这使得将这些东西写入(序列化)到文件更容易。 我所拥有的双重分配要求的唯一例外是其基本上为零开销。我遇到的唯一原因是我从一个固定的缓冲区(using this system顺序分配内存,我想出了它),但它也是一个特殊的例外,以防止超级复制。
答案 0 :(得分:8)
我会与DynamicObject
概念达成妥协。所有不依赖于大小的东西都会进入基类。
struct TextBase
{
uint_32 fFlags;
uint_16 nXpos;
uint_16 nYpos;
TextBase* pNext;
};
template <const size_t nSize> struct TextCache : public TextBase
{
char pBuffer[nSize];
};
这应该减少所需的铸件。
答案 1 :(得分:3)
C99祝福'struct hack' - 又名灵活的阵列成员。
§6.7.2.1结构和联合说明符
¶16作为一种特殊情况,具有多个命名成员的结构的最后一个元素可以 有一个不完整的数组类型;这被称为灵活的阵列成员。有两个 异常,灵活的数组成员被忽略。首先,结构的大小应为 等于替换其他相同结构的最后一个元素的偏移量 具有未指定长度数组的灵活数组成员。 106)其次,当a。 (或 - &gt;) operator有一个左操作数,它是一个带有灵活数组成员的结构(指向) 并且右边的操作数命名该成员,它的行为就像该成员被替换一样 最长的数组(具有相同的元素类型),不会构成结构 大于被访问的对象;数组的偏移量应保持为 灵活的阵列成员,即使这与替换阵列的成员不同。如果这 数组没有元素,它的行为好像它有一个元素,但行为是 如果尝试访问该元素或生成过去的指针,则为undefined 它
¶17示例假设所有数组成员在声明之后对齐相同:
struct s { int n; double d[]; }; struct ss { int n; double d[1]; };
三个表达式:
sizeof (struct s) offsetof(struct s, d) offsetof(struct ss, d)
具有相同的值。结构struct具有灵活的数组成员d。
106)未指定长度以允许实现可能使数组成员不同 根据它们的长度进行对齐。
否则,使用两个单独的分配 - 一个用于结构中的核心数据,另一个用于附加数据。
答案 2 :(得分:2)
关于C或C ++程序员的经济思维方式可能看起来很异议,但上次我有一个类似的问题要解决我选择在我的struct中放置一个固定大小的静态缓冲区并通过指针间接访问它。如果给定的结构比我的静态缓冲区大,则间接指针随后被动态分配(并且内部缓冲区未被使用)。这非常简单,并解决了你提出的问题,如碎片,因为静态缓冲区被用于超过95%的实际用例,剩下的5%需要非常大的缓冲区,因此我并不关心内部缓冲区的损失很小。
答案 3 :(得分:0)
我相信,在C ++中,技术上这是 Undefined Behavior (由于对齐问题),尽管我怀疑它可能适用于每个现有的实现。
但是为什么呢?
答案 4 :(得分:0)
您可以在C ++中使用placement new
:
char *buff = new char[sizeof(TextCache) + (sizeof(char) * nLength)];
TextCache *pCache = new (buff) TextCache;
唯一需要注意的是,您需要删除buff
而不是pCache
,如果pCache
有析构函数,则必须手动调用它。
如果您打算使用pBuffer
访问此额外区域,我建议您这样做:
struct TextCache
{
...
char *pBuffer;
};
...
char *buff = new char[sizeof(TextCache) + (sizeof(char) * nLength)];
TextCache *pCache = new (buff) TextCache;
pCache->pBuffer = new (buff + sizeof(TextCache)) char[nLength];
...
delete [] buff;
答案 5 :(得分:0)
管理自己的记忆没有错。
template<typename DerivedType, typename ElemType> struct appended_array {
ElemType* buffer;
int length;
~appended_array() {
for(int i = 0; i < length; i++)
buffer->~ElemType();
char* ptr = (char*)this - sizeof(DerivedType);
delete[] ptr;
}
static inline DerivedType* Create(int extra) {
char* newbuf = new char[sizeof(DerivedType) + (extra * sizeof(ElemType))];
DerivedType* ptr = new (newbuf) DerivedType();
ElemType* extrabuf = (ElemType*)newbuf[sizeof(DerivedType)];
for(int i = 0; i < extra; i++)
new (&extrabuf[i]) ElemType();
ptr->lenghth = extra;
ptr->buffer = extrabuf;
return ptr;
}
};
struct TextCache : appended_array<TextCache, char>
{
uint_32 fFlags;
uint_16 nXpos;
uint_16 nYpos;
TextCache* pNext;
// IT'S A MIRACLE! We have a buffer of size length and pointed to by buffer of type char that automagically appears for us in the Create function.
};
但是,您应该考虑这种优化是不成熟的,并且有更好的方法,例如拥有对象池或托管堆。此外,我没有计算任何对齐,但是我的理解是sizeof()返回对齐的大小。而且,这将是维持非平凡建筑的婊子。此外,这是完全未经测试的。托管堆是一种更好的想法。但是你不应该害怕管理自己的内存 - 如果你有自定义内存要求,你需要管理自己的内存。
我突然意识到我已经破坏但没有删除“额外”记忆。