【问题标题】:Determine the size of a C++ array programmatically?以编程方式确定 C++ 数组的大小?
【发布时间】:2010-09-16 21:43:07
【问题描述】:

这个问题的灵感来自一个类似的问题:How does delete[] “know” the size of the operand array?

我的问题有点不同:有没有办法以编程方式确定 C++ 数组的大小?如果不是,为什么?我见过的每个接受数组的函数也需要一个整数参数来指定它的大小。但正如链接问题所指出的,delete[] 必须知道要释放的内存大小。

考虑一下这个 C++ 代码:

int* arr = new int[256];
printf("Size of arr: %d\n", sizeof(arr));

这将打印“Size of arr: 4”,这只是指针的大小。有一些打印 256 的函数会很好,但我认为 C++ 中不存在这样的函数。 (同样,问题的一部分是为什么它不存在。)

澄清:我知道如果我在堆栈而不是堆上声明数组(即“int arr[256];”),sizeof 运算符将返回 1024(数组长度 * sizeof( int))。

【问题讨论】:

  • 实际上,如果您在堆栈上分配数组,则 sizeof 运算符将返回 1024——即 256(元素的数量)* 4(单个元素的大小)。 (sizeof(arr)/sizeof(arr[0])) 将给出结果 256。
  • 谢谢,我忽略了这一点,因为我实际上在我的测试代码中使用了 char[](并且 sizeof(char) == 1)
  • 虽然这只是假设性的 - 因为它不起作用 - 我必须指出你应该写 printf("Size of arr: %d\n", sizeof(*arr)); 而不是 printf("Size of arr: %d\n", sizeof(*arr)); 因为你希望检索取消引用指针的大小。

标签: c++ arrays pointers memory-management


【解决方案1】:

delete [] 确实知道分配的大小。但是,该知识存在于运行时或操作系统的内存管理器中,这意味着编译器在编译期间无法使用它。而sizeof() 不是一个真正的函数,它实际上是由编译器评估为一个常量,这对于动态分配的数组是无法做到的,其大小在编译期间是未知的。

另外,考虑这个例子:


int *arr = new int[256];
int *p = &arr[100];
printf("Size: %d\n", sizeof(p));

编译器如何知道p 的大小是多少?问题的根源在于 C 和 C++ 中的数组不是一流的对象。它们衰减为指针,编译器或程序本身无法知道指针是指向由new 分配的内存块的开头,还是指向单个对象,还是指向中间的某个位置new分配的一块内存。

其中一个原因是 C 和 C++ 将内存管理留给程序员和操作系统,这也是它们没有垃圾收集的原因。 newdelete 的实现不是 C++ 标准的一部分,因为 C++ 旨在用于各种平台,这些平台可能以非常不同的方式管理它们的内存。如果您正在为在最新 Intel CPU 上运行的 windows 机器编写文字处理器,则可能让 C++ 跟踪所有分配的数组及其大小,但是当您编写运行在DSP。

【讨论】:

  • C++ 中绝对有数组。否则你会解释为什么用这个“char x[4]; size_t sz = sizeof(x);” 'sz' 将被分配 4?
  • Dima,绝对有数组。数组不同于指针。可悲的是,许多老师混淆了它并告诉他们的学生他们“只是”指针。不,他们不是。你怎么解释这个: char const**s = &"bar";不编译? [...]
  • litb,原因 char const **s = &"bar";不编译是“bar”是一个常量而不是左值,所以你不能取它的地址。它与 int *p = &5; 相同。也不会编译。
  • 很清楚,但几乎所有事情都是错误的。已经存在 sizeof 是运行时而不是编译时的情况,数组确实存在,并且有办法让实现知道所有数组的大小。即使是 DSP必须保留分配的大小信息。
  • void foo(int *a); 接受一个指针,void foo(int (&a)[5]); 接受一个数组。数组名称衰减为指针,这很糟糕,但这并不意味着数组和指针是一回事。
【解决方案2】:

不,在标准 C++ 中没有办法做到这一点。

