views:

329

answers:

7

In the following lines of code, I need to adjust the pointer pm by an offset in bytes in one of its fields. Is there an better/easier way to do this, than incessantly casting back and forth from char * and PartitionMap * such that the pointer arithmetic still works out?

PartitionMap *pm(reinterpret_cast<PartitionMap *>(partitionMaps));
for ( ; index > 0 ; --index)
{
    pm = (PartitionMap *)(((char *)pm) + pm->partitionMapLength);
}
return pm;

For those that can't grok from the code, it's looping through variable length descriptors in a buffer that inherit from PartitionMap.

Also for those concerned, partitionMapLength always returns lengths that are supported by the system this runs on. The data I'm traversing conforms to the UDF specification.

+5  A: 

Casting is the only way, whether it's to a char* or intptr_t or other some such type, and then to your final type.

Jim Buck
+1. You can write a macro to clean up your code's appearance, if that's what you're after.
Carl Norum
Or a function, as Evan demonstrated. And functions are usually to be preferred over macros.
GMan
A: 

The casting has to be done, but it makes the code nearly unreadable. For readability's sake, isolate it in a static inline function.

Norman Ramsey
+5  A: 

I often use these templates for this:

    template<typename T>
    T *add_pointer(T *p, unsigned int n) {
            return reinterpret_cast<T *>(reinterpret_cast<char *>(p) + n);
    }

    template<typename T>
    const T *add_pointer(const T *p, unsigned int n) {
            return reinterpret_cast<const T *>(reinterpret_cast<const char *>(p) + n);
    }

They maintain the type, but add single bytes to them, for example:

T *x = add_pointer(x, 1); // increments x by one byte, regardless of the type of x
Evan Teran
In general, adding one byte to a structure pointer is a recipe for SIGBUS errors (core dumps) - at least, on machines where structures and pointers have to be properly aligned. That doesn't stop the functions being useful - but the example perhaps leaves somewhat to be desired.
Jonathan Leffler
great idea, i often write template wrappers like this for other things, C++ is a PITA sometimes.
Matt Joiner
Though, Jonathan, I'm confused what you'd do, then.
GMan
GMan: You'd copy the bytes from the misaligned buffer into a properly aligned structure variable using `memcpy`.
caf
lol, but if the system required alignment, you'd have to do bitwise operations on every source item, and then copy it across. i'd assume `memcpy()` on those systems would do this for you?
Matt Joiner
If you're using memcpy() to extract structures, it takes a void pointer, and you don't need to cast to a specific ptr-to-object type.
Adisak
@Gman: if you added sizeof(appropriate_structure) instead of one, you've written an array indexing function. You could at least use that reliably. It might be better to scale the value passed in by the size of the structure anyway, but then you've written something that does what the subscript operator does anyway. So, not a great deal of value - I was giving the code the benefit of the doubt. It does encapsulate the moderately nasty casts relatively cleanly. (And that could be said to be 'damning with faint praise'.)
Jonathan Leffler
A good example of when you'd want to advance by a set number of bytes instead of a whole structure would be network code which deals with raw sockets. If you have a pointer to an IP header, and you want to get to the TCP header, you need to advance by a specific number of bytes (which can be variable because of IP options). When dealing with low level packed structures, sometimes pointer arithmetic is simply the proper solution.
Evan Teran
@Evan Teran, precisely, i'm extract UDF structures
Matt Joiner
A: 

What is puzzling me is why you have 'partitionMapLength' in bytes?

Wouldn't it be better if it was in 'partitionMap' units since you anyway cast it?

PartitionMap *pmBase(reinterpret_cast<PartitionMap *>(partitionMaps));
PartitionMap *pm;
...
pm = pmBase + index; // just guessing about your 'index' variable here
Anders K.
nope, they vary in length. unless you know a way to magicallyl work out the length :)
Matt Joiner
i didn't write the spec for partition maps, i merely converted it into a C++ struct, and now I'm doing stuff with the values.
Matt Joiner
It's not too unusual for a structure to end in some variable length data. This bit of code would walk though a 'packed array' of such structures.
Michael Burr
+2  A: 

You can of course just keep two variables around: a char * to step through the buffer and a PartitionMap * to access it. Makes it a little clearer what's going on.

for (char *ptr = ??, pm = (PartitionMap *)ptr ; index > 0 ; --index)
{
    ptr += pm->partitionMapLength;
    pm = (PartitionMap *)ptr;
}
return pm;
caf
except that the code you've given is wrong, this is a bit tidier than what i had.
Matt Joiner
Erm yes, fixed.
caf
+1  A: 

As others have mentioned you need the casts, but you can hide the ugliness in a macro or function. However, one other thing to keep in mind is alignment requirements. On most processors you can't simply increment a pointer to a type by an arbitrary number of bytes and cast the result back into a pointer to the original type without problems accessing the struct through the new pointer due to misalignment.

One of the few architectures (even if it is about the most popular) that will let you get away with it is the x86 architecture. However, even if you're writing for Windows, you'll want to take this problem into account - Win64 does enforce alignment requirements.

So even accessing the partitionMapLength member through the pointer might crash your program.

You might be able to easily work around this problem using a compiler extension like __unaligned on Windows:

PartitionMap __unaliged *pm(reinterpret_cast<PartitionMap *>(partitionMaps));
for ( ; index > 0 ; --index)
{
    pm = (PartitionMap __unaligned *)(((char *)pm) + pm->partitionMapLength);
}
return pm;

Or you can copy the potentially unaligned data into a properly aligned struct:

PartitionMap *pm(reinterpret_cast<PartitionMap *>(partitionMaps));

char* p = reinterpret_cast<char*>( pm);

ParititionMap tmpMap;
for ( ; index > 0 ; --index)
{

    p += pm->partitionMapLength;

    memcpy( &tmpMap, p, sizeof( newMap));
    pm = &tmpMap;
}

// you may need a more spohisticated copy to return something useful
size_t siz = pm->partitionMapLength;
pm = reinterpret_cast<PartitionMap*>( malloc( siz));
if (pm) {
    memcpy( pm, p, siz);
}
return pm;
Michael Burr
Windows does not by default enforce it on x64 as far as I know? It does on Itanium, but that's a fairly rare case. Alignment checking can be enforced on both x86 and x64 though, and of course, you take a significant performance hit from unaligned accesses.
jalf
Looks like you're right - I learned to get my code alignment clean when porting stuff to Itanium some time ago. I thought MS kept alignment requirements strict by default on x64, but looks like I'm mistaken there.
Michael Burr
A: 

Both C and C++ allow you to iterate through an array via pointers and ++:

#include <iostream>

int[] arry = { 0, 1, 2, 3 };
int* ptr = arry;
while (*ptr != 3) {
    std::cout << *ptr << '\n';
    ++ptr;
}

For this to work, adding to pointers is defined to take the memory address stored in the pointer and then add the sizeof whatever the type is times the value being added. For instance, in our example ++ptr adds 1 * sizeof(int) to the memory address stored in ptr.

If you have a pointer to a type, and want to advance a particular number of bytes from that spot, the only way to do so is to cast to char* (because sizeof(char) is defined to be one).

Max Lybbert