C - Why cast to uintptr_t vs char* when doing pointer arithmetic
Asked Answered
L

1

10

I am working on a programm where I have to modify the target process memory/ read it.

So far I am using void* for storing adresses and cast those to char* if I need to change them (add offset or modify in general)

I have heard of that type defined in stdint.h but I don't see the difference in using it for pointer arithmetic over the char* conversion (which seems more C89 friendly atleast to me)

So my question: What of those both methods should I use for pointer arithmetic? Should I even consider using uintptr_t over char* in any case?

EDIT 1

Basically I just need to know if this yields

0x00F00BAA hard coded memory adress in target
process
void* x = (void*)0x00F00BAA;
char* y = (void*)0x00F00BAA;
x = (uintptr_t)x + 0x123;
y = (char*)y + 0x123;

x == y?
x == (void*)0x00F00CCD?
y == (void*)0x00F00CCD?
Legend answered 14/4, 2017 at 20:15 Comment(8)
I think the standard doesn't guarantees that an arithmetic modification of a uintptr_t variable followed by a cast to char* will get you the same result as will applying the math directly to the char* the uintptr_t was derived from, although I think it'd be crazy if this didn't hold.Markswoman
@PSkocik of course it doesn't hold, and it is not crazy at all.Purdum
@AnttiHaapala If I move a uintptr_t by 3, I'd also expect the corresponding char* to move by 3. I don't see why a flat-memory-model platform would ever want to do something different.Markswoman
See edit for simple example what I try to accomplishLegend
@MuradBabayev Th standard gives you no such guarantee (only port70.net/~nsz/c/c11/n1570.html#7.20.1.4 ) , but practically it should hold.Markswoman
But if the C standard guarantees that a char is 1 Byte big, and I am doing pointer arithmetic on a char pointer with byte size offset (+0x42 == 0x42 * sizeof(char)) shouldn't my example be valid?Legend
@MuradBabayev: No. See my answer.Dishevel
So you have a "target process". Are you trying to manipulate addresses within the target process in code running in a different process? If so my answer might be incorrect. If you're dealing with multiple processes, your code already isn't 100% portable; it might be best to accept that and just be clear about what non-portable assumptions you're making. How are the processes related?Dishevel
D
8

In comments user R.. points out that the following is likely incorrect if the addresses the code is dealing with are not valid within the current process. I've asked the OP for clarification.

Do not use uintptr_t for pointer arithmetic if you care about the portability of your code. uintptr_t is an integer type. Any arithmetic operations on it are integer arithmetic, not pointer arithmetic.

If you have a void* value and you want to add a byte offset to it, casting to char* is the correct approach.

It's likely that arithmetic on uintptr_t values will work the same way as char* arithmetic, but it absolutely is not guaranteed. The only guarantee that the C standard provides is that you can convert a void* value to uintptr_t and back again, and the result will compare equal to the original pointer value.

And the standard doesn't guarantee that uintptr_t exists. If there is no integer type wide enough to hold a converted pointer value without loss of information, the implementation just won't define uintptr_t.

I've actually worked on systems (Cray vector machines) where arithmetic on uintptr_t wouldn't necessarily work. The hardware had 64-bit words, with a machine address containing the address of a word. The Unix-like OS needed to support 8-bit bytes, so byte pointers (void*, char*) contained a word address with a 3-bit offset stored in the otherwise unused high-order 3 bits of the 64-bit word. Pointer/integer conversions simply copied the representation. The result was that adding 1 to a char* pointer would cause it to point to the next byte (with the offset handled in software), but converting to uintptr_t and adding 1 would cause it to point to the next word.

Bottom line: If you need pointer arithmetic, use pointer arithmetic. That's what it's for.

(Incidentally, gcc has an extension that permits pointer arithmetic on void*. Don't use it in portable code. It also causes some odd side effects, like sizeof (void) == 1.)

Dishevel answered 14/4, 2017 at 21:32 Comment(22)
I assume casting from a void* to a char* on such an architecture would convert pointer representations. But, what happens on the conversion back? Would converting an unaligned char* back to void* trap?Birthwort
This answer is maybe right for a different question, but completely wrong for OP's question. OP is considering using a pointer type, and pointer arithmetic on these pointers, to store and manipulate addresses in a "target process" (of some sort of debugging/memory-peeking/poking, I assume). Using pointers for this is invalid. Pointers can only point to existent objects in the address space of the program itself, not some other process, and arithmetic on them is only valid as long as it stays within the object pointed into.Korikorie
Note further that while uintptr_t may work (and it's not subject to the undefinedness issues void * would be subject to), it also assumes the debugger and target process use the same ISA/ABI, which in general is a poor assumption to make when designing this type of software. Instead you should probably use an integer type chosen to match the (target-implementation-defined) pointer representation in the target process. On ELF systems, types like Elf32_Addr/Elf64_Addr would be appropriate, for example.Korikorie
@TrentP: The standard guarantees that void* and char* (and unsigned char* and signed char*) have the same representation and alignment requirements.Dishevel
@R..: Hmm, you may be right. I'll ask the OP for clarification.Dishevel
@KeithThompson: Isn't it required that void* can be converted to any pointer type? In which case, the representation issue would still arise if an unaligned void* were converted to a int*. That's hardly uncommon, plenty of architectures, even though all pointers have the same format, will trap when using an unaligned pointer. But would this machine trap on the pointer conversion?Birthwort
The way my program works is not clear yet. Currently I am reading the targets memory trough ReadProcessMemory (which of course can't be defined portably because I am reading another process memory). I also think about using DLL injection instead of manipulating and reading trough a handle to the process. The common workflow I need to do (which has to ensure that the offset addition works as supposed) is adding a offset to the adress, dereferencing and reading the value (which itself is a new adress), adding a offset to the new adress amd dereferencing this again to get a new value and so on.Legend
@TrentP: Yes, void* can be converted to or from any object pointer type (that doesn't apply to function pointers), but the result might not be aligned correctly. Alignment is not an issue for char*. C11 6.3.2.3p7: "A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned for the referenced type, the behavior isundefined." So yes, converting an unaligned void* to int* can trap -- or it might not trap until you try to dereference it.Dishevel
I've settled down on using usual pointer arithmetic on char* and so far it works (at least on my windows machine).Legend
Many sources recommend using uintptr_t for pointer arithmetic though. I used it all the time and it worked on both linux and windows. At least when using C++Maud
@Maud What sources? Sure, it "works" on systems where pointers behave the way you expect them to, but neither C nor C++ guarantees that. And arithmetic is defined on pointers, so why would you need to convert to uintptr_t and back again?Dishevel
e.g. "While, for the majority of modern platforms, you can assume a flat address space and that arithmetic on uintptr_t is equivalent to arithmetic on char *, it's entirely possible for an implementation to perform any transformation when casting void * to uintptr_t as long the transformation can be reversed when casting back from uintptr_t to void *."Maud
riptutorial.com/cplusplus/example/15789/…Maud
Many more sources saying that the behaviour should mostly be the same.Maud
So I think if you are not developing for some weird, niche platform and using modern compiler you should be fine. But from your experience I think you are right that char* is more portable.Maud
@Maud Are there any sources that actually recommend doing arithmetic using uintptr_t? Doing so makes the code more complicated and less reliable than using built-in pointer arithmetic, which is guaranteed to work correctly. (One possible exception is examining a pointer value to determine its alignment, for example (uintptr_t)ptr % 8; that's non-portable but might be necessary at times.)Dishevel
see e.g. definition for ptrdiff_t Maud
@Maud Was that intended to answer my question? (ptrdiff_t is the type of the result of subtracting two pointers.)Dishevel
"The intptr_t and uintptr_t types are extremely useful for casting pointers when you want to perform address arithmetic. Use intptr_t and uintptr_t types instead of long or unsigned long for this purpose. "Maud
Google the line above and you will find the site that actually recommends using them, and there are few more, and you can often see people using these typedefs for pointer arithmetic.Maud
And when you google "intptr_t and uintptr_t" it's a top resultMaud
@Maud You could have posted the link. It's the Solaris 64-bit Developer's Guide. It recommends using uintptr_t over using unsigned long, which is correct as far as it goes. And whoever wrote that page (at least 10 years ago) should have recommended just using pointer arithmetic directly whenever possible. Perhaps "many sources" recommend converting to uintptr_t instead of using pointer arithmetic. If so, many sources are wrong.Dishevel

© 2022 - 2024 — McMap. All rights reserved.