【问题标题】:Why does C++ array created with new behave differently to C style array?为什么用 new 创建的 C++ 数组的行为与 C 样式数组不同?
【发布时间】:2014-08-01 12:58:53
【问题描述】:

我正在自学 C++,因此一直在编写一些示例代码来真正加深我对指针和数组的理解。

我写了这个:

int myints[] = {20, 40, 60, 80, 100}; 
// C style array? should be stored on stack? is myint's type pointer to int or an array of int? how does it differ from myotherints?

int* myotherints = new int[5]{20, 40, 60, 80, 100}; // new always returns pointer, is this a C++ style array?     
// does this pointer get created on stack while the elements themselves are created in free heap?

int j = 5; // should be stored on stack

cout << "myints: " << myints << endl; // decays to pointer, shows address array myints is stored at
cout << "*myints: " << *myints << endl; // myints decays to pointer and is dereferenced to return value stored at start of array myints
cout << "myints[0]: " << myints[0] << endl; // [] dereferences and returns value for element 0 (20)
cout << "myotherints: " << myotherints << endl; // some value?? this is totally unlike the others, why? what is this?
cout << "*myotherints: " << *myotherints << endl; // dereferences pointer myotherints to get address that holds value 20 for first element
cout << "myotherints[0]: " << myotherints[0] << endl; // [] dereferences pointer to get address that holds value 20 for first element
cout << "j: " << j << endl << endl; // 5, sure

cout << "&myints: " << &myints << endl; // array behaving as pointer, gives address of myints[0]
cout << "&myints[0]: " << &myints[0] << endl; // array behaving as pointer, gives address of myints[0]
cout << "&myotherints: " << &myotherints << endl; // address of myotherints, is this where the pointer to the array is stored?
cout << "&myotherints[0]: " << &myotherints[0] << endl; // [] dereferences the pointer that myotherints points to and returns element 0
cout << "&j: " << &j << endl; // address of j

/*
myints: 0x7fff096df830 <-- this makes sense to me, array decays to pointer to give first element address
*myints: 20            <-- this makes sense to me, dereference first element address for value
myints[0]: 20          <-- [] dereferences implicitly, returns value from pointer

myotherints: 0x2308010 <-- myotherints is a pointer to an array of ints, but its address is much lower compared to myints and j, why is that?
*myotherints: 20       <-- getting the value from above address returns 20
myotherints[0]: 20     <-- [] dereferences to address pointed to by pointer myotherints, returns value

j: 5

&myints: 0x7fff096df830      <-- same as below
&myints[0]: 0x7fff096df830   <-- same as above, meaning *myints and myints[0] are the same thing, this address

&myotherints: 0x7fff096df828 <-- how can the pointer to myotherints array be stored here when dereferencing it (*) returns 20 and...
&myotherints[0]: 0x2308010   <-- dereferencing this address with [] also returns 20, yet they are different memory locations unlike myints

&j: 0x7fff096df824 
*/

说 myints 是“C 样式数组”而 myotherints 是“C++ 样式数组”是真的吗?

如果我理解正确的话,myotherints 是一个指针,而 myints 是一个数组,大多数时候它的行为就像一个指针?因此,虽然你可以用 myints 做指针的事情,但有时它的行为不像指针,即使用 & 来显示它的地址。这意味着 myints 与指针的类型不同。它的类型是“整数数组”吗?

myints(myints 的 事物 存储在哪里,而不是其数组中的值),如果它总是自动取消对数组存储位置的引用,我该如何显示它的地址?使用 C++ 样式的新数组返回的指针?

这些在内存中是否以功能不同的方式表示?

任何可以真正巩固我的理解的文档提示或说明将不胜感激。谢谢!

【问题讨论】:

  • 不,关于myotherints 的唯一远程C++ 类似的事实是您使用new 来初始化它。你可以很容易地使用malloc。 C++ 方法是使用容器类,例如std::vector
  • "是 [myotherints] 一个 C++ 风格的数组吗?"不,这不对。它是 c 风格的 dynamic 数组(或者更确切地说是指向此类数组的指针),但使用 new[] 运算符分配,这是 c++ 风格的动态分配(malloc 将在 c 中使用)。
  • 谢谢你们。所以这为我澄清了术语,C++ 风格是指使用带有 RAII 的容器。上面的两个数组都是“C-style”,除了第一个只是在编译时确定的存储空间的标签。第二个是动态的,它的元素存储在空闲堆上,因此地址不同。

