Why do I get "cast from pointer to integer of different size" error?
Asked Answered
S

7

21

The following line (pure c) compiles cleanly on windows (win7 64 bits + codeblocks 13 + mingw32) and debian (wheezy 32 bits + codeblocks 10 + gcc) but raises warning on kali (64 bits + codeblocks + gcc). Any comments? I mean, why do I get this warning, though the same line compiles w/o any warning on windows & debian?

void* foo(void *dst, ...) {
    // some code
    unsigned int blkLen = sizeof(int); // this line ok.
    unsigned int offset = (unsigned int) dst % blkLen; // warning here!
    // some code cont...
}

The message in codeblocks is: "error: cast from pointer to integer of different size [-Werror=pointer-to-int-cast]"

note: my compiler options are -std=c99 -Werror -save-temps (same on all three systems).

edit 2: Though I've managed to have it compiled w/o warning using the preprocessor lines below, @Keith Thompson (see below) has a crucial point about the issue. So, my last decision is using uintptr_t would be a better choice.

edit 1: Thanks for everyone replied. As all the replies note, the problem is a 32 bits vs 64 bits issue. I've inserted following preprocessor lines:

#if __linux__   //  or #if __GNUC__
    #if __x86_64__ || __ppc64__
        #define ENVIRONMENT64
    #else
        #define ENVIRONMENT32
    #endif
#else
    #if _WIN32
        #define ENVIRONMENT32
    #else
        #define ENVIRONMENT64
    #endif
#endif // __linux__

#ifdef ENVIRONMENT64
    #define MAX_BLOCK_SIZE unsigned long long int
#else
    #define MAX_BLOCK_SIZE unsigned long int
#endif // ENVIRONMENT64

and then replaced the problem line as:

unsigned int offset = (MAX_BLOCK_SIZE) dst % blkLen;

Now, everything seems OK.

Snoop answered 7/11, 2014 at 16:14 Comment(1)
on some systems/compilers, the size of a int is different from the size of a pointer. However, for a typical system, the value of offset will be 0...3. so a bit of casting of the result of the modulo to unsigned int would fix the problem.Malvia
H
35

The reason for the warning is that the compiler suspects you might be trying to round-trip a pointer through int and back. This was common practice before the advent of 64-bit machines and it is not safe or reasonable. Of course here the compiler can clearly see that you're not doing this, and it would be nice if it were smart enough to avoid the warning in cases like this, but it's not.

A clean alternative that avoids the warning, and another much nastier issue of wrong result when the converted value is negative, is:

unsigned int offset = (uintptr_t) dst % blkLen;

You'll need to include stdint.h or inttypes.h to have uintptr_t available.

Hendrickson answered 7/11, 2014 at 16:25 Comment(0)
S
17

The problem is that converting a void* pointer to unsigned int is inherently non-portable.

The possible difference in size is only part of the problem. That part of the problem can be solved by using uintptr_t, a type defined in <stdint.h> and <inttypes.h>. uintptr_t is guaranteed to be wide enough that converting a void* to uintptr_t and back again will yield the original pointer value (or at least a pointer value that compares equal to the original one). There's also a type intptr_t, which is signed; usually unsigned types make more sense for this kind of thing. uintptr_t and intptr_t are not guaranteed to exist, but they should exist on any (C99 or later) implementation that has the appropriate integer types.

But even if you have an integer type that's big enough to hold a converted pointer, the result isn't necessarily meaningful for anything other than converting back to a pointer.

The C standard says, in a non-normative footnote, that:

The mapping functions for converting a pointer to an integer or an integer to a pointer are intended to be consistent with the addressing structure of the execution environment.

which is not helpful unless you happen to know what that addressing structure is.

You seem to be trying to determine what the offset of the void* argument is relative to the next lower multiple of blkLen; in other words, you're trying to determine how the pointer value is aligned with respect to blkLen-sized blocks of memory.

If you happen to know that that's a sensible thing to do on the system you're using, that's fine. But you should be aware that arithmetic operations on integers resulting from pointer conversions are still inherently non-portable.

A concrete example: I've worked on systems (Cray vector machines) where a void* pointer is a 64-bit machine address (which points to a 64-bit word), with a 3-bit byte offset inserted by software into the otherwise unused high-order 3 bits. Converting a pointer to an integer simply copied the representation. Any integer arithmetic on such an integer is likely to yield meaningless results unless it takes this (admittedly exotic) representation into account.