据我所知,没有很好的理由不这样做。可能,大小被认为是实现细节,最好不要暴露。请注意,当您说 malloc(1000) 时,不能保证返回的块是 1000 字节 --- 只有它是至少 1000 字节。很可能大约是 1020(1K 减去 4 字节的开销)。在这种情况下,“1020”大小是运行时库要记住的重要大小。当然,这会在实现之间发生变化。

这就是标准委员会添加 std:vector 的原因,它确实会跟踪它的确切大小。

【讨论】:

  • 需要注意的一点是,new[] 也确实存储了请求项的数量,以便为数组调用正确数量的构造函数和析构函数。存储它的位置又是特定于实现的。不包括获得它的方法的原因超出了我的理解。
  • 我认为“很好的理由”是数组根本不是对象。数组只是一个原始内存块。大小是内存管理数据,而不是对象数据。您可以编写一个 Array 类来跟踪内存和大小,但您可以只使用 std::vector 而不用担心它。
  • 啊哈...当然。 int* 无法知道它指向的数组是新数组还是本地数组或数组中间的某个位置。
  • @Herms: std::string[10] 绝对不是原始内存,而是一个数组。
  • workmad3,可能仅适用于具有非平凡析构函数的项目和具有用户定义的运算符 delete 的类型,该运算符希望知道大小。对于其他任何事情,不存储数字就足够了
【解决方案3】:

其实有一种方法可以确定大小,但它不是“安全的”,并且会因编译器而异...... 所以根本不应该使用它

当你这样做时: int* arr = new int[256];

256 无关紧要,假设这种情况下为 1024,您将得到 256*sizeof(int),该值可能存储在 (arr - 4)

所以给你“项目”的数量

int* p_iToSize = arr - 4;

printf("项目数%d", *p_iToSize / sizeof(int));

对于每个 malloc、new,无论在您收到的连续内存块之前的任何内容,都会分配一个保留空间,其中包含有关您获得的内存块的一些信息。

【讨论】:

  • 不过,这实际上回答了问题。
  • 很有趣,:) 作为额外的 2 美分,您可以重载“new”并根据需要实现内存管理,您可以像 joao 所描述的那样拥有它,或者将每个指针与其对应的映射存储大小...总之有很多疯狂的方法可以实现,但我不会使用它们:p
  • char 数组呢? char * arr = new char[100];
【解决方案4】:

处理此问题的常用方法是使用向量

int main()
{
   std::vector<int> v(256);
   printf("size of v is %i capacity is %i\n", sizeof(int) * v.size(), sizeof(int) * v.capacity());
}

或预定义尺寸

const int arrSize = 256;
int main()
{
    int array[arrSize];
    printf("Size of array is %i", sizeof(int) * arrSize);
}

【讨论】:

  • sizeof(int) * arrSize 和 malloc('sizeof(int) * arrSize') 一样不是吗??
【解决方案5】:

C++ 决定添加 new 来执行类型安全的 malloc,而不是 new 必须知道两个大小为 e 的元素数才能调用 ctors,所以 delete 才能调用 dtors。在早期,您必须通过实际传递来删除您传递给新对象的数字。

string* p = new string[5];
delete[5] p;

但是他们认为如果使用 new[] 数字的开销很小。所以他们决定 new[n] 必须记住 n 并将其传递给删除。主要有三种实现方式。

  1. 保留一个大小指针的哈希表
  2. 直接写在向量附近
  3. 做一些完全不同的事情

也许可以得到这样的尺寸:

size_t* p = new size_t[10];
cout << p[-1] << endl;
// Or
cout << p[11] << endl;

或者这些都没有。

【讨论】:

    【解决方案6】:

    根据您的应用程序,您可以在数组末尾创建一个“哨兵值”。

    标记值必须具有一些独特的属性。

    然后,您可以处理数组(或进行线性搜索)以查找标记值,并在执行过程中计数。一旦达到哨兵值,您就有了数组计数。

    对于简单的 C 字符串,终止符 \0 是标记值的示例。

    【讨论】:

      【解决方案7】:

      一些魔法:

      template <typename T, size_t S>
      inline
      size_t array_size(const T (&v)[S]) 
      { 
          return S; 
      }
      

      这就是我们在 C++11 中的做法:

      template<typename T, size_t S>
      constexpr 
      auto array_size(const T (&)[S]) -> size_t
      { 
          return S; 
      }
      

      【讨论】:

      • 非常有用和漂亮的解决方案。只有一件事:我会使用 size_t 作为第二个模板参数。
      【解决方案8】:

      那是因为你的变量 arr 只是一个指针。它保存着内存中特定位置的地址,但对它一无所知。您将其声明为 int*,这为编译器提供了一些指示,说明当您增加指针时要执行的操作。除此之外,您可能指向数组的开头或结尾,或者指向堆栈或无效内存。 但我同意你的看法,不能调用 sizeof 很烦人:)

      量子皮特

      【讨论】:

      • 但系统不知何故知道数组的大小,否则“delete[] arr”将不起作用。
      • 嗯,系统在运行时就知道了,但 sizeof 是编译时调用。
      • delete[] arr 会知道数组的大小,但如果数组是在堆栈上分配的,则不会。
      【解决方案9】:

      在 C++ 中没有可移植的方法来确定仅给定指针的动态分配数组的大小。 C++ 变得非常灵活并赋予用户权力。例如,该标准没有定义内存分配器必须如何工作,例如通过添加所需的大小标题。不需要标头可以提供更大的灵活性。

      作为一个例子,考虑一个实现为 char * 数组的字符串。通常使用指向数组中间的指针来挑选子字符串。例如,请参阅标准 C 库中的 strtok 函数。如果需要在每个数组之前嵌入一些标头,则需要在子字符串之前删除数组的某些部分。

      另一种处理头的方法是将数组头放在一个内存块中,并让它们指向其他地方的原始数组内存。在许多情况下,这将需要对每个引用进行两次指针查找,这将对性能造成很大的拖累。有一些方法可以克服这些缺陷,但它们增加了复杂性并降低了实现的灵活性。

      std::vector 模板是我最喜欢的保持数组大小绑定到数组本身的方式。

      C 是具有更好语法的可移植汇编语言。

      【讨论】:

      • 如果数组有标题,strtok 的工作方式完全相同,因为 strtok 采用指向字符的指针,而不是数组。
      【解决方案10】:

      现在有std::array,一个高效的编译时包装器,围绕一个固定大小的数组:

      #include <array>
      
      int main (int argc, char** argv)
      {
          std::array<int, 256> arr;
          printf("Size of arr: %ld\n", arr.size());
      }
      

      参数为&lt;type, #elements&gt;

      您还可以获得其他一些细节,例如迭代器、empty() 和 max_size()。

      【讨论】:

        【解决方案11】:

        很遗憾,这是不可能的。在 C 和 C++ 中,程序员有责任记住数组的长度,因为数组长度不会存储在任何地方。 Delete[] 和 free() 确实记住了已分配块的大小,但它们分配的内存可能比请求的多,因此它们存储已分配内存块大小的内部数据结构可能无法为您提供数组的确切大小。

        请注意,C++ STL 向量(基本上是包含在具有一些辅助函数的类中的数组)会存储数组的长度,因此如果您确实需要此功能,则可以使用向量。

        【讨论】:

          【解决方案12】:

          一般来说,不会。 C 和 C++ 中的数组只是没有附加簿记信息的内存块。如果不将数组的长度存储在内存中并增加开销,一般情况下是不可能的。

          静态分配的数组有一个例外。例如,如果您声明:int a[50],那么sizeof(a) 将起作用。这是可能的,因为 [50] 是数组的静态类型的一部分:编译器知道它。 sizeof 在编译时被解释。

          但是,如果您创建一个指针:int *p = a,那么sizeof(p) 将返回您提到的指针的大小,而不是数组的大小,因为编译器不知道 p 指向什么。

          【讨论】:

            【解决方案13】:

            你不能,从根本上说:

            void foo(int* arr);
            
            int arr[100] = {0};
            
            foo(arr+1); // Calls foo with a pointer to 100-1 elements.
            

            C++ 数组只不过是存储在连续内存区域中的对象的集合。由于它们之间没有空洞(填充是 inside 对象),您可以通过简单地递增指针来找到数组的下一个元素。在 CPU 级别,这是一个简单的调整。 C++ 只插入一个 sizeof(element) 乘数。

            请注意,实现可能会选择实现包含数组边界的“胖指针”。它们需要两倍大,因为您需要链接到某种“数组绑定描述符”。作为副作用,在此类实现中,您可以调用delete [] (1+new int[5]);

            【讨论】:

              【解决方案14】:

              不,没有任何方法可以做到这一点,您必须跟踪它的外部大小。像 std::vector 这样的类会为你做这件事。

              【讨论】:

                【解决方案15】:

                编译器无法知道

                char *ar = new char[100] 
                

                是一个 100 个字符的数组,因为它不会在内存中创建一个实际的数组,它只是在内存中创建一个指向 100 个未初始化字节的指针。

                如果您想知道给定数组的大小,只需使用 std::vector。 std::vector 只是一个更好的数组。

                【讨论】:

                  【解决方案16】:

                  有没有办法以编程方式确定 C++ 数组的大小?如果不是,为什么?

                  1. 不,除非您自己跟踪。
                  2. 因为如果编译器不必将这些信息告诉除自己之外的任何人,它对编译器的约束就会更少。这是否可取还有待商榷。

                  【讨论】:

                    【解决方案17】:

                    @迪玛,

                    编译器如何知道 p 的大小?

                    编译器必须知道 p 的大小;否则,它无法实现delete[]。编译器不需要告诉其他人它是如何计算出来的。

                    为了验证这一点的有趣方法,请将operator new[] 返回的指针与new[] 返回的指针进行比较。

                    【讨论】:

                      【解决方案18】:

                      当您创建数组指针时(使用指向指针的模板创建包装器)您不能,但当您创建对象数组时, 你可以像这样得到数组的大小:

                      char* chars=new char[100];
                      printf("%d",*((int*)chars-1));
                      

                      delete[] 函数需要解构其中的所有对象。要做到这一点,new[] 关键字将元素的数量放在所有数组的后面。

                      数组的主体是这样的:

                      int count;
                      ObjectType* data; //This value is returned when using new[]
                      

                      【讨论】:

                        【解决方案19】:

                        我这样做的方法是将数组的大小除以第一个元素的大小

                        int intarray[100];
                        printf ("Size of the array %d\n", (sizeof(intarray) / sizeof(intarray[0]));
                        

                        打印 100

                        【讨论】:

                        • 请重新阅读问题(最后一行),作者很清楚这一点。这不是被问到的。
                        【解决方案20】:

                        您可以只创建一个额外的数组元素,然后应用将存储在数组中的最不可能的数字。然后,您可以通过传递该数字通过某个函数确定元素的数量。

                        如果在创建时声明和初始化数组,则可以对其进行扫描,然后生成一个与数组的任何元素都不匹配的数字。但是,如果您随后修改其中一个元素,您将不知道该元素是否存储与最后一个元素相同的值,因此您必须生成一个新数字来存储在最后一个元素中。通过所有这些,您不妨将创建时的元素总数存储在变量中。如果您只在函数中使用数组,则可能会出现这种情况。

                        【讨论】:

                        • 这既模糊又不切实际,同时也干扰了被测量的东西。不是一个严肃的答案。
                        • 在一个人的数据中引入一个魔术字符串(“最不可能的数字”)是一种反模式,这是有原因的。当这个最不可能的数字由于程序员无法预见的原因而实际发生时会发生什么?
                        猜你喜欢
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 2019-11-18
                        • 2011-11-21
                        • 1970-01-01
                        • 2010-11-13
                        • 1970-01-01
                        • 1970-01-01
                        相关资源
                        最近更新 更多