简体   繁体   中英

Access struct variable value by pointer offset

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?

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

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.

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".

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.

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 << " ";
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM