-6

How to swap values of two pointers without using a temporary variable. By swapping values I mean the address which is stored in each pointer. Pointer can be of any type say int. This is an interview question of Qualcomm. As per my knowledge, only subtraction is allowed with pointers, we cannot perform any other arithmetic operation.

  • 2
    The point of these kinds of questions is not that you know some answer that you give them, it's that you reason intelligently about the question and demonstrate knowledge and command. – David Schwartz May 28 '17 at 04:19
  • So you want us to help you cheat on an interview? – Borgleader May 28 '17 at 04:19
  • 2
    @DavidSchwartz Either that, or the interviewer has no clue. My money is on the second one, any day of the week. – n. m. could be an AI May 28 '17 at 04:21
  • Hint: if we tell you the answer, then other people going for the same interview will know it too :-) – Stephen C May 28 '17 at 04:21
  • Hint: *"only subtraction is allowed with pointers"* is incorrect knowledge ... if you consider converting a pointer to something else. – Stephen C May 28 '17 at 04:22
  • 10
    For example, I would be quite happy to hear an answer like: "I'd just use a temporary variable. I haven't experimented with all the weird corner cases with pointers because it is much more sensible just to avoid them. There's probably a way to do it, it may or may not risk UB, but for the love of all that is holy, why would you ever try this kind of non-optimization?! Any sensible compiler will eliminate the temporary unless it makes more sense not to, in which case why would *I* want to?!" – David Schwartz May 28 '17 at 04:24
  • Hint: XOR. (This is an old "trick" from the 1960's when this kind of space micro-optimization was sometimes necessary. But I agree that asking this as an interview question is probably missing the point.) – Stephen C May 28 '17 at 04:25
  • @DavidSchwartz To save the additional 8 bytes, of course. Memory is scarce these days, don't you know? This is what you call one of those "timeless" interview questions that just never age. – Hiko Haieto May 28 '17 at 04:27
  • @HikoHaieto But,of course, it won't save 8 bytes. If there was a better way to swap pointers, then the compiler would use it if I make it clear that I'm swapping pointers. If I obscure the fact that I'm swapping pointers, it may make it harder for the compiler to employ the optimal method. If the compiler doesn't know the optimal way to swap pointers on the platform, it's a piece of junk and I'd just use a better compiler. :) – David Schwartz May 28 '17 at 04:29
  • 5
    At least in C++, you'd just use `std::swap(ptr1, ptr2);` and be done with it. – Jerry Coffin May 28 '17 at 04:32
  • 3
    @DavidSchwartz But compilers are stupid and bad at finding ways to optimise code, so a single good programmer can always do a better job than the thousands of people that have worked countless hours on compilers over the last several decades. That's why we still have all these micro-optimisations. A thousand interviewers can't be wrong! – Hiko Haieto May 28 '17 at 04:37
  • 2
    @HikoHaieto LOL! Nearly as funny as John's massive assembler answer below:) – ThingyWotsit May 28 '17 at 05:49

2 Answers2

3

Here is a solution that works on amd64 (aka x86_64), or any other 64-bit system where the high 16 address bits are unused (this requirement is not checked in the code!).

#include <cassert>
#include <cstring>
#include <iostream>

// swap two pointers without using any additional space
// this is really a terrible idea and should never be used
template <typename T>
void ptr_swap(T*& p1, T*& p2)
{
    // this only works on amd64, where the high 16 address bits are unused
    static_assert(sizeof(T*) == 8, "only works on amd64");

// swap Nth pair of bytes
#define PTR_SWAP_PAIR(N) \
    memcpy((char*)&p1 + 6, (char*)&p2 + N, 2); \
    memcpy((char*)&p2 + N, (char*)&p1 + N, 2); \
    memcpy((char*)&p1 + N, (char*)&p1 + 6, 2); \

    PTR_SWAP_PAIR(0);
    PTR_SWAP_PAIR(2);
    PTR_SWAP_PAIR(4);

    // restore amd64 pointer invariant (sign extension)
    p1 = (T*)(((intptr_t)p1 << 16) >> 16);
}