标签: c++ c arrays pointers


【解决方案1】:

两者都是从 C 继承的东西(正确的 C++ 数组 东西是 std::array),但你混淆了:

  • 第一个是C数组,即具有静态/自动存储持续时间的东西,代表一块内存。该块的大小和“位置”(地址)在编译时确定。

  • 第二个是指针,指向动态分配的内存块。换句话说,您使用指针来存储您向操作系统请求的内存块的地址。这对新手来说很困惑,因为这个东西有时被称为动态数组。与 C 数组的意义不同,但实际上我们以相同的方式使用两者,但用途不同。

关于 C++:

  • C 数组的行为类似于指向内存块的指针,带有一些糖(数组索引等),因此它们总是通过引用传递(因为我们拥有的是按值传递的地址,而不是数组本身),事实上,它们在许多情况下会自动衰减为指针。这些问题使std::array 成为更好的选择,因为它具有正确的值语义并且没有隐式衰减。

  • 在 C++ 手动内存管理中应该避免,您应该使用标准库提供的容器(最著名的std::vector)。 C++ 语言提供了自动、确定性和安全的资源管理功能;包括内存资源。你应该使用这些。手动内存管理仅适用于非常底层的编程和创建您自己的资源管理处理程序。

【讨论】:

  • “C 数组实际上是加了糖的指针”:不,区别更深,尽管在大多数情况下它们看起来像是指向第一个元素的指针。尽管如此,仍有 C++ 技术依赖于这种差异,如果这是真的,数据布局也会完全不同。
  • @Deduplicator 当然它们不仅仅是指针,它们是具有增强语法的真正块(表现得像一个指向块开头的指针)。但是我不想扩展这个话题,只是为了解释OP的“两种数组”之间的区别,以及发生了什么。
  • 如果你不想详细解释也没关系,你可能是对的,那就太多了。不过,请避免过于笼统地概括,以免使您所说的明显错误。
【解决方案2】:

C++ 从 C 得到类型衰减。

在 C 中,很少有方法可以有效地使用整个数组。您不能将它们传递给函数、从函数返回它们、执行 []==+ 或它们上的几乎任何东西,至少直接。

当你看起来很有趣时,数组“衰减”为指向其第一个元素的指针。 (基本上,只要你在除少数情况下将它视为实际数组之外的所有情况下使用它,它都会衰减为指针)。

所以arr[3] 变为(&amp;(first element of arr))[3]*((&amp;(first element of arr))+3)(它们的含义相同)。

当您 return arr; 或将其传递给函数(在 C 中)时,也会发生类似的事情。您的cout &lt;&lt; arr 表示cout.operator&lt;&lt;( arr ),它只是一个函数。好吧,它也可能是operator&lt;&lt;( cout, arr )。在 C++ 中,您可以将对实际数组的引用传递给函数,但这需要一些工作并且在您的示例代码中不会发生。

如果您键入&amp;arr,则不会发生衰减,并且您会得到一个指向整个数组的指针。这很重要,因为指针算法以及其他原因,以及数组数组如何以零开销工作。 (&amp;arr)+1 指向数组的末尾,无论它有多大 -- ((&amp;(arr[0]))+1) 指向数组的第二个元素。

这就是int arr[3]={4,5,6};int arr[]={4,5,6}; 的工作方式(同样的——第二个只是为您确定数字3)。 arr 在这两种情况下都是 int[3] 类型。它很容易衰减到int*,但它的类型是int[3]sizeof(arr)sizeof(int) 的三倍,而不是 sizeof(int*)

当您 new int[3] 时,您不会获得指向 int[3] 的指针,而是会获得指向 int[3] 数组的第一个元素的指针。从某种意义上说,它已经提前腐烂了。这是指针的类型——数组和第一个元素的地址是相同的。但是类型信息(这是一个编译时间概念)不同!

它也存储在免费存储中,而不是自动存储(分别称为堆和堆栈)。但这不是根本区别。

