在C++/G++中,是否有*任何*方法可以获得C样式数组的长度
Is there *any* way to get the length of a C-style array in C++/G++?
我已经尝试实现lengthof(T*v)函数很长一段时间了,但到目前为止没有任何成功。
对于Tv[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的arrayproxy、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所建议的,也有其他类型的数组可以进行长度计算,因此,构建一个包装类应该是可能的,也是可取的,该包装类可以在其构造函数中接受几种数组(及其长度),并且可以隐式或显式转换为其他包装类和数组类型。不同类型的数组的不同寿命可能是一个问题,但可以通过垃圾收集来解决。
要回答这个问题:
对非编译器特定的代码有希望吗?
没有。
更普遍地说,如果你发现自己需要这样做,那么你可能需要重新考虑你的设计。例如,使用std::vector
。
您的分析基本上是正确的,但我认为您忽略了一个事实,即具有琐碎析构函数的类型不需要存储长度,因此不同类型的过度分配可能不同。
该标准允许operator new[]
窃取几个字节供自己使用,因此您必须对指针进行范围检查,而不是进行精确匹配。std::map
可能对此没有效率,但排序向量应该是(可以二进制搜索)。一棵平衡的树也应该很好地工作。
前段时间,我使用了一个类似的东西来监控内存泄漏:
当被要求分配size字节的数据时,我会分配size+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,0,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,0,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就足够了。
这是一种侵入性的方法……但它是有效的(前提是您使用这些修改后的分配例程分配所有内容,并且您知道数组的起始地址)。
- 将包含C样式数组的对象初始化为成员变量(C++)
- C++中的高效循环缓冲区,它将被传递给C样式数组函数参数
- 当 std::move 与 C 样式数组或不移动对象时会发生什么
- 如何在谷歌模拟中匹配 C 样式数组
- 迭代器库中的 std::size() 不适用于传递给函数的 C 样式数组
- 解压缩 C 样式数组以及C++中的参数包
- 复制 C 样式数组和结构
- C++中循环和 C 样式数组的范围工作
- C++ - 移动具有固定大小的 c 样式数组成员的类的构造函数
- std::array与C样式数组用于连续内存
- 从原始指针(衰减的 C 样式数组)和大小生成范围::视图
- 不要声明 C 样式数组,而是使用 std::array<>
- 带C++的 C 样式数组?
- 如何从嵌套的 std::initializer_list 初始化 2D C 样式数组?
- 将 unique_ptr<std::vector> 与 c 样式数组结合使用
- 为什么我不能将 C 样式数组复制到 std::array?
- 函数需要 C 样式数组
- 键入 trait 以获取 std::array 或 C 样式数组的元素类型
- 何时需要删除 C 样式数组
- C++ C 样式数组作为语法错误的参数