4

I recently got curious about pointers and I cannot understand why the following doesn't work.

My idea is to create a variable, say its address (in decimal), ask the user to input it back and take that as the address for a pointer which points to the previous variable. In the end both variables should have the same value.

The last pointer points to supposedly, a bad memory address because it segfaults, but when I print the address of the pointers they are the same!

#include <iostream>

int main() {
    int temp = 0;
    printf("Temporary integer addr 0x%x (dec %d)\n", &temp, &temp);

    int input = -1;
    printf("Give me an address to read (in dec)\n");
    std::cin >> input;
    printf("You chose the number %d (hex 0x%x)\n", input, input);

    int* intPtr = (int*)input;
    int* tempPtr = &temp;

    if (intPtr == tempPtr) {
        printf("It works, they are equal!\n");
    } else {
        printf("Expected: 0x%x got 0x%x\n", tempPtr, intPtr);
        printf("Expected value: 0x%d got 0x%d", temp, *intPtr); // segfaults
    }

    return 0;
}

And this is the console output

Main function addr 0xb0ddf7d0 (dec -1327630384)
Temporary integer addr 0xb0ddf7c8 (dec -1327630392)
Give me an address to read (in dec)
-1327630392
You chose the number -1327630392 (hex 0xb0ddf7c8)
Expected: 0xb0ddf7c8 got 0xb0ddf7c8

Process finished with exit code 139 (interrupted by signal 11: SIGSEGV)

What is wrong with it? How can I create a pointer that points to the address the user inputs? I compiled it on g++ (GCC) 8.2.1 20181127

  • I wonder if gcc is assuming undefined behavior somewhere, so assuming the pointers never match, so removing the check entirely? – Mooing Duck Apr 16 at 20:43
  • Also, this isn't a minimal example. There's no reason for the mainPtr lines – Mooing Duck Apr 16 at 20:44
  • Yeah, sorry that was something I should've removed – jamezrin Apr 16 at 20:44
  • 1
    What happens if you declare your input variable as long long int? – SergeyA Apr 16 at 20:46
  • 4
    Try with std::uintptr_t instead of int, as it's guaranteed to fit a pointer. – François Andrieux Apr 16 at 20:47
8

Your program has undefined behavior. First of all, the format specifier %x will cause printf to expect an argument of type unsigned int. However, you're passing it an argument of type int*. You'd want to use %p for printing that. Most likely, you're working on a 64-Bit machine, which means that addresses will be 64 Bits wide. An int will almost certainly not be large enough to represent all possible addresses. While passing an argument of one type to a function that will interpret the value of that argument as the wrong type is generally undefined behavior, what will most likely happen here in practice is that printf is simply going to print just the lower couple of Bits of your pointer value interpreted as an unsigned int. You then have your user type back in what is effectively only the lower half of your actual address, cast that back to a pointer, and access that memory location, which is almost certainly not going to be a valid address of an object of type int. That's where you get undefined behavior all over again (luckily, this time manifesting itself in a segfaul).

If you want an integer that can represent a pointer value, use, e.g., std::uintptr_t. Also, just don't use C-style casts in C++. They are there for backwards compatibility with C code. There is no advantage to C-style casts but a lot of disadvantages (see, e.g., this, or this, or this, or this question for more). Also, printf is not type safe, don't use printf. Your question is yet another testimony to that (see, e.g., here for more). If you had used std::cout instead, your pointer would have automatically been printed correctly. In general, printf expects you to take care of making sure the types of arguments that are actually passed matche the types specified in your format string. If you make any mistake at all, you end up in undefined behavior land. std::cout, on the other hand, will either do the right thing or fail to compile. Finally, I just can't help myself but note how really, really weird it is to see std::cin being used for input next to printf being used for output…

  • 1
    I saw the internal variables had enough bits, but failed to notice that %x was truncating in the output. Well done! – Mooing Duck Apr 16 at 20:49
  • @ybungalobill on 64-Bit systems, the first couple integer and pointer arguments are generally passed in registers, not on the stack… – Michael Kenzel Apr 16 at 20:56
  • Thank you, that fixed it. I didn't have in mind 64 bit... After using 64 bit ints it works, thank you. – jamezrin Apr 16 at 20:58
  • @ybungalobill it does on all systems I'm aware of. – Michael Kenzel Apr 16 at 20:58
  • 2
    @JaimeMartinezRincon best use std::intptr_t or std::uintptr_t whenever you need an integer type that is always guaranteed to be able to represent a pointer value (ideally, that would be never ;-). Simply switching to some 64-Bit integer type solves the problem only for a particular set of target platforms… – Michael Kenzel Apr 16 at 21:01
2

int won't store a pointer on a 64 bit architecture. Usually you'll need longs (on LP64 architectures; long long is guaranteed to be at least 64 bits wide anywhere), or portably the intptr_t or uintptr_t typedef defined in stdint.h/cstdint if they're available.

If you need a larger type than int and you use int, the addresses will get truncated.

This is working for me:

#include <iostream>
#include <inttypes.h>
#include <stdint.h>

int main() {
    void* mainPtr = (void*) &main;
    printf("Main function addr 0x%" PRIxPTR " (" PRIdPTR " %ld)\n", (uintptr_t)&mainPtr, (uintptr_t)&mainPtr);

    int temp = 0;
    printf("Temporary integer addr 0x%" PRIxPTR " (dec %" PRIdPTR ")\n", (uintptr_t)&temp, (uintptr_t)&temp);

    uintptr_t input = -1;
    printf("Give me an address to read (in dec)\n");
    std::cin >> input;
    printf("You chose the number %" PRIdPTR " (hex 0x%" PRIxPTR ")\n", input, input);

    int* intPtr = (int*)input;
    int* tempPtr = &temp;

    if (intPtr == tempPtr) {
        printf("It works, they are equal!\n");
    } else {
        printf("Expected: 0x%" PRIxPTR " got 0x%" PRIdPTR "\n", (uintptr_t)tempPtr, (uintptr_t)intPtr);
        printf("Expected value: 0x%d got 0x%d", temp, *intPtr); // segfaults
    }

    return 0;
}

Note that the portable way to print these special typedefs is with the PRIxPTR and similar macros defined int inttypes.h/cinttypes, relying on string literal concatenation if you want to use stdio (C++'s std::cout << should print them with less hustle).

  • On my GCC (targeting x86_64) long is 4 bytes. Why not uintptr_t instead? – HolyBlackCat Apr 16 at 20:49
  • @HolyBlackCat I'll fix it up – PSkocik Apr 16 at 20:50
  • @HolyBlackCat By GCC do you refer to MinGW? Are there other x86_64 systems besides Windows that have 32 bit long? – eerorika Apr 16 at 20:52
  • 2
    @SergeyA int32_t isn't required to exist. – melpomene Apr 16 at 21:03
  • 1
    @PSkocik my observation was generic, not tied to use of integers as holders of pointers. – SergeyA Apr 16 at 21:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.