c++ 在哪里存储固定大小数组的大小?
Where does c++ store the size of fix sized array?
此问题是 Declaring array of int
的跟进
考虑以下程序:
#include <iostream>
#include <string>
int main()
{
int x[10];
std::cout << sizeof(x) << std::endl;
int * y = new int [10];
std::cout << sizeof(y) << std::endl;
//delete [] y;
}
sizeof(x)
打印 40(总大小),sizeof(y)
打印 8(指针大小)
看起来很有趣,对我来说 int x[10]
与 y
没有什么不同,只是它位于堆栈中。 c++实际上在哪里存储x
的大小? c++ 是否从堆栈中获取它?或者固定大小的数组在内部被视为具有大小的结构?
你有两种不同的类型:
int x[10];
这声明了 int[10]
的类型。可以看到,数组的大小是类型的一部分。
int * y;
这是一个指向 int 的指针。它的大小将等于您系统上指针的大小。您的系统似乎有 64 位指针(现在很常见)。
这里有一个你可以在 C++17 中做的小技巧:
template<class T>
std::enable_if_t<std::is_pointer_v<T>> foo(T ptr)
{
std::cout << "Called foo for a pointer type" << std::endl;
}
template<class T, int N>
void foo(T (&arr)[N])
{
std::cout << "Called foo for an array type of size " << N << std::endl;
}
我们定义了两个名为 foo
的函数。第一个使用类型特征只为指针类型启用自身,第二个用于数组类型(通过引用)。
我们可以从 main:
int x[10];
int * y = nullptr;
foo(x);
foo(y);
并得到如下所示的输出:
Called foo for an array type of size 10
Called foo for a pointer type
Runnable code
编辑: 很好地说明了分配给 y
的数组的大小,我也将加入一小部分。虽然它确实是一个实现细节,但常见的是内存分配器的实现(如 malloc
或 new
)在幕后会将此值写入它分配的内存块的开头。
这样,当您调用 delete
时,您不必知道要删除多少字节;释放器(free
或 delete
)可以检查内存块的起始处有多大。因此,例如 1 个字节的分配实际上可能分配超过 1 个字节。 (同样,这只是可以做到的一种方式)
不需要存储数组的大小,编译器本身知道它有多大,因为它是类型的一部分。
当您像示例中的 y
那样动态分配数组时,数组的大小 确实 需要存储在某处,以便 delete[]
可以做正确的事。但这是程序无法访问的实现细节。
数组的大小在编译的 C++ 代码(汇编代码)中显而易见。这等同于 sizeof()
在编译时如何求值。
因此,它不存储它,编译器知道数组的大小。
当您动态分配内存时,编译器再次知道数组的大小并记住它(这是实现定义的位置和方式)。结果,当你调用delete[]
时,你不需要指定数组的大小,编译器知道。
此问题是 Declaring array of int
的跟进考虑以下程序:
#include <iostream>
#include <string>
int main()
{
int x[10];
std::cout << sizeof(x) << std::endl;
int * y = new int [10];
std::cout << sizeof(y) << std::endl;
//delete [] y;
}
sizeof(x)
打印 40(总大小),sizeof(y)
打印 8(指针大小)
看起来很有趣,对我来说 int x[10]
与 y
没有什么不同,只是它位于堆栈中。 c++实际上在哪里存储x
的大小? c++ 是否从堆栈中获取它?或者固定大小的数组在内部被视为具有大小的结构?
你有两种不同的类型:
int x[10];
这声明了 int[10]
的类型。可以看到,数组的大小是类型的一部分。
int * y;
这是一个指向 int 的指针。它的大小将等于您系统上指针的大小。您的系统似乎有 64 位指针(现在很常见)。
这里有一个你可以在 C++17 中做的小技巧:
template<class T>
std::enable_if_t<std::is_pointer_v<T>> foo(T ptr)
{
std::cout << "Called foo for a pointer type" << std::endl;
}
template<class T, int N>
void foo(T (&arr)[N])
{
std::cout << "Called foo for an array type of size " << N << std::endl;
}
我们定义了两个名为 foo
的函数。第一个使用类型特征只为指针类型启用自身,第二个用于数组类型(通过引用)。
我们可以从 main:
int x[10];
int * y = nullptr;
foo(x);
foo(y);
并得到如下所示的输出:
Called foo for an array type of size 10
Called foo for a pointer type
Runnable code
编辑:y
的数组的大小,我也将加入一小部分。虽然它确实是一个实现细节,但常见的是内存分配器的实现(如 malloc
或 new
)在幕后会将此值写入它分配的内存块的开头。
这样,当您调用 delete
时,您不必知道要删除多少字节;释放器(free
或 delete
)可以检查内存块的起始处有多大。因此,例如 1 个字节的分配实际上可能分配超过 1 个字节。 (同样,这只是可以做到的一种方式)
不需要存储数组的大小,编译器本身知道它有多大,因为它是类型的一部分。
当您像示例中的 y
那样动态分配数组时,数组的大小 确实 需要存储在某处,以便 delete[]
可以做正确的事。但这是程序无法访问的实现细节。
数组的大小在编译的 C++ 代码(汇编代码)中显而易见。这等同于 sizeof()
在编译时如何求值。
因此,它不存储它,编译器知道数组的大小。
当您动态分配内存时,编译器再次知道数组的大小并记住它(这是实现定义的位置和方式)。结果,当你调用delete[]
时,你不需要指定数组的大小,编译器知道。