问题描述
|
我已经尝试实现lengthof(T * v)函数了一段时间了,到目前为止还没有成功。
对于T v [n]数组,有两种众所周知的基本解决方案,一旦将数组衰减为T * v指针,这两种方法都是无用的,甚至是危险的。
#define SIZE(v) (sizeof(v) / sizeof(v[0]))
template <class T,size_t n>
size_t lengthof (T (&) [n])
{
return n;
}
有一些解决方法涉及包装器类和容器,例如STLSoft的array_proxy,boost :: array,std :: vector等。它们都有缺点,并且缺乏简单性,语法糖和数组的广泛使用。
关于涉及特定于编译器的解决方案的说法存在误解,当delete []需要知道数组的长度时,编译器通常会使用这些解决方案。根据C ++ FAQ Lite 16.14,编译器使用两种技术来知道要释放多少内存:过度分配和关联数组。在过度分配时,它会分配更多的字量,并将数组的长度放在第一个对象之前。显然,另一种方法将长度存储在关联数组中。是否有可能知道G ++使用哪种方法,并提取适当的数组长度?那开销和填充物呢?对非编译器专用代码有希望吗?甚至非平台特定的G ++内置程序?
我还实现了涉及重载运算符new []和运算符delete []的解决方案:
std::map<void*,size_t> arrayLengthMap;
inline void* operator new [] (size_t n)
throw (std::bad_alloc)
{
void* ptr = GC_malloc(n);
arrayLengthMap[ptr] = n;
return ptr;
}
inline void operator delete [] (void* ptr)
throw ()
{
arrayLengthMap.erase(ptr);
GC_free(ptr);
}
template <class T>
inline size_t lengthof (T* ptr)
{
std::map<void*,size_t>::const_iterator it = arrayLengthMap.find(ptr);
if( it == arrayLengthMap.end() ){
throw std::bad_alloc();
}
return it->second / sizeof(T);
}
直到出现一个奇怪的错误,它一直运行良好:lengthof找不到数组。事实证明,G ++在此特定数组的开头分配了比应有的多8个字节。尽管运算符new []应该返回了整个数组的开始,将其称为ptr,但调用代码却改为ptr + 8,因此lengthof(ptr + 8)显然因失败而失败了(即使没有,它也可能可能返回了错误的数组大小)。那8个字节是某种开销还是填充?不能被前面提到的过度分配,该功能对于许多数组都能正常工作。假设可以使用G ++特定的调用或欺骗,它是什么以及如何禁用或解决它?
编辑:
由于可以通过多种方式分配C样式的数组,因此通常无法像Oli Charlesworth所建议的那样通过其指针来判断任意数组的长度。但是可能会出现非衰减的静态数组(请参见上面的模板函数),以及根据Ben Voigt的想法用自定义运算符new [](size_t,size_t)分配的数组:
#include <gc/gc.h>
#include <gc/gc_cpp.h>
#include <iostream>
#include <map>
typedef std::map<void*,std::pair<size_t,size_t> > ArrayLengthMap;
ArrayLengthMap arrayLengthMap;
inline void* operator new [] (size_t size,size_t count)
throw (std::bad_alloc)
{
void* ptr = GC_malloc(size);
arrayLengthMap[ptr] = std::pair<size_t,size_t>(size,count);
return ptr;
}
inline void operator delete [] (void* ptr)
throw ()
{
ArrayLengthMap::const_iterator it = arrayLengthMap.upper_bound(ptr);
it--;
if( it->first <= ptr and ptr < it->first + it->second.first ){
arrayLengthMap.erase(it->first);
}
GC_free(ptr);
}
inline size_t lengthof (void* ptr)
{
ArrayLengthMap::const_iterator it = arrayLengthMap.upper_bound(ptr);
it--;
if( it->first <= ptr and ptr < it->first + it->second.first ){
return it->second.second;
}
throw std::bad_alloc();
}
int main (int argc,char* argv[])
{
int* v = new (112) int[112];
std::cout << lengthof(v) << std::endl;
}
不幸的是,由于编译器的任意开销和填充,到目前为止,尚无可靠的方法来确定自定义运算符new [](size_t)中动态数组的长度,除非我们假定填充小于一的大小。数组元素。
但是,正如Ben Voigt所建议的那样,还有其他类型的数组也可以进行长度计算,因此应该并且可能希望构造一个在其构造函数中可以接受多种数组(及其长度)的包装器类,并且可以隐式或显式转换为其他包装类和数组类型。不同种类的数组的不同生存期可能是一个问题,但是可以通过垃圾回收解决。
解决方法
要回答这个问题:
对非编译器专用代码有希望吗?
没有。
一般来说,如果您发现自己需要这样做,那么您可能需要重新考虑您的设计。例如,使用“ 3”。
, 您的分析基本上是正确的,但是我认为您忽略了以下事实:带有琐碎析构函数的类型不需要存储长度,因此对于不同类型,过度分配可能会有所不同。
该标准允许ѭ4窃取一些字节以供自己使用,因此您必须对指针进行范围检查,而不是精确匹配。
std::map
可能对此无效,但应该对排序后的向量进行排序(可以进行二进制搜索)。平衡的树也应该很好地工作。
,前一段时间,我使用了类似的方法来监视内存泄漏:
当要求分配大小字节的数据时,我将分配大小+ 4个字节,并将分配的长度存储在前4个字节中:
static unsigned int total_still_alloced = 0;
void *sys_malloc(UINT size)
{
#if ENABLED( MEMLEAK_CHECK )
void *result = malloc(size+sizeof(UINT ));
if(result)
{
memset(result,size+sizeof(UINT ));
*(UINT *)result = size;
total_still_alloced += size;
return (void*)((UINT*)result+sizeof(UINT));
}
else
{
return result;
}
#else
void *result = malloc(size);
if(result) memset(result,size);
return result;
#endif
}
void sys_free(void *p)
{
if(p != NULL)
{
#if ENABLED( MEMLEAK_CHECK )
UINT * real_address = (UINT *)(p)-sizeof(UINT);
total_still_alloced-= *((UINT *)real_address);
free((void*)real_address);
#else
free(p);
#endif
}
}
在您的情况下,检索分配的大小只需将提供的地址移位4并读取值即可。
请注意,如果某处内存损坏...您将得到无效的结果。
还要注意,malloc通常是内部工作的方式:在返回地址之前,将分配的大小放在隐藏字段中。在某些体系结构上,我什至不必分配更多,使用系统malloc就足够了。
这是一种侵入式的方法……但是它可以工作(只要您使用这些修改后的分配例程分配所有内容,并且您知道数组的起始地址)。