14

Is there an integer type with the same size as pointer? Guaranteed on all microarchitectures?

  • 2
    *Why* do you want to store a pointer in an integer? It's not plain to me that this is a good idea even if it were guaranteed to work (which it isn't). – paxdiablo Feb 02 '09 at 11:06
  • Optimisation. Single access to the memory/cache instead of two separate accesses. –  Feb 02 '09 at 11:30
  • 3
    One good reason to do this seems to be bitwise manipulation. Bitwise operators don't seem to work on pointers. For instance, if you wanted to implement an XOR linked list or wanted to mask out some of the bits (to get a page address maybe), you need to convert to an integer first. – Jay Conrod May 04 '09 at 22:06

10 Answers10

16

According to this Wikipedia page, in C99 your stdint.h header might declare intptr_t and uintptr_t, but then that of course requires

  • C99
  • A compiler implementor which has chosen to implement this optional part of the standard

So in general I think this one is tough.

unwind
  • 391,730
  • 64
  • 469
  • 606
  • GCC implements it; for Visual Studio, try http://www.azillionmonkeys.com/qed/pstdint.h or http://code.google.com/p/msinttypes/ – Christoph Feb 02 '09 at 11:03
  • There's the MinGW version as well: http://cygwin.com/cgi-bin/cvsweb.cgi/src/winsup/mingw/include/stdint.h?rev=1.8&content-type=text/x-cvsweb-markup&cvsroot=src – Christoph Feb 02 '09 at 11:16
  • 1
    The [standard](http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf) does **NOT** guarantee that they are the same size; only that you can cast to (`u`)`intptr_t` and back again (provided the memory it references is still valid), and that a null pointer gets converted to 0. On "sensible" architectures it will just be a memory address, but this is by no means guaranteed (e.g. the cast could randomly permute the bits, there's no guarantee that you can infer alignment from the `uintptr_t` representation, pointer arithmetic doesn't need to work...). – tc. Jun 11 '12 at 17:32
  • @tc. Since when does the C standard guarantee that a null pointer converts to a zero integer? – Deduplicator Aug 24 '20 at 23:12
  • Huh, indeed it does not. C89/99 only seems to guarantee that converting an "integer constant expression with the value 0" to a pointer gives a null pointer, so e.g. `int x = 0; if ((void*)x) { ... }` is implementation-defined. It would be really weird if behaviour changed drastically the moment the compiler decided something was no longer constant, though. – tc. Jun 19 '21 at 20:28
15

Simply put, no. Not guaranteed on all architectures.

My question is: why? If you want to allocate a type big enough to store a void*, the best thing to allocate is (surprisingly enough :-) a void*. Why is there a need to fit it within an int?

EDIT: Based on your comments to your duplicate question, you want to store special values of the pointer (1,2,3) to indicate extra information.

NO!! Don't do this!!. There is no guarantee that 1, 2 and 3 aren't perfectly valid pointers. That may be the case in systems where you're required to align pointers on 4-byte boundaries but, since you asked about all architectures, I'm assuming you have portability as a high value.

Find another way to do it that's correct. For example, use the union (syntax from memory, may be wrong):

typedef struct {
    int isPointer;
    union {
        int intVal;
        void *ptrVal;
    }
} myType;

Then you can use the isPointer 'boolean' to decide if you should treat the union as an integer or pointer.

EDIT:

