Skip to main content
GameDev.net gamedev.net
🔒 Locked

Accessing Elements of Structure with a Pointer?

Started by AfroFire Sep 21, 2011 at 3:17 AM 24 replies 3.6k views
Original Post
AfroFire
AfroFire
Is it possible to access the individual elements of a structure using some offset from the current memory address?

For example, if I had a struct that was as such:

typedef struct {
int x;
int y;
int z;
} BALL;

BALL *a = malloc(sizeof(BALL));


Instead of calling a->y I was wondering if there was a way I could do something like *(a+offset) where offset is sizeof(int) or something, and then it would access the y element. So far nothing I do seems to work. I could've sworn that my understanding of the way struct was laid out meant that the struct was contiguously stored in memory or something.
AfroFire | Brin"The only thing that interferes with my learning is my education."-Albert Einstein
JTippetts
JTippetts
Since the pointer, a, points to type BALL, any offset applied to a is going to be applied in chunks the size of ball. ie, it thinks a+offset is an array access into an array of BALLS. You would need to cast a to a char ptr in order to do byte-level offsetting.

Out of curiosity, why is -> indirection unsuitable?
L. Spiro
L. Spiro
The offset of a variable in a structure can be found as follows:

BALLS * pbThis = new BALLS;
#1: Convert the pointer to a numeric value for arithmetic.
UINT_PTR uiptrBaseAddr = reinterpret_cast<UINT_PTR>(pbThis);

#2: Convert the address of the member to an integer-form numeric value.
UINT_PTR uiptrMemberAddr = reinterpret_cast<UINT_PTR>(&pbThis->y);

#3: Subtract them.
UINT_PTR uiptrOffset = uiptrMemberAddr - uiptrBaseAddr; // uiptrOffset = 4 in Microsoft Visual Studio (sizeof( int )).


To then access that variable via offset instead of ->:
int iY = (*reinterpret_cast<int *>(uiptrBaseAddr + uiptrOffset));

Or without the predetermined offsets:
int iY = (*reinterpret_cast<int *>(reinterpret_cast<UINT_PTR>(pbThis) + sizeof( int )));



I did not provide this solution because I think you should be doing this. This is for learning only or for making a script system in which you can send member offsets to the script and it can use offsets to access those members.


L. Spiro
I restore Nintendo 64 video-game OST’s into HD! https://www.youtube.com/channel/UCCtX_wedtZ5BoyQBXEhnVZw/playlists?view=1&sort=lad&flow=grid
AfroFire
AfroFire

Since the pointer, a, points to type BALL, any offset applied to a is going to be applied in chunks the size of ball. ie, it thinks a+offset is an array access into an array of BALLS. You would need to cast a to a char ptr in order to do byte-level offsetting.

Out of curiosity, why is -> indirection unsuitable?


This isn't even a game development related question. I just feel like this is one of the most trust worthy sites to ask programming related questions. Thank you for pointing out the +1 being equal to the size of BALL. I will do the casting to char bit.

The reason why -> is unsuitable is because I am writing a function that has to turn large arrays of structures in to arrays of the constituents of the structure. So for example, I'd like a giant array of x properties, so I'd like to be able to iterate through a list of BALLs and store the x values in to an array of integers that are equal to the x values. That's simple enough when I have an x, y, and z, but if this so-called struct had say 50 properties, I'd have to write some code for all of them. This little bit of help you provided is going to help me write a general function that does the task, and all I want to have to do is provide an offset, and it will just go to that offset and create an array from that.

Again, thank you for the very quick help.
AfroFire | Brin"The only thing that interferes with my learning is my education."-Albert Einstein
AfroFire
AfroFire

The offset of a variable in a structure can be found as follows:

BALLS * pbThis = new BALLS;
#1: Convert the pointer to a numeric value for arithmetic.
UINT_PTR uiptrBaseAddr = reinterpret_cast<UINT_PTR>(pbThis);

#2: Convert the address of the member to an integer-form numeric value.
UINT_PTR uiptrMemberAddr = reinterpret_cast<UINT_PTR>(&pbThis->y);

#3: Subtract them.
UINT_PTR uiptrOffset = uiptrMemberAddr - uiptrBaseAddr; // uiptrOffset = 4 in Microsoft Visual Studio (sizeof( int )).


To then access that variable via offset instead of ->:
int iY = (*reinterpret_cast<int *>(uiptrBaseAddr + uiptrOffset));

