3

所以这似乎是一个被广泛回答的问题,但我更感兴趣的是两者之间究竟发生了什么不同的内部结构。

除了第二个示例不仅创建了内存,还创建了指向内存的指针这一事实之外,当以下情况发生时,内存中会发生什么:

char a[5];
char b* = new char[5];

和我为什么问这个问题更直接相关,我怎么能做

const int len = 5;
char* c = new char[len];

但不是

const int len = 5;
char d[len]; // Compiler error

编辑应该提到我在 VC++ 上遇到了这个编译器错误(去图......)

1>.\input.cpp(138) : error C2057: expected constant expression
1>.\input.cpp(138) : error C2466: cannot allocate an array of constant size 0
1>.\input.cpp(138) : error C2133: 'd' : unknown size

编辑 2:应该发布我正在使用的确切代码。当使用运行时值计算动态分配数组的恒定长度时,会产生此错误。

假设random(a,b)返回一个和intab

const int len1 = random(1,5);
char a[len1]; // Errors, since the value
              // is not known at compile time (thanks to answers)

然而

const int len2 = 5;
char b[len2]; // Compiles just fine
4

8 回答 8

16

不同之处在于数组的生命周期。如果你写:

char a[5];

那么数组具有它定义的块的生命周期(如果它是在块范围内定义的)、包含它的类对象的生命周期(如果它是在类范围内定义的)或静态生命周期(如果它是在命名空间范围内定义的)。如果你写:

char* b = new char[5];

, 那么数组有任何你想给它的生命周期——你必须明确地终止它的生命周期:

delete [] b;

关于你的最后一个问题:

int const len = 5;
char d[len];

是完全合法的,应该编译。有区别的地方:

int len = 5;    //  _not_ const
char d[len];    //  illegal
char* e = new char[len];    //  legal

造成差异的原因主要是编译器技术和历史之一:在早期,编译器必须知道长度才能将数组创建为局部变量。

于 2012-06-27T13:04:01.993 回答
6

当以下情况发生时,内存中会发生什么:

char a[5]; 
char *b = new char[5];

假设一个典型但有些简化的 C++ 实现,并且上面的代码出现在一个函数中:

char a[5];

堆栈指针移动 5 个字节,形成 5 个字节的空间。该名称a现在指的是 5 个字节的内存块。

char *b = new char[5];

堆栈指针移动sizeof(char*), 为 腾出空间b。一个函数被调用,它从一个叫做“自由存储”的东西中分配 5 个字节,基本上它从操作系统获得的一大块内存中分割出 5 个或更多字节,并做一些簿记以确保当您使用 释放这些字节delete[],它们将可用于将来的分配以重新使用。它返回分配的 5 字节块的地址,该地址存储在堆栈上的空间中b

第二个比第一个工作更多的原因是分配的对象new可以按任何顺序删除。局部变量(又名“堆栈上的对象”)总是以与创建相反的顺序被销毁,因此需要较少的簿记。在普通可破坏类型的情况下,实现只需将堆栈指针沿相反方向移动相同的距离。

为了消除我所做的一些简化:堆栈指针并没有真正为每个变量移动一次,可能它只在函数中所有变量的函数入口处移动一次,在这种情况下,所需的空间至少为sizeof(char*) + 5. 堆栈指针或各个变量可能存在对齐要求,这意味着它不会移动所需的大小,而是移动一些四舍五入的量。实现(通常是优化器)可以消除未使用的变量,或者为它们使用寄存器而不是堆栈空间。可能还有一些我没有想到的事情。

const int len1 = random(1,5);

语言规则相当简单:数组的大小必须是一个常量表达式。如果一个const int变量在同一个 TU 中有一个初始化器,并且初始化器是一个常量表达式,那么变量名可以用在常量表达式中。random(1,5)不是常量表达式,因此len1不能在常量表达式中使用。5是一个常数表达式,所以len2很好。

