62

这个问题的灵感来自一个类似的问题:delete[] 如何“知道”操作数数组的大小?

我的问题有点不同: 有没有办法以编程方式确定 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))。

4

20 回答 20

70

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

另外,考虑这个例子:


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

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

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

于 2008-10-13T15:03:26.007 回答
20

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

当你这样做时: 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,无论在您收到的连续内存块之前的任何内容,都会分配一个保留空间,其中包含有关您获得的内存块的一些信息。

于 2008-10-13T16:07:26.983 回答
19

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

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

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

于 2008-10-13T14:58:18.280 回答
5

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

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);
}
于 2008-10-13T14:56:13.570 回答
4

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

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

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

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

也许有可能获得这样的大小:

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

或者地狱没有这些。

于 2008-10-13T15:37:13.583 回答
3

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

哨兵值必须具有一些独特的属性。

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

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

于 2013-05-15T15:15:22.797 回答
3

一些魔法:

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; 
}
于 2013-06-09T22:29:27.630 回答
2

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

量子皮特

于 2008-10-13T14:56:22.527 回答
2

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

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

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

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

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

于 2008-10-13T15:06:36.903 回答
2

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

#include <array>

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

参数是<type, #elements>

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

于 2013-12-07T02:12:35.173 回答
1

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

于 2008-10-13T14:57:23.507 回答
1

你不能,从根本上说:

void foo(int* arr);

int arr[100] = {0};

foo(arr+1); // Calls foo with a pointer to 100-1 elements.

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

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

于 2008-10-13T15:03:48.097 回答
1

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

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

于 2008-10-13T15:06:25.313 回答
1

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

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

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

于 2008-10-13T15:08:00.363 回答
0

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

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

@迪玛,

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

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

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

于 2008-10-15T20:42:53.307 回答
0

编译器无法知道

char *ar = new char[100] 

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

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

于 2009-01-07T11:16:05.707 回答
0

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

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

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

数组的主体是这样的:

int count;
ObjectType* data; //This value is returned when using new[]
于 2012-09-19T20:32:01.363 回答
0

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

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

它打印 100

于 2015-01-12T02:58:27.277 回答
-1

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

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

于 2012-10-26T15:13:20.257 回答