If execution speed is of prime importance, then the typedef solution is the way to go. Basically, you'll have to define the integer you want for each platform you want to run on. You can do this with conditional compilation. I would also add in a runtime check to ensure you've compiled for each platform correctly thus (I'm defining it in the source but you would pass that as a compiler flag, like "cc -DPTRINT_INT"):

#include <stdio.h>
#define PTRINT_SHORT
#ifdef PTRINT_SHORT
    typedef short ptrint;
#endif
#ifdef PTRINT_INT
    typedef int ptrint;
#endif
#ifdef PTRINT_LONG
    typedef long ptrint;
#endif
#ifdef PTRINT_LONGLONG
    typedef long long ptrint;
#endif

int main(void) {
    if (sizeof(ptrint) != sizeof(void*)) {
        printf ("ERROR: ptrint doesn't match void* for this platform.\n");
        printf ("   sizeof(void*    ) = %d\n", sizeof(void*));
        printf ("   sizeof(ptrint   ) = %d\n", sizeof(ptrint));
        printf ("   =================\n");
        printf ("   sizeof(void*    ) = %d\n", sizeof(void*));
        printf ("   sizeof(short    ) = %d\n", sizeof(short));
        printf ("   sizeof(int      ) = %d\n", sizeof(int));
        printf ("   sizeof(long     ) = %d\n", sizeof(long));
        printf ("   sizeof(long long) = %d\n", sizeof(long long));
        return 1;
    }

    /* rest of your code here */

    return 0;
}

On my system (Ubuntu 8.04, 32-bit), I get:

ERROR: ptrint typedef doesn't match void* for this platform.
   sizeof(void*    ) = 4
   sizeof(ptrint   ) = 2
   =================
   sizeof(short    ) = 2
   sizeof(int      ) = 4
   sizeof(long     ) = 4
   sizeof(long long) = 8

In that case, I'd know I needed to compile with PTRINT_INT (or long). There may be a way of catching this at compile time with #if, but I couldn't be bothered researching it at the moment. If you strike a platform where there's no integer type sufficient for holding a pointer, you're out of luck.

Keep in mind that using special pointer values (1,2,3) to represent integers may also not work on all platforms - this may actually be valid memory addresses for pointers.

Still ,if you're going to ignore my advice, there's not much I can do to stop you. It's your code after all :-). One possibility is to check all your return values from malloc and, if you get 1, 2 or 3, just malloc again (i.e., have a mymalloc() which does this automatically). This'll be a minor memory leak but it'll guarantee no clashes between your special pointers and real pointers.

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • The problem here is that you have to check the memory/cacheline twice in the union soltion (for isPointer and ptrVal). This is the innermost loop of the cycle - nanoseconds make a *real* difference. –  Feb 02 '09 at 11:42
  • You *can* store all the necessary information in a pointer field, but *only* if you throw out the portability-concern. Special case your code if you really need this. – Magnus Hoff Feb 02 '09 at 11:48
  • Ok, understood. I cannot have both portability and micro-optimisation. Still, is anyone aware of a platform where malloc can actually return values of 1, 2 or 3? –  Feb 02 '09 at 11:54
  • See updates to answwer, Martin. If you really want to avoid clashes, code your own mymalloc() which doesn't pass through those addresses. It's a kludge, but it should work. – paxdiablo Feb 02 '09 at 11:58
  • 1
    Pax, thanks for the answer. However, let me suggest a different way to solve the problem: unsigned char *dummy = malloc (4); void *special_value1 = dummy + 0; .... This would keep the special values in L1d cache rather than in L1i, but I think I can do with that. –  Feb 02 '09 at 12:11
  • Checks like this should preferably be compile-time asserts instead of having to be executed to detect a problem. – Erik Ekman Oct 19 '12 at 15:00
  • Erik, the runtime checks are just an _added_ protection against the developer entering the wrong type during compilation and only needed until your makefile (or other build tool) is configured correctly - I tend to wrap the whole thing in an `#ifdef DEVMODE` macro so it doesn't affect production code. – paxdiablo Oct 19 '12 at 22:07
13

The C99 standard defines standard int types:

7.18.1.4 Integer types capable of holding object pointers The following type designates a signed integer type with the property that any valid pointer to void can be converted to this type, then converted back to pointer to void, and the result will compare equal to the original pointer:

    intptr_t

The following type designates an unsigned integer type with the property that any valid pointer to void can be converted to this type, then converted back to pointer to void, and the result will compare equal to the original pointer:

   uintptr_t

These types are optional.

C99 also defines size_t and ptrdiff_t:

The types are

  ptrdiff_t

which is the signed integer type of the result of subtracting two pointers;

  size_t

which is the unsigned integer type of the result of the sizeof operator; and

The architectures I've seen have the maximum size of an object equal to the whole memory, so sizeof(size_t) == sizeof(void*), but I'm not aware of anything that is both portable to C89 ( which size_t is ) and guaranteed to be large enough ( which uintptr_t is ).

Pete Kirkham
  • 48,893
  • 5
  • 92
  • 171
4

This would be true on a standard 32 bit system, but there certainly are no guarantees, and you could find lots of architectures where it isn't true. For example, a common misconception is that sizeof(int) on x86_64 would be 8 (since it's a 64 bit system, I guess), which it isn't. On x86_64, sizeof(int) is still 4, but sizeof(void*) is 8.

Liedman
  • 10,099
  • 4
  • 34
  • 36
2

The standard solution to this problem is to write a small program which checks the sizes of all int types (short int, int, long int) and compares them to void*. If there is a match, it emits a piece of code which defines the intptr type. You can put this in a header file and use the new type.

It's simple to include this code in the build process (using make, for example)

Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820
1

No, the closest you will come to a portable pointer-capable integer type would be intptr_t and ptrdiff_t.

Ronny Vindenes
  • 2,361
  • 1
  • 18
  • 15
0

No.

I do not believe the C standard even specifies standard int sizes. Combine that with all the architectures out there (8/16/32/64bit etc) and there is no way to guarantee anything.

Rob McCready
  • 1,909
  • 1
  • 19
  • 20
  • That's right it just requires sizeof(short) <= sizeof(int) <= sizeof(long), but the guarantees are there if one uses the stdint.h provided limits – Friedrich Feb 02 '09 at 12:32
0

int data type would be the answer on most architectures.

But thre is NO guarantee to this for ANY (micro)architecture.

AlexDrenea
  • 7,981
  • 1
  • 32
  • 49
  • 1
    But note that `int` in general is 32 bits on x86-64, so in some cases you might rather want to use `long`. – Magnus Hoff Feb 02 '09 at 11:00
  • Using int on a 64-bit machine would be wrong except in the exceptional case of ILP64. DEC Alpha was the leading example of ILP64; I'm not aware of any current architectures using ILP64 rather than LP64 or (for Windows 64) LLP64. – Jonathan Leffler Feb 03 '09 at 04:46
0

The answer seems to be "no", but if all you need is a type that can act as both, you can use a union:

union int_ptr_t {
    int i;
    void* p;
};
Magnus Hoff
  • 21,529
  • 9
  • 63
  • 82
  • Though you do have to be careful with alignment issues here. For example, if void* is 64-bits and int is 32-bits, which 32-bits of the void* are matched with the int? – Zooba Feb 02 '09 at 11:00
  • 1
    Of course, there is no guarantee here that you can access all the bits of the pointer member through the integer member. That is hopefully obvious, but I thought I'd point it out anyway. – unwind Feb 02 '09 at 11:00
0

Usually sizeof(*void) depends on memory bus width (although not necessarily - pre-RISC AS/400 had 48-bit address bus but 64-bit pointers), and int usually is as big as CPU's general-purpose register (there are also exceptions - SGI C used 32-bit ints on 64-bit MIPS).

So there is no guarantee.

qrdl
  • 34,062
  • 14
  • 56
  • 86