Conclusions:

  1. You should definitely use uintptr_t rather than playing preprocessor tricks to determine which integer type you can use. The implementer of your compiler has already done the work of determining an integer type that can safely hold a converted pointer value. There's no need to reinvent that particular wheel. (Caveat: <stdint.h> was added to C by the 1999 ISO standard. If you're stuck using an ancient compiler that doesn't implement it, you might still need to use some kind of #ifdef hacks. But I'd still suggest using uintptr_t if it's available. You can test __STDC_VERSION__ >= 199901L to test for C99 conformance -- though some compilers might support <stdint.h> without fully supporting C99.)

  2. You need to be aware that converting a pointer to an integer and playing with its value is non-portable. That's not to say you shouldn't do it; one of C's greatest strengths is its ability to support non-portable code when that's what you need.

Shortcircuit answered 7/11, 2014 at 19:39 Comment(1)
This might be the most valuable comment concerning the issue. Thanks a lot. I'll take into account your point & re-edit the question, for all followers to make use of it.Snoop
C
6

Because casting a void * to an unsigned int is exactly what that warning is intended to catch because it is unsafe. The pointer can be 64-bit and the int can be 32-bit. For any given platform, the sizeof(unsigned int) is not guaranteed to be sizeof(void *). You should use uintptr_t instead.

Caruso answered 7/11, 2014 at 16:29 Comment(0)
H
2

Maybe because on a 64 bits architecture a pointer is 64 bits long and an int in only 32 bits long ?

You should try

void* foo(void *dst, ...) {
    // some code
    unsigned int blkLen = sizeof(int); // this line ok.
    uintptr_t offset = (uintptr_t) dst % blkLen; // warning here!
    // some code cont...
}
Harrietharriett answered 7/11, 2014 at 16:26 Comment(0)
P
1

I think you get the warning because the size of int depends on the implemetations eg int might be 2 byte long or 4 byte long. This might be the reason for the warning(Please correct me if I am wrong). But anyways why are you trying to do a modulo on a pointer.

Prescience answered 7/11, 2014 at 16:28 Comment(1)
On many but not all systems. See the Cray example in Keith's answer where it would not work.Tinnitus
K
0

Your have make the macro but don't you think its still wrong. Because your pointer will be converted to unsigned long long int or unsigned long int which will be 32 bit and 64 bit in 86x and 64x OS but your variable offset is unsigned int which is 32bit in 64x and 86x OS. So i think you should convert offset also to the respective macro.

Or simply you can convert the pointer to long (i.e. unsigned int to long) and offset to long (i.e. unsigned int to long) also.

Kessiah answered 17/9, 2015 at 7:33 Comment(0)
O
0

An option that also worked for me in other cases was SIZE_T.

size_t data type in C is the unsigned integer type of the result of sizeof, meaning it returns the size in bytes of an object.

Because of this, it is commonly used for array indexing and loop counting and it represents the perfect alternative to int types.

"Programs that use other types, such as unsigned int, for array indexing may fail on, e.g. 64-bit systems when the index exceeds UINT_MAX or if it relies on 32-bit modular arithmetic. "

The code would become:

unsigned int blkLen = sizeof(int); // this line ok.
SIZE_T offset = (SIZE_T) dst % blkLen; // warning here!

I have not tested it though.

Reference: https://en.cppreference.com/w/c/types/size_t

Ovida answered 28/2 at 16:1 Comment(3)
Although the sizes of size_t and uintptr_t will 'mirror' each other (i.e. switch between 32- and 64-bits) on many/most platforms, it is not guaranteed that they will by any Standard, AFAIK. So, it is theoretically possible for a platform to have 64-bit pointers (and uintptr_t) but a 32-bit size_t. Also note that use of the SIZE_T (capitals) macro is also not platform-independent and is most common only on MSVC.Crumb
You explain what size_t is, but what is SIZE_T?Suffolk
Thank you for the clarification. I was definitely referring to size_t. Weird that i find no clear reference about SIZE_T macro. Could you point me to a resource detailing about this and other similar specs ?Ovida

© 2022 - 2024 — McMap. All rights reserved.