Or without the predetermined offsets:
int iY = (*reinterpret_cast<int *>(reinterpret_cast<UINT_PTR>(pbThis) + sizeof( int )));



I did not provide this solution because I think you should be doing this. This is for learning only or for making a script system in which you can send member offsets to the script and it can use offsets to access those members.


L. Spiro


I have never seen the UINT_PTR before, and while this isn't your fault, my code is in C, and if I can recall correctly, the reinterpret_cast< ... > is a C++ thing, right?

Either way, I am content with (char*) casting.

Thanks to you as well!



AfroFire | Brin"The only thing that interferes with my learning is my education."-Albert Einstein
L. Spiro
L. Spiro
UINT_PTR exists within the Win32 SDK to provide an unsigned integer whose size is always the same size as a pointer.
Many people naively convert pointers to int, unsigned long, DWORD, and a number of other atrocities.
If you need an integer form of a pointer, always cast to UINT_PTR.

And in C, you can use a C cast:
UINT_PTR uiptrBaseAddr = (UINT_PTR)pbThis;


L. Spiro
I restore Nintendo 64 video-game OST’s into HD! https://www.youtube.com/channel/UCCtX_wedtZ5BoyQBXEhnVZw/playlists?view=1&sort=lad&flow=grid
Aardvajk
Aardvajk

Either way, I am content with (char*) casting.


Compiler is free to insert padding between items so simply assuming that the second int member is at pointer+sizeof(int) from pointer is not guaranteed. It is likely with ints as if padding is done, it is probably going to aligned to machine word boundaries, but not for sure.

So code that "works" could suddenly break if you change compiler, or if your compiler updates, or if you add another member to the struct etc etc.
AfroFire
AfroFire

[quote name='AfroFire' timestamp='1316584253' post='4864114']
Either way, I am content with (char*) casting.


Compiler is free to insert padding between items so simply assuming that the second int member is at pointer+sizeof(int) from pointer is not guaranteed. It is likely with ints as if padding is done, it is probably going to aligned to machine word boundaries, but not for sure.

So code that "works" could suddenly break if you change compiler, or if your compiler updates, or if you add another member to the struct etc etc.
[/quote]

Well shit, then what can I do? This is a very real concern.
AfroFire | Brin"The only thing that interferes with my learning is my education."-Albert Einstein
AfroFire
AfroFire

UINT_PTR exists within the Win32 SDK to provide an unsigned integer whose size is always the same size as a pointer.
Many people naively convert pointers to int, unsigned long, DWORD, and a number of other atrocities.
If you need an integer form of a pointer, always cast to UINT_PTR.

And in C, you can use a C cast:
UINT_PTR uiptrBaseAddr = (UINT_PTR)pbThis;


L. Spiro


Ah, well this is code that is being run on super computers that will more than likely be running Linux... is there a non Win32 version?
AfroFire | Brin"The only thing that interferes with my learning is my education."-Albert Einstein
Hodgman
Hodgman

The offset of a variable in a structure can be found as follows:BALLS * pbThis = new BALLS;#1: Convert the address of the member to a pointer to a byte-sized type
char* baseAddr = reinterpret_cast<char*>(pbThis);#2: Convert the address of the member to a pointer to a byte-sized type
char* memberAddr = reinterpret_cast<char*>(&pbThis->y);#3: Subtract them.
ptrdiff_t offset = memberAddr - baseAddr; // offset = 4 in Microsoft Visual Studio (sizeof( int )).To then access that variable via offset instead of ->:int iY = (*reinterpret_cast<int *>(baseAddr + offset));
^^Standard-compliant version
Slavik81
Slavik81
Most C++ compilers should support uintptr_t. It's a part of C99.
rip-off
rip-off
Most compilers provide some kind of macro for this. The wikipedia claims that offsetof() is standard C. It links to documentation pages for GCC and Microsoft C++.
AndyEsser
AndyEsser
#pragma pack(0) will ensure no byte-padding happens within structs. This is useful when reading/writing to files.

Remember to place a #pragma pack(pop) afterwards to return to the compiler default. For example:


#pragma pack(push)
#pragma pack(0)

struct SOME_STRUCT
{
//
}

#pragma pack(pop)
Serapth
Serapth

#pragma pack(0) will ensure no byte-padding happens within structs. This is useful when reading/writing to files.

