【问题标题】:Swap Pointer values交换指针值
【发布时间】:2017-05-28 04:16:02
【问题描述】:

如何在不使用临时变量的情况下交换两个指针的值。通过交换值,我指的是存储在每个指针中的地址。指针可以是任何类型,比如 int。这是高通的面试题。据我所知,指针只允许减法,我们不能执行任何其他算术运算。

【问题讨论】:

  • 这类问题的重点不是你知道你给出的答案,而是你对问题的智能推理并展示知识和命令。
  • @DavidSchwartz 要么,要么面试官不知道。我的钱在第二个,一周中的任何一天。
  • 例如,我会很高兴听到这样的回答:“我只是使用一个临时变量。我没有用指针尝试所有奇怪的极端情况,因为它更多避免它们是明智的。可能有办法做到这一点,它可能会或可能不会冒 UB 的风险,但是为了所有神圣的事物的爱,你为什么要尝试这种非优化?!任何明智的编译器都会消除暂时的,除非不这样做更有意义,在这种情况下,为什么想要?!”
  • 至少在 C++ 中,你只需使用 std::swap(ptr1, ptr2); 就可以了。
  • @DavidSchwartz 但是编译器是愚蠢的,而且不善于寻找优化代码的方法,所以一个优秀的程序员总能比过去数以千计在编译器上工作了无数小时的人做得更好几十年。这就是为什么我们仍然有所有这些微优化。一千名面试官都不会错!

标签: c++ c pointers swap


【解决方案1】:

这种简单的方法可以工作,注意类型转换。在我的机器中,它的指针大小等于 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;
}

【讨论】:

  • 如果您使用uintptr_t 而不是long,您可以避免一些可移植性问题。
  • 很漂亮。我将相应的汇编代码发布到我的答案中。
【解决方案2】:

这是一个适用于 amd64(又名 x86_64)或任何其他 64 位系统的解决方案,其中高 16 位地址位未使用(代码中未检查此要求!)。

#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';
}

关于地址高 16 位可用作暂存器的参考:Using the extra 16 bits in 64-bit pointers

如果你对上面生成的汇编代码感到好奇:

    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

相对于使用std::swap()

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

是的,太可怕了。

编辑:这是@Ajay 的回答生成的程序集:

    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

std::swap()相比,它花费了两条额外的指令并节省了一个寄存器(rdx)。

【讨论】:

  • 大声笑,我印象深刻:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-21
  • 2012-10-14
  • 2014-08-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多