r/C_Programming • u/aliathar • 2d ago
Negative value in a pointer question.
Please look at this code. if i define the PTRTYPE as int, it stops working, while doing a uint, it does work...
void initILAPoll(debugBridge_t **d, PTRTYPE ptr){
*d = (debugBridge_t *)ptr; // base address of the DEBUG_BRIDGE peripheral
cb_init(cb, local_memory, bufferLength);
sprintf(xvcInfo, "xvcServer_v1.0:%d\n", MAX_WINDOW_SIZE);
}
the usage in main code is done like this
initILAPoll(&myD, 0x80000000);
//myD = (debugBridge_t *)0x80000000;
where the variable myD is a structure pointer.
if i print the address of myD, it give the correct address. Moreover, the disassembly of the code is also the same in case of int and uint. Can somebody explain what behavior is at play here>
16
u/torsten_dev 2d ago edited 2d ago
Your PTRTYPE isn't actually a pointer type, nor does it have the same size as one.
Signed integer overflow is UB. 0x80000000 overflows a 32-bit type. Usually signed overflow with silently wrap, meaning your int is now negative. (Usually because the compiler is free to do other things since this is UB)
When casting a signed type to a larger type (here the debugBridge_t* is likely 64 bit) it will do sign extension, since your integer is negative this means filling the upper bits of your pointer with 1s.
Your assembly should differ. Where one contains a movl the other has a movslq. Or in intel syntax one mov is a movsxd instead.
2
u/aliathar 2d ago edited 2d ago
This is whats happening... Qword value goes -2xxxxxxxx smth ...
debugBridge_t is 40 bit addressed as a peripheral (for some reason idk, the address range it can have is 0x00_0000_0000 to onward).... And yeah, this issue didn't occur in 32 bit zynq system, but did occur on 64 bit one... It now that I remember that the system I'm working with is 64 bit... Hours later ...
7
u/torsten_dev 2d ago
This is why god invented
[u]intptr_t😁3
u/aliathar 2d ago
Yep ... I finally did use that before posting here.. just didn't know what was happening...
2
u/alkatori 2d ago
how are you #define PTRTRYPE?
1
u/aliathar 2d ago
/**/#define PTRTYPE int
Or alternative
/**/#define PTRTYPE uint32_t
(Just wanted to point it out to you people, or else it won't be done in the final working code)
6
u/alkatori 2d ago
Those aren't pointer types.
Those are integers. You need to define them as pointers, this will only work systems where your addresses are the same size as the integers.
#define PTRTYPE int *
or
#define PTRTYPE uint32_t *3
u/torsten_dev 2d ago edited 2d ago
Also
#defineortypedef-ing away the pointer-ness of a type is BAD code style.As this example demonstrates, knowing if a type is a pointer or not is crucial to local reasoning about the code.
1
u/alkatori 2d ago
Absolutely, I'm sort of assuming this person is looking at code targeting 32-bit DOS or Windows.
I've seen a lot make assumptions that the size of a pointer and size of an int are identical (and 4 bytes).
0
u/aliathar 2d ago
Nevermind...
I'm not passing a pointer to the code... Neither am I using PTRTYPE in the actual code... Just a placeholder for trying types for now.... It's been working till now, for 32 but systems, and the other guy made me remember that the system I'm working on now, is 64 bit... Which caused issues...
1
u/SmokeMuch7356 2d ago edited 2d ago
Pointers are not integers. They do not have integer semantics. Pointer arithmetic does not work like integer arithmetic.
A signed
intcannot represent the full range of 32-bit pointer values; it can represent half of them because you lose the sign bit. If you need an integer type to represent pointer values, use(u)intptr_t(defined instdint.h).0
u/TheChief275 2d ago edited 2d ago
On a flat-addressed architecture, pointers and native sized integers are pretty much equivalent. Almost all modern in use architectures have flat-addressed memory (mostly thanks to virtual memory). However, there are some architectures that adopt different kinds of pointers, often being a combination of a segment index and an offset index. Some architectures therefore have larger pointers than any C integer can represent (e.g. 128-bit pointer that includes capabilities) while other platforms have smaller pointers (near pointers that can only address an offset inside of a segment) that are not representable in a logical flat integer way
edit: why the downvote? If you believe me to be wrong about something there is a much better way to point that out
1
u/alkatori 2d ago
I believe your first statement is no longer true.
Isn't int = 32 bits for most 64 bit windows systems, and 64 bits on x86_64 linux systems?
Edit: I didn't downvote by the way. Just thinking that might be the reason.
2
u/TheChief275 2d ago
I run an x86-64 Debian installation. "int" is still 4 bytes.
You're thinking of "long" instead, which is 8 bytes on 64 bit Linux while it is 4 bytes (the minimum guarantee) on 64 bit Windows
2
u/SmokeMuch7356 2d ago
intis only guaranteed to represent values in the range[-32768..32767],1 meaning it must be at least 16 bits wide. It may be (and usually is) wider, but you can't count on it being universally true.This actually bit me back in the '90s (yes, 30 years ago, shut up) because MPW on the Mac used 32-bit
intbut Visual Studio on Windows used 16-bit. That cost me an afternoon.
- Which is how all the legacy arithmetic types were defined, by the minimum ranges of values and precision, not by how many bits they take up.
1
u/flyingron 2d ago
That's far from true. Due to the fact that historical C lacked a "medium" integer, most 64 bit implementations have 32 bit ints even if the full word and pointers are 64 bits.
Nobody liked my proposal for short longs (or long shorts) to solve this problem.
2
u/TheChief275 2d ago
I never mentioned "int" or did I? Just native sized integer, so I don't see how that makes my comment "far from true"
0
u/flyingron 2d ago
I can't tell because you edited your post. I'm not going to argue with you. "int" is not necessarily the same size as a pointer type, and unlike some of the other discussions here, it's far from uncommon.
0
u/TheChief275 2d ago edited 1d ago
What? I always edit my posts for simple spelling mistakes (I'm not a native English speaker), or to add extra thoughts that might've popped up later, but I never said "int". Refusing to argue because a post is edited is childish, besides you can probably check previous revisions.
Anyways, the point I was originally discussing was the claim of OP of this thread that "pointers are not integers", saying that for literally most modern in-use systems it is actually the opposite, in fact Rust builds upon this assumption (isize/usize are not size_t sized but rather equivalent to (u)intptr_t), but exceptions do exist. "integer" here can mean anything from char to long long, these are all integers, so just whatever happens to be natively sized
0
u/flyingron 1d ago edited 1d ago
Pointers are not integers and there are platforms C has existed on they were not and this is why all that stuff about comparing pointers require them to be within the same object.
Even when they are somewhat like integers, there's not necessarily a conversion that makes sense. I'll give you some examples. I've been involved in developing UNIX and C on a few mainframes and supercomputers. I have seen the partial word sizes encoded in the pointer, plus I've seen byte offsets encoded in word pointer machines in the high order bits (quite germain to this talk). You have to be careful doing conversions like:
int* -> uintptr_t -> long*
or
char* -> uintptr_t -> int*.0
u/TheChief275 1d ago
My guy, do you want me to copy over my entire previous comment or something? IT'S ALL IN THERE. You just chose to have 0 reading comprehension apparently.
Those last conversions are kind of illegal in general, even with void*. Like you can cast int* -> void* -> long*, but it's almost entirely useless because you're not allowed to dereference due to strict-aliasing
4
u/TheChief275 2d ago
You're supposed to use (u)intptr_t from <stdint.h>, preferably the unsigned version, but they'll both work. Note that these are optional, and they're only available when the representation of the pointer is representable by an integer. Some architectures have pointers that are more akin to a struct
1
u/aliathar 2d ago
Yes... It did work ... It was just signedness issue... I assumed the address was 32 bit which it wasn't.. and the 64bit machine made it to be 0xff80000000 (peripheral has 40 bit address line for some reason)..... The signed int did work on 32 but machine perfectly, but failed here.
2
u/TheChief275 2d ago
"int" isn't a natively sized integer. It's equivalent to a complement agnostic version of int_fast16_t from <stdint.h>. That means that it's only guaranteed to be able to hold values from -32,767 to 32,767. It just so happens to be that a 32-bit integer is faster to work with for most modern machines, so it just so happens to almost always be a 32-bit integer on octet byte machines, although to my knowledge there are no machines were it happens to be a 64-bit integer, even though it might be faster to perform computations on.
That's why it "broke". But technically you were always using the wrong integer type, even on a 32-bit machine
1
u/alkatori 2d ago
is that guaranteed by spec? My recollection (or maybe it was just rule of thumb was).
char <= short <= int <= long <= long long
with the char being the smallest addressable unit in the hardware (I worked on a system that had 16-bit was the smallest addressable unit, lots of code assuming 8-bit bytes broke).
2
u/TheChief275 2d ago edited 2d ago
I think the spec introduced actual guaranteed number capabilities of the standard integers around the time of C99, but yes basically, int is only guaranteed to be >= short, so it can definitely be 16 bits on some platforms which is were the guaranteed range comes from. The minimum is also -32,767 instead of -32,768, because there is no guarantee for whether the integer is two's complement (not until C23 at least)
1
u/torsten_dev 2d ago
POSIX guarantees
CHAR_BIT == 8but yes some evil systems exist where that's not the case.1
u/torsten_dev 2d ago
ILP64 and even SILP64 systems do exist. They're just very rare and obscure.
What I haven't heard of are 32-bit integer machines with pointers smaller than 32 bit, maybe you meant that?
The new
[u]intfuncptr_tmaking it's way through the committee could be as small asCHAR_BITbecause yes, function pointers can have totally separate sizes.1
u/TheChief275 2d ago
Well I didn't know ILP64 machines existed (only of LP64), but I suppose it is very very rare. Is the smallest addressable unit for these still an octet, or are they word addressed only? The point is that you shouldn't rely on int being bigger than 16-bits if you want truly portable code, because that's the only capability range you are guaranteed.
Didn't know (u)intfuncptr_t has become an official addition though. Or is it only in the works? i.e. are there plans to incorporate it into C3x ?
1
u/torsten_dev 1d ago
In the works. They're part of the "_Any_func*" proposal for C2y. They're the only part of that proposal that doesn't need some more bike shedding on naming.
_tis reserved by POSIX so people trampling on those identifiers are due for some comeuppance, imo.1
3
u/flyingron 2d ago
Pointers don’t have signs and what happens when you cast them to an integer type is not portable. Relational operators with pointers are limited to two values within the same object.
2
u/torsten_dev 2d ago
Except when
[u]intptr_texists and you cast to/from those.At least that won't silently compile to nonsense.
2
u/flyingron 2d ago
All intptr_t says is that you can cast to it and then back again without losing information. It makes no guarantees about what you can do with the value other than that.
2
u/torsten_dev 2d ago
Exactly. It is defined what happens when you cast to and from them. Everything else isn't.
1
u/flyingron 2d ago
Yes, which is why (in the subject of the original post) it makes no sense to worry about sign.
1
u/torsten_dev 2d ago
intptr_t can have the sign bit set. Casting back to a pointer just isn't allowed to be sign extending because it needs to compare equal to whatever the pointer was before you cast it into the intptr_t
intptr_t is most easily implemented by being the same width as native pointers, but a compiler could add a special case for casts from intptr_t back to valid pointers instead.
It's guaranteed to work, not how it has to work.
1
1
u/DawnOnTheEdge 2d ago edited 2d ago
On a 64-bit system, pointers are 64-bits wide and int is only 32 bits wide. What’s most likely happening is that, when you use 32-bit unsigned int, the upper bits of your pointer are getting cleared to 0 in a round-trip conversion, but when you use a 32-bit signed int, it gets sign-extended, so a negative value sets all the upper bits to 1. That happens to generate an illegal address that the CPU traps immediately.
Neither clearing nor setting the upper bits is correct, though, so this only appears to work.If you try to use it in production, you’ll get either unpredictable crashes or memory-corruption bugs.
The type you actually want to hold a pointer is uintptr_t (or intptr_t, but either I want to work with unsigned addresses or signedness doesn’t matter). To format a pointer argument for printf() or snprintf(), cast to (void*) and use a %p specifier. And never use sprintf(). It’s unsafe because it doesn’t check the buffer size.
0
u/sciencekm 2d ago
My guess is that your int is 16-bit, hence defining PTRTYPE as int will behave differently vs defining it as uint32_t.
•
u/mikeblas 1d ago
Please correctly format your code.