1

I have a struct which looks like:

#pragma pack(1)
typedef struct WHEATHER_STRUCT {
    uint8_t packetID; // Value 9
    uint16_t packetSize; // Value 7
    float cloudLayerAltitude; // Value 25000
} Wheather_Struct

This struct was initialized correctly. Due to design of an algorithm I need to read these three attributes values by a pointer offset. I thank about declare an array which have the size in bytes of these attributes. Just like:

int sizeOfStructAttributes = {1, 2, 4};

And finally to access these values do something like:

pointer = (*this->wheather_struct->packetID)
for (i=0; i<sizeof(sizeOfStructAttributes); i++)
    cout << &pointer << ' ';
    pointer = pointer + sizeOfStructAttributes[i];

Expected result:

9 7 25000

Could you help me please?

JoseJimRin
  • 372
  • 1
  • 4
  • 20
  • 8
    "Due to design of an algorithm I need to read these three attributes values by a pointer offset." Change the design of the algorithm. Dealing with structure internals is usually a bad idea and it's not portable solution. – Yksisarvinen Oct 19 '18 at 08:46
  • I need to do this because I will develop an algorithm which read values of a different structs using size values of attributes. – JoseJimRin Oct 19 '18 at 08:50
  • 1
    You can use [offsetof](https://en.cppreference.com/w/cpp/types/offsetof). This looks like an [XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem), though. – molbdnilo Oct 19 '18 at 08:52
  • @Yksisarvinen is right - avoid accessing internals of a struct by offset. It's fragile and error prone. There are better ways. – Rags Oct 19 '18 at 08:53
  • 2
    *"Expected result: 9 7 25000"*, so you also need size of the variable(and probably type instead). – Jarod42 Oct 19 '18 at 08:59
  • Ok, I know that use offset to access values is dangerous. I can not acces by attribute name because of I want that this algorithm read struct values by size offset. What do you recommend to get this functionality. – JoseJimRin Oct 19 '18 at 08:59
  • Isn't library providing reflexivity an alternative ? – Jarod42 Oct 19 '18 at 09:01
  • Because I wanted to do an algorithm that get the values of structs by offset. If I want have an algorithm which get the values by attribute I need to declare a method per struct. So I think about did it by offset for only have one method to do this. – JoseJimRin Oct 19 '18 at 09:06
  • The troubles with paddings and offsets are (likely) going to be much worse than providing one method for each struct. – Yksisarvinen Oct 19 '18 at 09:25
  • @Yksisarvinen ok, but if I used pragma pack(1) compiler can not change the size of these offset. And if I used a differente method per struct I will have more than ten methods with "the same code". Because of this I am trying to get this solution. – JoseJimRin Oct 19 '18 at 09:27
  • Or providing tuple view might also help. – Jarod42 Oct 19 '18 at 09:40
  • I believe you could also use a serializing/deserializing library like [Google's protobuf](https://developers.google.com/protocol-buffers). I won't post an answer, because I haven't used it personally, but it might be worth to check it. – Yksisarvinen Oct 19 '18 at 09:43

5 Answers5

2

You have many problems with the code I will try to go through them all:

1- Your structure has padding values that depends on the architecture you are targeting maybe 3 or 7 bytes after the first member (packetID) it depends on the architecture and compiler.

2- You are initializing the pointer in a wrong way, it should be:

pointer = &(this->wheather_struct->packetID);

3- cout should be:

cout << *((datatype*)pointer) << ' '; 
//datatype should be different in each loop iteration of course.

4- In case you are creating array of this strcutrue, I am not sure if you will face a problem of padding or not. It happens in very rare cases when you use different packing and padding due to mixing your code with other libraries that are compiled with different compiler directives or even uses #pragma to modify the behavior of the compiler during the compile time.

Finally I am sure there is no need at all to enumerate struct members with a pointer.

I encourage you to read about struct padding and packing, good place to start is this question on SO: Structure padding and packing

Mahmoud Fayez
  • 3,398
  • 2
  • 19
  • 36
0

One thing for sure, you won't be able to write these offsets manually. This is absolutely not a stable way of doing things, because your compiler might do optimizations such as aligning your struct members.

What you can do is this:

Wheather_Struct w;
long offsetsOfStructAttributes[3] = {0, 
                                     (char*)&w.packetSize - (char*)&w.packetID, 
                                     (char*)&w.cloudLayerAltitude - (char*)&w.packetID};

Notice that this is the byte difference in size.

Having told you how to do that, I have to say like people said in the comments, please find another way of doing this. This is not safe, unless you absolutely know what you're doing.

Community
  • 1
  • 1
The Quantum Physicist
  • 24,987
  • 19
  • 103
  • 189
  • Why not use the `offsetof` macro? – Martin Bonner supports Monica Oct 19 '18 at 09:08
  • @MartinBonner No reason at all. Just the straight-forward way used here for a beginner. – The Quantum Physicist Oct 19 '18 at 09:10
  • I used pragma pack, so i will work with offset because of compiler can not change this offset size value. Am I wrong? – JoseJimRin Oct 19 '18 at 09:25
  • I'm not convinced this is well-defined, anyway, so even if you get the maths right, and even if you have strictly controlled your type's layout, you could still get dodgy results / crashes / spontaneous gamma-ray bursts. Remember, compilers are very complicated and rely on standard-imposed constraints when translating your code into a real computer program, which involves "optimisations" based on aliasing rules and all sorts of stuff. How about some nice pointers-to-members? – Lightness Races in Orbit Oct 19 '18 at 09:54
  • @LightnessRacesinOrbit I'm providing what the OP asked for (read the last paragraph in my answer). I would never do this in my own code and I would never even accept a pull request with this. However, I don't believe getting the layout like this is invalid. There are even CPU instructions that depend on the offsets with an immediate field. From a low-level perspective, this is how structs work internally. I don't think the compiler would be stupid enough to provide wrong addresses here, then use the correct ones in the CPU instructions. Maybe I'm wrong though. Not 100% sure. – The Quantum Physicist Oct 19 '18 at 10:15
  • @JoseJimRin The offset is supposed to be fixed at compile-time. – The Quantum Physicist Oct 19 '18 at 10:16
  • 1
    @TheQuantumPhysicist I'm not saying your answer's wrong; my comment was an addendum for everyone. However, I must address the misconception in your comment because it misses the point - compilers are not stupid enough to provide wrong addresses here, they are _too clever_ to guarantee expected results from this invalid code. That's _if_ I'm right about the UB; not sure. Don't make the mistake of thinking that the C++ program is somehow a one-to-one mapping to those CPU instructions and internal struct representations. It's not. – Lightness Races in Orbit Oct 19 '18 at 10:16
0

There are two problems with your approach:

Firstly, it requires you to get the sizes right. Use sizeof to do that. So your array would look like:

size_t sizeOfStructAttributes = {sizeof(wheather_struct::packet_id),
                               sizeof(wheather_struct::packet_size),
                               sizeof(wheather_struct::cloudLayerAltitude) };

The second (more serious) problem is that you don't allow for padding in your structure. Almost all compilers will (unless specially instructed), insert a padding byte between packet_id and packet_size so that everything is nicely aligned. Fortunately, there is a solution for that too - use the offsetof macro (defined in stddef.h):

size_t offsetOfStructAttributes = {offsetof(wheather_struct, packet_id),
                                 offsetof(wheather_struct, packet_size),
                                 offsetof(wheather_struct, cloudLayerAltitude) };

The code then becomes:

for (size_t offset: offsetsOfStructAttributes) {
    pointer = &(this->wheather_struct->packetID) + offset
    cout << pointer << ' ';
}

Actually: the above code fixes a third problem with your code: sizeof() returns the size in bytes, which is unlikely to be the element count.

Finally, your variables have a typo: meteorology is concerned with whether the weather will be fine or not. You have confused the two words and I am pretty sure you mean "weather".

0

Your mistake is that you've assumed that the class has no padding between the members. But there must be padding in order to meet the alignment requirements of the members. Thus the offsets are not what you assume.

To get the offset of a class member, you can use the offsetof macro provided by the standard library. That said, without knowing what you need it for, I remain skeptical about it being appropriate. Note that offsetof works only if your class is a standard layout class. Otherwise the behaviour will be undefined. Your example WHEATHER_STRUCT is standard layout.

cout << &pointer << ' ';

Something like this can not possibly have the output that you expect. You take the address of the pointer, it cannot possibly give you the value of the pointed object that you wanted.

The way to get the pointed value is the indirection operator. But, indirection operator can only work correctly if the pointer is of correct type (float* for float members, uint16_t* for uint16_t members ...) but it cannot be of correct type since it has to be a pointer to a byte for the pointer arithmetic to work with the offsets.

Besides the offset, you also need to know the type of the variable in order to interpret the value. You could store the type in some structure. But you cannot cast the pointer to a type determined at runtime, so what you need is some runtime flow-structure such as a switch or a jump table for the conversion.

eerorika
  • 232,697
  • 12
  • 197
  • 326
0

You'd better do not use pointer hack: one day underlying memory layout will be changed and your program may corrupt it. Try to simulate metadata instead.

enum WheatherStructFields
{
    wsfPacketID,
    wsfPacketSize,
    wsfCloudLayerAltitude,
    wsfNone
};

typedef struct WHEATHER_STRUCT
{
    uint8_t packetID;
    uint16_t packetSize;
    float cloudLayerAltitude;
    void OutFieldValue(std::ostream& os, WheatherStructFields whatField)
    {
        switch (whatField)
        {
        case wsfPacketID:
            os << (int)packetID;
            break;
        case wsfPacketSize:
            os << packetSize;
            break;
        case wsfCloudLayerAltitude:
            os << cloudLayerAltitude;
            break;
        default:
            os << "Unsupported field: " << whatField;
        }
    }
} Wheather_Struct;


int main()
{
    Wheather_Struct weather = { 9, 7, 25000 };
    for (WheatherStructFields whatField = wsfPacketID; whatField < wsfNone; 
        whatField = (WheatherStructFields)((int)whatField + 1))
    {
        weather.OutFieldValue(std::cout, whatField);
        std::cout << " ";
    }
}
serge
  • 992
  • 5
  • 8
  • I used pragma pack, so i will work with offset because of compiler can not change this offset size value. Am I wrong? – JoseJimRin Oct 19 '18 at 09:25
  • Try different compilers, platforms and targets to ensure your hack is still working. – serge Oct 19 '18 at 09:38
  • Try to add new fields, too. Guess that someone (human) or automatic code formatter can reorder it. Why you need produce a smell and fragile code? – serge Oct 19 '18 at 09:47