从指向结构数组的指针中提取元素

Posted

技术标签:

【中文标题】从指向结构数组的指针中提取元素【英文标题】:extract elements from a pointer to pointer to array of structures 【发布时间】:2020-06-17 05:16:28 【问题描述】:

我有一个指向结构数组的指针的问题。

我声明并初始化三个点到具有成员val 的结构。 我创建了一个指向结构指针数组的指针arr。 然后我创建一个指针pa 指向指针arr 和一个指针pa1 指向pa

当我尝试使用pa1 提取arr 的第二个元素时遇到问题。

main.cpp

#include<iostream> 
#include<cstdio> 

struct a 
    int val; 
; 

int main() 

    // create structures 
    a *a1 = new a; 
    a1->val = 5; 
    a *a2 = new a; 
    a2->val = 3; 
    a *a3 = new a; 
    a3->val = 4; 

    a *arr[3] =  a1, a2, a3 ; 
    a **pa = arr; 

    std::cout << "Using pa:\n"; 
    std::printf( "1st val: %d\n", (*(pa+0))->val ); 
    std::printf( "1st pos: %p\n", (*(pa+0)) ); 
    std::printf( "2nd val: %d\n", (*(pa+1))->val ); 
    std::printf( "2nd pos: %p\n", (*(pa)+1) ); // modified 

    std::cout << std::endl << std::endl; 

    // a pointer to pa's value
    a *pa1 = *pa;  

    std::cout << "Using pa1:\n"; 
    std::printf( "1st val: %d\n", pa1->val ); 
    std::printf( "1st pos: %p\n", pa1 ); 
    std::printf( "2nd val: %d\n", (pa1+1)->val ); 
    std::printf( "2nd pos: %p\n", pa1+1 ); 


    delete a1, a2, a3;  

    return 0; 

然后我得到如下结果,

Using pa:
1st val: 5
1st pos: 0000000000e761e0
2nd val: 3
2nd pos: 0000000000e761e4

Using pa1:
1st val: 5
1st pos: 0000000000e761e0
2nd val: 0
2nd pos: 0000000000e761e4

问题是为什么pa12nd val不是3而是0

papa1 似乎都指向同一个地址 0000000000e761e4

更新

// modified 应该是 std::printf( "2nd pos: %p\n", *(pa+1) );

结果如下

Using pa:
1st val: 5
1st pos: 0000000000ee61d0
2nd val: 3
2nd pos: 0000000000ee61f0


Using pa1:
1st val: 5
1st pos: 0000000000ee61d0
2nd val: 0
2nd pos: 0000000000ee61d4

papa12nd pos 实际上并不相同。

现在的问题是如何使用pa1arr 获取第二个元素?

【问题讨论】:

我认为你不能 delete pa1; 和其他人,就此而言。 pa1+1(*pa)+1。为什么你认为这与*(pa+1) 相同? 让你感到困惑的是 std::printf( "2nd pos: %p\n", ((*pa)+1) );这应该是 std::printf("2nd pos: %p\n", (*(pa+1)) );以匹配您在上面的行中的内容。这将表明它们没有指向同一个地址。 @SouravGhosh 这些删除都无效(也许 pa1 删除了 a1)。他们都试图删除堆栈变量arr。并且分配的a1..a3 仍然存在。 @CraigR 谢谢。我已经更新了我的问题。 【参考方案1】:

你必须做一个图表,什么存储在哪里以及如何存储:

例如:

// Allocated values (they are completely unrelated and may point into different locations):
a1 = 0x11111100;
a2 = 0x22222200;
a3 = 0x33333300;
// Where each of those addresses points to the place in memory with one constructed element of a 

// next is creating arr of three pointers to a:
a * arr[3] =  a1, a2, a3 ;
// which is effectively:
a * arr[3] =  0x11111100, 0x22222200, 0x33333300;

// the arr is stored somewhere too, and it contains location, where those three addresses are stored
arr = 0xFFFFF340; // for example

// so when you do:
a ** pa = arr;
// then
pa == 0xFFFFF340;
// and pa + 1 == 0xFFFFF344  (increment depends on architecture) 
// ==> dereferenced value is location where pointer to a2 is stored

但是,如果你这样做:

a * pa1 = *pa; 
// then it means you did:
a * pa1 = arr[0];
// therefore:
pa1 == 0x11111100;
// and pa1+1 is memory location after a1 object eg.:
pa1+1 == 0x11111104;
// but as you've created only one element in that place, dereferencing it causes buffer overflow and using uninitialized memory

