为什么我们可以删除数组,但不知道C / C ++中的长度?

时间:2010-10-22 08:09:51

标签: c++ c arrays

我们有可能删除动态分配的数组,但我们无法找出它们有多少个元素?我们不能把内存位置的大小除以每个对象的大小吗?

7 个答案:

答案 0 :(得分:16)

在C ++中,两者都是......

  • new,new []或malloc调用请求的大小(字节)和
  • 新[]动态分配中请求的数组元素数

...是标准不需要以编程方式提供的实现细节,即使内存分配库必须记住前者和编译器后者,因此它可以在正确数量的元素上调用析构函数。 / p>

有时编译器可能会看到有一个恒定大小的分配,并且能够可靠地将其与相应的释放关联,因此它可以生成为这些编译时已知值定制的代码(例如内联和循环展开),但是复杂的使用(以及处理外部输入时)编译器可能需要在运行时存储和检索#个元素:#element计数器可能会有足够的空间 - 例如 - 在为数组内容返回的地址之前或之后,删除[]知道这个约定。在实践中,编译器可能会选择始终在运行时处理此问题,只是为了简化一致性。存在其他运行时可能性:例如#elements可能来自对特定内存池的一些洞察,从中可以满足分配,并与对象大小相结合。

标准不提供程序化访问,以确保实现在他们可能使用的优化(速度和/或空间)中不受约束。

(内存位置的大小可能大于所请求数量的元素所需的确切大小 - 内存分配库会记住该大小,该内存分配库可能是独立于C ++编译器的黑盒库)。

答案 1 :(得分:6)

内存分配器会记住分配的大小,但不会将其提供给用户。在带有malloc的C和带有new的C ++中都是如此。

无法获得“内存位置的大小”。如果你这样做

int *a = new int[N];
std::cout << sizeof(a);

你会发现它打印sizeof(int *),这是不变的(对于给定的平台)。

答案 2 :(得分:5)

C ++中常见的方法是使用std::vector而不是数组。

std::vector使用方法size返回元素数。

如果可能,您应该尽可能使用std::vector而不是数组。

答案 3 :(得分:4)

原因是C语言不公开此信息,尽管可能可用于特定实现。 (实际上,对于C ++中的数组new [],必须跟踪大小以调用每个对象的析构函数 - 但是这是如何完成的取决于特定的编译器。)

这种不公开的原因是编译器 - 编写者和平台实现者在如何实现可变大小的内存分配方面有更多的自由。一般情况下也没有必要知道这些信息,因此要求每个C平台提供此信息是没有意义的。

另外,一个实际的原因(对于malloc等)是他们没有给你你要求的:如果你问malloc 30个字节的内存,它会最有可能给你32个字节(或其他一些更大的分配粒度)。因此,内部可用的唯一信息是32个字节,作为程序员,您对此信息的用处不多。

答案 4 :(得分:3)

两件事情不利于它

  1. 首先,数组和指针是可互换的 - 数组对其长度没有任何额外的了解。 (*所有聪明的评论员都试图评论数组和指针之间的基本差异应该注意到这一点在这一点上没有任何区别;)*)

  2. 其次,因为知道分配的大小是堆的业务,并且堆不会暴露任何标准的方式来发现分配的大小。

  3. 然而,Symbian确实有一个AllocSize()函数,您可以从中导出数组中有多少元素。 然而,有时分配比请求的要大,因为它以字对齐的块管理内存。

答案 5 :(得分:3)

您可以轻松地创建一个类来跟踪分配计数。

我们不知道长度的原因是因为总是是一个实现细节(afaik)。编译器知道元素的对齐方式,而abi也会影响元素的实现方式。

例如,itanium 64 abi将cookie(元素计数)存储在分配的前导字节中(特别是非POD),然后在必要时填充对象的自然对齐。然后返回(从new[])第一个可用元素的地址,而不是实际分配的地址。因此涉及到一堆非便携式簿记。

包装类是管理它的简单方法。

实际上编写分配器,覆盖对象 :: new / delete,放置运算符以及查看这些是如何组合在一起实际上是一个有趣的练习(尽管如果你想要分配器,这不是一个特别简单的练习用于生产代码)。

简而言之,我们不知道内存分配的大小,并且更加努力地在多个平台上一致地计算出分配大小(以及其他必要变量),而不是使用自定义模板类。指针和size_t

此外,无法保证分配器完全分配所请求的字节数(因此,如果根据分配大小确定计数,则计数可能是错误的)。如果你通过malloc接口,你应该能够找到你的分配......但对于任何非平凡的情况,这仍然不是非常有用,可移植或安全。

更新

@Default创建自己的界面有很多原因。正如Tony所说,std::vector是一个众所周知的实现。这种包装器的基础很简单(接口来自std::vector

/* holds an array of @a TValue objects which are created at construction and destroyed at destruction. interface borrows bits from std::vector */
template<typename TValue>
class t_array {
    t_array(const t_array&); // prohibited
    t_array operator=(const t_array&); // prohibited
    typedef t_array<TValue>This;
public:
    typedef TValue value_type;
    typedef value_type* pointer;
    typedef const value_type* const_pointer;
    typedef value_type* const pointer_const;
    typedef const value_type* const const_pointer_const;
    typedef value_type& reference;
    typedef const value_type& const_reference;

    /** creates @a count objects, using the default ctor */
    t_array(const size_t& count) : d_objects(new value_type[count]), d_count(count) {
        assert(this->d_objects);
        assert(this->d_count);
    }

    /** this owns @a objects */
    t_array(pointer_const objects, const size_t& count) : d_objects(objects), d_count(count) {
        assert(this->d_objects);
        assert(this->d_count);
    }

    ~ t_array() {
        delete[] this->d_objects;
    }

    const size_t& size() const {
        return this->d_count;
    }

    bool empty() const {
        return 0 == this->size();
    }

    /* element access */
    reference at(const size_t& idx) {
        assert(idx < this->size());
        return this->d_objects[idx];
    }

    const_reference at(const size_t& idx) const {
        assert(idx < this->size());
        return this->d_objects[idx];
    }

    reference operator[](const size_t& idx) {
        assert(idx < this->size());
        return this->d_objects[idx];
    }

    const_reference operator[](const size_t& idx) const {
        assert(idx < this->size());
        return this->d_objects[idx];
    }

    pointer data() {
        return this->d_objects;
    }

    const_pointer data() const {
        return this->d_objects;
    }

private:
    pointer_const d_objects;
    const size_t d_count;
};

std::vector一样有用,在某些情况下,创建自己的基础会很有用:

  • 制作具有较小界面的对象。极简主义很好。
  • 制作一个不需要分配器的对象。例如:t_array将导致较少的导出符号,以及这些符号的较短名称(通过删除allocator参数)。
  • 制作处理其他const案例的变体。在上面的示例中,通常没有理由更改容器指向的内容。所以上面的t_array使用2个const成员,每个成员确保的变化小于std::vector。一个好的优化者应该利用这些细节。它还可以防止用户意外犯错。
  • 减少构建时间。如果您的需求像t_array一样简单,或者更简单,那么您可以通过使用最小的界面来缩短构建时间。

其他情况:

  • 制作具有更大界面或更多功能的对象
  • 使用其他调试工具制作对象
  • 创建一个可能被子类化的对象(std::vector的大部分实现都不是要子类化的)
  • 制作一个线程安全的对象

答案 6 :(得分:1)

完全符合C语言的“保持简单”理念:你必须在某一点确定数组/缓冲区/所需的大小;所以保持这个价值就是这样。为什么要浪费函数调用来重新获取已有的信息?