C++ 关于new int[3] 的唯一一点是您使用了new——您可以使用malloc 来获取空闲存储上的数据空间,并以int* 的形式获取指向第一个元素的指针C 也是如此。

【讨论】:

    【解决方案3】:

    听起来你对指针和数组的问题比什么都多,不过我会尝试解决你所有的问题,

    int myints[] = {20,40,60,80,100};
    

    C 风格的数组?

    是的,这就是您声明 C 样式数组的方式

    这是在堆栈上吗?

    是的,整个数组(所有 5 个变量)都位于堆栈上,这意味着当它的块超出范围时,数据本身也会超出范围,通过移动堆栈指针“释放”,这确实但这并不意味着您不能使用 & 运算符创建指向该数组的指针以用于其他函数。

    这与 myOtherInts 有何不同?

    这是栈上的一个值,而 myotherints 是堆上的一个值,栈上有一个指针。

    int* myOtherInts = new int[]{20,40,60,80,100};
    

    这个指针是在栈上创建的,而元素本身是在空闲堆中创建的吗?

    是的,声明int* 意味着您在堆栈上声明一个指针(指针不是 值是在堆栈上创建的),并且当您将该变量分配给new函数返回,(应该是在堆上分配的内存,稍后必须释放)然后你有一个堆栈存储的指针,指向一个堆存储的值。

    cout << "&myotherints: " << &myotherints << endl;
    

    myotherints 的地址,这是存储指向数组的指针的地方吗?

    是的,这会返回内存中哪个位置保存指向实际数据的指针的地址,该地址与实际存储数据的位置不同

    //myotherints: 0x2308010 <-- myotherints is a pointer to an array of ints, but its address is much lower compared to myints and j, why is that?
    

    您在此处打印的值是存储数据的地址,该地址与存储指针的位置不同。该值低得多的原因是因为该内存所在的地址由堆,通常存放程序的低地址空间。

    &myotherints: 0x7fff096df828 <-- how can the pointer to myotherints array be stored here           when dereferencing it (*) returns 20 and...
    &myotherints[0]: 0x2308010   <-- dereferencing this address with [] also returns 20, yet they are different memory locations unlike myints
    

    这有点棘手。回想一下,& 运算符的意思是“给我地址”,所以把你的代码翻译成英语一点点

    give me the address of myotherints
    

    回想一下,myotherints 是您在堆栈上分配的指针,但是该指针指向堆上的内存。所以你应该期望得到一个看起来像是来自堆栈的值(即,通常是一个更高的值)

    give me the address of the first element of myotherints
    

    现在,myotherints 又是一个指向堆上的值的指针,当你用[0] 取消引用它时,你会得到你的第一个元素的值,它存储在堆上,所以当你要求存储在堆上的东西的地址,您应该期望与存储在堆栈上的东西不同的结果。 (即代表堆数据的相当低的数字)

    编辑:对于真正的 C++ 分配数组的方法,您可能应该使用 std::array 参见 Manu's answer

    【讨论】:

    • 我非常感谢详尽的回答,它挑选出了我相当有针对性的问题并对其进行了阐述。我很感谢你和 Manu 提供了出色的见解,但是这个答案确实让我明白了这一点。
    【解决方案4】:

    new操作符其实是一种特殊的方法,简单来说就是请求操作系统提供一些空闲内存,并返回新分配的内存地址。因此,它返回标准内存地址。 myints 只是一个简单的地址。存储它的内存未在堆栈上分配。您可以对其执行基本的指针代数,但不能修改它的地址。如果您熟悉 asm,您可能会将myints 视为一个简单的标签。在 C 和 C++ 中,您确实可以像这样在方法中定义标签:

    ...
    some_label:
        /* some code here*/
        goto some_label;
    

    编译器应生成指示处理器使用某种跳转指令的代码。一些jmp stack_pointer + some_label。同样,尝试修改 myints 第四个值数字会指示编译器生成类似于前面示例的调用,例如“将这个值写入地址 stack_pointer + myints + 4 * sizeof(member of myints)”或类似的东西。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-11-15
      • 1970-01-01
      • 2022-11-27
      • 2012-07-03
      • 1970-01-01
      • 2023-03-12
      相关资源
      最近更新 更多