int main()
{
    int a = 1234567890;
    int b = 999777555;
    int* pa = &a;
    int* pb = &b;

    ptr_swap(pa, pb);

    assert(pa == &b);
    assert(pb == &a);

    std::cout << *pa << '\n';
    std::cout << *pb << '\n';
}

Reference regarding the high 16 address bits being usable as a scratch pad: Using the extra 16 bits in 64-bit pointers

If you're curious about the assembly code generated for the above:

    movzx   eax, WORD PTR [rsi]
    mov     WORD PTR [rdi+6], ax
    movzx   eax, WORD PTR [rdi]
    mov     WORD PTR [rsi], ax
    movzx   eax, WORD PTR [rdi+6]
    mov     WORD PTR [rdi], ax
    movzx   eax, WORD PTR [rsi+2]
    mov     WORD PTR [rdi+6], ax
    movzx   eax, WORD PTR [rdi+2]
    mov     WORD PTR [rsi+2], ax
    movzx   eax, WORD PTR [rdi+6]
    mov     WORD PTR [rdi+2], ax
    movzx   eax, WORD PTR [rsi+4]
    mov     WORD PTR [rdi+6], ax
    movzx   eax, WORD PTR [rdi+4]
    mov     WORD PTR [rsi+4], ax
    movzx   eax, WORD PTR [rdi+6]
    mov     WORD PTR [rdi+4], ax
    mov     eax, 16
    shlx    rax, QWORD PTR [rdi], rax
    sar     rax, 16
    mov     QWORD PTR [rdi], rax

Versus using std::swap():

    mov     rax, QWORD PTR [rdi]
    mov     rdx, QWORD PTR [rsi]
    mov     QWORD PTR [rdi], rdx
    mov     QWORD PTR [rsi], rax

So yeah, horrible.

Edit: Here's the assembly generated by @Ajay's answer:

    mov     rax, QWORD PTR [rdi]
    xor     rax, QWORD PTR [rsi]
    mov     QWORD PTR [rdi], rax
    xor     rax, QWORD PTR [rsi]
    mov     QWORD PTR [rsi], rax
    xor     QWORD PTR [rdi], rax

Compared to std::swap(), it spends two extra instructions and saves one register (rdx).

John Zwinck
  • 239,568
  • 38
  • 324
  • 436
  • LOL, I'm very impressed :) – ThingyWotsit May 28 '17 at 05:42
  • 1
    Fun fact: modern gcc / clang see through xor-swap ([Why is the XOR swap optimized into a normal swap using the MOV instruction?](https://stackoverflow.com/q/71382441)), and optimize out most of your swapping after inlining, just redoing sign-extension for one, blending one pointer's high half into the other one. https://godbolt.org/z/P553TEE1q. Fun fact #2: modern x86-64 has a PML5 extension to allow 57-bit virtual addresses (an extra 9 bits from an extra level of page tables). – Peter Cordes May 08 '22 at 09:50
3

This simple way will work, take care of typecasting. In my machine its pointer size equals to long.

#include <stdio.h>
int main()
{
    int *x = malloc(sizeof(int)), *y = malloc(sizeof(int));

    printf("Before Swap x: %p y: %p \n", x, y);

    // if x= 5 & y = 10, 
    // then code to swap 'x' (1010) and 'y' (0101)
    x = (int*)((uintptr_t )x ^ (uintptr_t )y); // x = x ^ y, now x becomes 15 (1111)
    y = (int*)((uintptr_t )x ^ (uintptr_t )y); // y = x ^ y, y becomes 10 (1010)
    x = (int*)((uintptr_t )x ^ (uintptr_t )y); // x = X ^ y, x becomes 5 (0101)

    printf("After Swapping: x = %p, y = %p", x, y);

    return 0;
}
Ajay
  • 2,483
  • 2
  • 16
  • 27