【问题标题】:Iterating through a vector of stucts's members with pointers and offsets使用指针和偏移量遍历结构成员的向量
【发布时间】:2019-06-23 18:34:09
【问题描述】:

我正在尝试通过不跟踪指针来优化一段代码,就像我现在一样。我想创建一个常量偏移量以添加到存储的指针以获取下一个数据条目,请参见下面的代码。但是,数据位于类或结构旁边,包含不同的数据类型。

所以我在下面的代码sn-p中得到了正确的行为,即输出为1、2、3。

#include <iostream>
#include <vector>

// knows nothing about foo
class Readfoo
{ 
    private:
    int offset;
    double* pdouble;

    public:
    void SetPoint(double* apdouble, int aoffset)  
    {
        offset = aoffset;
        pdouble = apdouble;
    };

    const double& printfoo(int aidouble) const
    {
       return *(pdouble + offset*aidouble);
    };
};

// knows nothing about readFoo
struct foo
{ 
    int a[5];
    double b[10];
};

int main() 
{
    // populate some data (choose b [2] or other random entry.).
    std::vector<foo> bar(10);
    bar[0].b[2] = 1;
    bar[1].b[2] = 2;
    bar[2].b[2] = 3;

    // access b[2] for each foo using an offset.
    Readfoo newReadfoo;
    newReadfoo.SetPoint(&(bar[0].b[2]), sizeof(foo)/sizeof(double));
    for(int ii = 0; ii < 3; ii++)
    {        
        std::cout<<"\n"<<newReadfoo.printfoo(ii);
    }
    return 0;
}

我的问题有两个:

  1. 这段代码真的合法吗?它会产生未定义的行为吗?
  2. 如果不合法,有没有办法在不存储实际指针的情况下,通过 foo 访问 b[2] 进行迭代,在上述情况下,使用某种形式的常量偏移量(例如在起始位置之间添加位数)每个数据条目的地址。)?

【问题讨论】:

  • 真的很好奇:你有任何证据表明你的代码比简单地访问bar[i].b[2];表现更好
  • 顺便说一句,标题有点误导,因为您没有迭代结构的成员,但是在迭代向量中的元素时总是访问相同的成员
  • 嗯,又看了一遍,标题其实还不错(还是会被误解)
  • 你为什么要用指针算法做你正在做的事情?你试图用它解决的真正的问题是什么?数组访问代码仍将作为指针算术代码生成(对于任何数组或指针a 和索引i,表达式a[i] 完全 等于*(a + i))。
  • 让我高度怀疑您的代码是否真正优化的原因是您没有使用任何编译器也无法使用的信息。如果编译器为您的代码发出相同的输出以及使用向量operator[] 编写的相同循环,我实际上不会太惊讶

标签: c++ pointers iteration heap-memory


【解决方案1】:

不,这不是合法的 C++。仅当您留在同一个数组内(或超过数组末尾)时才定义指针算法。

expr.add#4

当一个整数类型的表达式J被添加到一个指针类型的表达式P或从中减去时,结果的类型为P

  • (4.1) 如果P 的计算结果为空指针值,而J 的计算结果为0,则结果为空指针值。

  • (4.2) 否则,如果P 指向具有n 个元素的数组对象x 的元素x[i],则表达式P + JJ + P(其中J 的值为j)指向(可能-假设的)元素x[i+j] 如果0≤i+j≤n 和表达式P - J 指向(可能是假设的)元素x[i−j] 如果0≤i−j≤n

  • (4.3) 否则,行为未定义。

(4.1) 不适用,因为您不在nullptrs 上操作。 (4.2) 不适用,因为您正在使用double*,因此标准引用中的x 必须是double 数组,即结构的b 成员。根据剩下的(4.3),用指针算术离开它的边界是未定义的行为。

您在这里尝试做的正是一个好的编译器应该(并且将会)在后台做的事情:

volatile double output;

void bar(std::vector<foo> bar, int innerOffset)
{
    for (foo& f : bar)
        output = f.b[innerOffset];
}

https://godbolt.org/z/S9qkTf

注意反汇编如何执行您想要的指针运算(因为编译器知道它在目标平台上工作)。这是最里面的循环:

.L3:
    movsd   xmm0, QWORD PTR [rax+24+rsi*8]
    add     rax, 104
    movsd   QWORD PTR output[rip], xmm0
    cmp     rdx, rax
    jne     .L3

104 字节正好是 foo 的大小。 [rax+24+rsi*8] 表达式正在免费执行所有额外的指针运算。

【讨论】:

  • OP 确实保留在同一个数组中,但指针不是指向数组中的元素(但有一些偏移)。我认为你是对的,我只是不确定细节
  • @user463035818 补充说明。
  • OP 可以使用指向数组中元素的指针进行算术运算,然后才添加偏移量以到达结构的成员,但我想这只是移动 UB
  • @user463035818 好吧,您必须将成员指针应用到您取消引用的foo 数组元素,并使用指针算法转到正确的内部数组索引。这只是bar[i].b[y] 的一种疯狂复杂的方式。
  • @MaxLanghof 考虑到我编辑的 sn-p,您的答案是否会有任何变化,尽管我知道它仍然不合法。
猜你喜欢
  • 1970-01-01
  • 2011-08-02
  • 1970-01-01
  • 1970-01-01
  • 2021-11-23
  • 2012-02-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多