What the language rule is there for, is to ensure that array sizes are known at compile time. So to move the stack, the compiler can emit an instruction equivalent to stack_pointer -= 5 (where stack_pointer will be esp, or r13, or whatever). After doing that, it still "knows" exactly what offsets every variable has from the new value of the stack pointer -- 5 different from the old stack pointer. Variable stack allocations create a greater burden on the implementation.

于 2012-06-27T13:12:04.033 回答
2

当以下情况发生时,内存中会发生什么

char a[5];
char b* = new char[5];

char a[5]在堆栈内存上分配 5 个字符。
new char[5]在堆内存上分配 5 个字符。


更直接地与我问这个问题的原因有关,我怎么能这样做:

const int len = 5;
char* c = new char[len];

但不是

const int len = 5;
char d[len]; // Compiler error

两者都为我成功编译。

于 2012-06-27T13:03:49.810 回答
1

在 C++ 中,堆栈中不能有动态数组。C99 有这个特性,但 C++ 没有。

当您声明char d[ len ]您正在分配空间时stack。当你这样做时,你在堆上char *c = new char[ len ]分配空间。

堆有它的管理器,可以分配可变数量的内存。在 C++ 中,堆栈必须由常量表达式值分配,因此编译器有很多优化空间。编译器知道以这种方式将在给定上下文上花费多少空间,并且能够预测堆栈帧。使用动态数组是不可能的,所以语言工作人员决定禁止它(至少在 C++11 之前)。

于 2012-06-27T13:05:04.343 回答
0

第三对行应该可以工作,这不应该是编译器错误。那里一定有其他事情发生。

前两个示例之间的区别在于,for 的内存char a[5];将自动被释放,而char* b = new char[5];分配的内存在您明确释放之前不会被释放。一旦特定变量超出范围,就不能使用第一种方式分配的数组,因为它的析构函数被自动调用并且内存可以被覆盖。对于使用创建的数组new,您可以传递指针并在原始变量的范围之外自由使用它,甚至可以在创建它的函数之外使用delete它。

你不能做的是:

int a = 5;
int *b = new int[a];

对于动态内存分配,大小必须在编译时已知。

于 2012-06-27T13:01:53.303 回答
0

您的数组在堆栈上分配;这意味着,一旦程序被编译,它就知道它必须保留 5 个字节来存储 a 的字符。相反,b 只是被声明为一个指针,它的内容将在运行时在堆上分配,如果内存太稀缺,这可能会失败。最后,由于是新的,它必须在某个时候被删除,否则你会泄漏内存。

于 2012-06-27T13:02:25.863 回答
0

当你使用 new 时,你是从自由存储/堆中分配内存,你需要自己释放它。此外,定位空闲内存实际上可能需要一些时间,释放它也一样。

当您不使用 new 时,您的内存将保留在堆栈上,并被隐式分配和释放。即,当您输入一个函数时,调用堆栈将仅扩展所有局部变量的大小(至少在概念上-例如,某些变量可以完全存在于寄存器中),并且在您离开该函数时它只会递减。

当您像上一个示例一样在堆栈上分配具有动态大小的变量时,这意味着您在进入函数范围时需要一些额外的信息。具体来说,需要保留的空间量取决于函数输入。现在,如果可以在函数的开头确定上下文,那么一切都很好——这大概是 C99 中允许这样做的原因——但是如果你有一个变量的大小,你只知道函数中间的值,你最终会添加“假”函数调用。再加上 C++ 的作用域规则,这可能会变得很麻烦,所以从概念上讲,让 C++ 作用域通过 std::vector 来处理这个问题在概念上要容易得多。

于 2012-06-27T13:04:34.407 回答
0

char a[5]sizeof(char)当将这些字节分配给堆内存时,分配 5个字节给堆栈new char[5]内存。分配给堆栈内存的字节也保证在作用域结束时被释放,这与堆内存不同,您应该显式释放内存。

char d[len]应该允许,因为变量被声明为 const ,因此编译器可以轻松地使代码将这些字节分配给堆栈内存。

于 2012-06-27T13:07:44.067 回答