Remember to place a #pragma pack(pop) afterwards to return to the compiler default. For example:


#pragma pack(push)
#pragma pack(0)

struct SOME_STRUCT
{
//
}

#pragma pack(pop)



Zero is not a valid parameter to pack().

That said, given that this question is being For Beginners, the answer should probably be simply "don't". Without a very very very good reason, this is a bad idea and should be approach in a different manner. With the exception of emulators, I have seen almost no code where this is a good idea.
AndyEsser
AndyEsser

[quote name='AndyEsser' timestamp='1316621834' post='4864242']
#pragma pack(0) will ensure no byte-padding happens within structs. This is useful when reading/writing to files.

Remember to place a #pragma pack(pop) afterwards to return to the compiler default. For example:


#pragma pack(push)
#pragma pack(0)

struct SOME_STRUCT
{
//
}

#pragma pack(pop)



Zero is not a valid parameter to pack().

That said, given that this question is being For Beginners, the answer should probably be simply "don't". Without a very very very good reason, this is a bad idea and should be approach in a different manner. With the exception of emulators, I have seen almost no code where this is a good idea.
[/quote]

My apologies - I meant #pragma pack(1)
SiCrane
SiCrane
Depending on what exactly what you're trying to do you may be able to use pointers to members rather than offsets.

Edit: nevermind, I just noticed where you said you were using C, not C++.
Akusei
Akusei

UINT_PTR exists within the Win32 SDK to provide an unsigned integer whose size is always the same size as a pointer.
Many people naively convert pointers to int, unsigned long, DWORD, and a number of other atrocities.
If you need an integer form of a pointer, always cast to UINT_PTR.

And in C, you can use a C cast:
UINT_PTR uiptrBaseAddr = (UINT_PTR)pbThis;


So this is wrong to do then?
DWORD *uiptrBaseAddr = (DWORD *)pbThis;
-akusei
ApochPiQ
ApochPiQ
IIRC, by the C standard, the only type you can safely cast any pointer to and back from is char*. Anything else falls afoul of strict aliasing rules. I forget exactly how void* is handled by the C standard offhand, but the problem with void* is you can't do pointer arithmetic on it, so your offset magic won't work.


Just for completeness - if you have the ability to modify the struct definitions, you could do some macro stuff to have a single definition of each structure which then also lets you generate type-safe code for converting your array-of-structs to a struct-of-arrays. That only works if you can replace the struct definitions themselves, though, so I won't bother trying to write the macros unless you can confirm that that would be OK :-)
BeerNutts
BeerNutts

Is it possible to access the individual elements of a structure using some offset from the current memory address?

For example, if I had a struct that was as such:

typedef struct {
int x;
int y;
int z;
} BALL;

BALL *a = malloc(sizeof(BALL));


Instead of calling a->y I was wondering if there was a way I could do something like *(a+offset) where offset is sizeof(int) or something, and then it would access the y element. So far nothing I do seems to work. I could've sworn that my understanding of the way struct was laid out meant that the struct was contiguously stored in memory or something.


O my God, I don't understand why people make this so hard.

#include <stddef.h> /* contains offsetof macro */
#include <stdlib.h> /* contains malloc */
#include <stdio.h> /* contains printf */

typedef struct {
int x;
int y;
int z;
} BALL;

void main(void)
{
BALL *pBall;
char *pLocation;
int *pyValue;

pBall = (BALL *)malloc(sizeof(BALL));

pBall->y = 123;

/* set the char * to the memory address of pBall */
pLocation = (char *)pBall;

/* using offsetof(), find the offset of y in the structure BALL, and add it to location, then cast to int* and assign to pyValue */
pyValue = (int *)(pLocation + offsetof(BALL, y));

/* this will print 123 as the value */
printf(" Value in pyValue is %d\n", *pyValue);

free (pBall);
}


That should work on all C compiler, with padding or not.

EDIT: Fixed typo (comments were right, code wrong)
My Gamedev Journal: 2D Game Making, the Easy Way

---(Old Blog, still has good info): 2dGameMaking
-----
"No one ever posts on that message board; it's too crowded." - Yoga Berra (sorta)
ApochPiQ
ApochPiQ
Your solution is not generalized; you have to hard-code it for every structure member. The OP is looking for something more flexible.

Also, your solution is not correct. pyValue should be assigned to (int*)(pLocation + offsetof(BALL, y)), not (...pBall...).

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.