c++ - 在 C++/G++ 中是否有*任何*方法来获取 C 样式数组的长度?

标签 c++ arrays gcc g++

我已经尝试实现一个 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++ 内置函数?

还有重载operator new[]和operator 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 个字节。尽管 operator 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 所建议的那样,还有其他类型的数组也可以进行长度计算,因此构造一个可以接受多种数组(及其长度)的包装类应该是可能的,也是可取的它的构造函数,并且可以隐式或显式地转换为其他包装类和数组类型。不同类型数组的不同生命周期可能是个问题,但可以通过垃圾回收来解决。

最佳答案

回答这个问题:

Any hope for non-compiler-specific code?

没有。

更一般地说,如果您发现自己需要这样做,那么您可能需要重新考虑您的设计。例如,使用 std::vector

关于c++ - 在 C++/G++ 中是否有*任何*方法来获取 C 样式数组的长度?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6373442/

相关文章:

c++ - 在 constexpr 函数中实例化多个模板

c++ - const引用传递的参数在函数中是否完全充当了const类型的变量?

c++ - `type *var = (int)0` ,合法与否?

c++ - 当我只对比较部分值感兴趣而不能为另一部分假定默认构造函数时,如何使用 std::lower_bound

c++ - 如何初始化作为类成员的数组?

javascript - 如何在 React 中的对象内显示对象数组?

arrays - 检查哈希表数组是否包含哈希表

c - char *a[10] 的 strcat 问题

c++ - 何时为可执行文件分配内核版本

c - 关于重复符号的奇怪 ld 错误