问题
I am trying to optimize a piece of code by not pointer chasing, as much as I currently am. I want to create a constant offset to add to a stored pointer to get to the next data entry, see below code. However, the data is sitting side a class or struct, containing different data types.
So I get the correct behavior in the below code snippet, ie the output is 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;
}
My question is two-fold:
- Is this code actually legal, and will it ever produce undefined behavior?
- If it is not legal is there a way to, without storing the actual pointers, iterate through foo accessing b[2], in the above case, with some form of constant offset (Like adding the number of bits between the starting address of each data entry.)?
回答1:
No, this is not legal C++. Pointer arithmetic is only defined if you stay inside the same array (or one past its end).
expr.add#4
When an expression
J
that has integral type is added to or subtracted from an expressionP
of pointer type, the result has the type ofP
.
(4.1) If
P
evaluates to a null pointer value andJ
evaluates to0
, the result is a null pointer value.(4.2) Otherwise, if
P
points to elementx[i]
of an array objectx
with n elements, the expressionsP + J
andJ + P
(whereJ
has the valuej
) point to the (possibly-hypothetical) elementx[i+j]
if0≤i+j≤n
and the expressionP - J
points to the (possibly-hypothetical) elementx[i−j]
if0≤i−j≤n
.(4.3) Otherwise, the behavior is undefined.
(4.1) does not apply because you're not operating on nullptr
s. (4.2) does not apply because you are working with double*
, so x
in the standard quote must be a double
array, i.e. the b
member of your struct. Leaving its bounds with pointer arithmetic, according to the remaining (4.3), is undefined behavior.
What you are trying to do here is exactly what a good compiler should (and will) do under the hood anyway:
volatile double output;
void bar(std::vector<foo> bar, int innerOffset)
{
for (foo& f : bar)
output = f.b[innerOffset];
}
https://godbolt.org/z/S9qkTf
Notice how the disassembly does the pointer arithmetic you want (because the compiler knows that it works on the target platform). Here is the innermost loop:
.L3:
movsd xmm0, QWORD PTR [rax+24+rsi*8]
add rax, 104
movsd QWORD PTR output[rip], xmm0
cmp rdx, rax
jne .L3
104 bytes is exactly how big one foo
is. The [rax+24+rsi*8]
expression is doing all the additional pointer arithmetic for free.
来源:https://stackoverflow.com/questions/54441263/iterating-through-a-vector-of-stuctss-members-with-pointers-and-offsets