顺便说一句:您可以使用调试器和变量监视来跟踪正在发生的事情以及指向何处及其值的内容

编辑:添加了更大的结构,因此差异更加明显:

#include <stdio.h>

struct Data 
    int x[100] = 0;
;


int main()

    Data * a = new Data[10];
    Data * b = new Data[5];
    Data * c = new Data[1];

    printf("a value: %p\n", a);
    printf("b value: %p\n", b);
    printf("c value: %p\n", c);

    Data * arr[] =  a, b, c;

    for (auto const& ptr : arr)
    
        printf("Arr: element addr: %p  position: %ld  ptr to: %p\n", &ptr, &ptr-arr, ptr);
    

    Data ** pa = arr;
    printf("pa   value: %p  dereferenced value: %p\n", pa, *pa);
    printf("pa+1 value: %p  dereferenced value: %p\n", pa+1, *(pa+1));
    printf("pa+2 value: %p  dereferenced value: %p\n", pa+2, *(pa+2));

    Data * pa0 = *pa;
    printf("pa0   value: %p\n", pa0);
    printf("pa0+1 value: %p\n", pa0+1);
    printf("pa0+2 value: %p\n", pa0+2);

    // cleanup - eventually :D  But using std::unique_ptr would be much better.

还有输出:

a value: 0x55bd5d988eb0
b value: 0x55bd5d989e60
c value: 0x55bd5d98a640
Arr: element addr: 0x7ffc528dcf00  position: 0  ptr to: 0x55bd5d988eb0
Arr: element addr: 0x7ffc528dcf08  position: 1  ptr to: 0x55bd5d989e60
Arr: element addr: 0x7ffc528dcf10  position: 2  ptr to: 0x55bd5d98a640
pa   value: 0x7ffc528dcf00  dereferenced value: 0x55bd5d988eb0
pa+1 value: 0x7ffc528dcf08  dereferenced value: 0x55bd5d989e60
pa+2 value: 0x7ffc528dcf10  dereferenced value: 0x55bd5d98a640
pa0   value: 0x55bd5d988eb0
pa0+1 value: 0x55bd5d989040
pa0+2 value: 0x55bd5d9891d0

【讨论】:

a *pa1 = *pa; 正好指向pa 指向的内容,也就是a1。所以当我执行pa+1 时,它只会移动struct a 的大小。我说的对吗? 另一个问题是为什么pa+1移动了32个字节而不是8个字节?指针的大小是8个字节。 @wukaihua119 是的,它应该移动到下一个(但未分配的)对象。另一个问题:std::printf( "2nd pos: %p\n", *(pa+1) 你没有打印pa+1,你在那个位置打印地址。您应该在 a1 .. a3 分配之间进行一些分配,这样它就不会存储在连续的内存中,并且更容易发现 @wukaihua119 我添加了更明显的输出示例(-std=c++11 或者您可以展开该循环) 哦,我搞砸了。 pa+1 正是arr 的第二个元素的地址。 *(pa+1)a2的地址。【参考方案2】:

papa1 似乎都指向同一个地址 0000000000e761e4

但是他们是不同的类型。因此+1 给出了不同的地址。

pa1a* 类型,因此pa1+1reinterpret_cast&lt;a*&gt;(reinterpret_cast&lt;unsigned char*&gt;(pa1)+sizeof(a))。请注意,我们剥离了一层间接计算偏移量。

paa** 类型,因此pa1+1reinterpret_cast&lt;a**&gt;(reinterpret_cast&lt;unsigned char*&gt;(pa)+sizeof(a*))

由于reinterpret_cast&lt;unsigned char*&gt;(pa)==reinterpret_cast&lt;unsigned char*&gt;(pa1),但sizeof(a)!=sizeof(a*)我们有不同的结果。

问题是pa1 不是a 数组中的第一个地址!所以这个计算是错误的。

【讨论】:

@Scheff 我搞砸了。你能读到第二个版本吗?

以上是关于从指向结构数组的指针中提取元素的主要内容,如果未能解决你的问题,请参考以下文章

C语言 | 结构体指针

如何从 STL 列表中的迭代器访问指向结构的指针而不是其元素

C语言-结构体

指向结构数组的指针在输入值时崩溃

c ++指针将结构新数组指向delphi到DLL函数

第二次作业