以编程方式确定C ++数组的大小?

问题描述 投票:60回答:20

这个问题的灵感来自一个类似的问题: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))。

c++ arrays pointers memory-management
20个回答
65
投票

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。


1
投票

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

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


1
投票

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

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

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


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(元素)乘数。

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


1
投票

不,没有办法做到这一点,你必须跟踪外部有多大。像std::vector这样的课程为你做这个。


1
投票

现在有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()。


0
投票

编译器无法知道

char *ar = new char[100] 

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

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


0
投票

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

  1. 不,除非你自己跟踪它。
  2. 因为如果编译器不必告诉除此之外的任何人关于该信息,则它会减少编译器的约束。这是否可取是一个有争议的问题。

0
投票

@Dima,

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

编译器必须知道p的大小;否则,它无法实现delete[]。编译器不需要告诉其他人如何解决这个问题。

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


0
投票

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

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

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

数组的主体是这样的:

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

0
投票

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

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

它打印100


18
投票

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

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

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


-1
投票

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

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


16
投票

那么实际上有一种方法可以确定大小,但它不是“安全的”,并且与编译器和编译器不同......因此它根本不应该被使用。

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

256是无关紧要的,你会得到256 * sizeof(int)假设这个情况1024,这个值可能存储在(arr - 4)

那么给你一些“项目”

int * p_iToSize = arr - 4;

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

对于每个malloc,new,无论你收到的连续内存块之前的什么,都会分配一个空间,其中包含有关你给出的内存块的一些信息。


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);
}

3
投票

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

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

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

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

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

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

或者他们都没有。


3
投票

根据您的应用程序,您可以在阵列末尾创建“标记值”。

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

然后,您可以处理数组(或进行线性搜索)以获取sentinel值,并随时计算。达到哨兵值后,您的阵列数量就会增加。

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


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; 
}

2
投票

那是因为你的变量arr只是一个指针。它在内存中保存特定位置的地址,但不知道任何有关它的信息。您将其声明为int *,这为编译器提供了一些指示,指示在递增指针时要执行的操作。除此之外,您可能指向数组的开头或结尾,或指向堆栈或无效内存。但我同意你的意见,不能调用sizeof非常讨厌:)

QuantumPete


2
投票

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

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

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

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

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

© www.soinside.com 2019 - 2024. All rights reserved.