2

以下代码在 32 位和 64 位系统上打印不同的结果:

#include <stdio.h>
void swapArray(int **a, int **b) 
{ 
    int *temp = *a; 
    *a = *b; 
    *b = temp; 
} 

int main() 
{ 
    int a[2] = {1, 3}; 
    int b[2] = {2, 4}; 
    swapArray(&a, &b); 
    printf("%d\n", a[0]); 
    printf("%d\n", a[1]); 
    return 0; 
}

在 32 位系统中编译后,输出为:

2
3

在 64 位上,输出为:

2
4

据我了解,该函数swapArray只是交换指向 and 中第一个元素的a指针b。所以调用后,swapArray应该a指向应该指向。 出于这个原因,应该让步,并且应该引用内存中的位置之后的下一个字节,其中包含.2b1
a[0]2a[1]24

谁能解释一下?

编辑:
感谢评论和答案,我现在注意到&aand&b是 typeint (*)[]而不是int **。这显然使代码不正确(实际上我收到了编译器警告)。然而,有趣的是,为什么编译器 (gcc) 只给出警告而不是错误。
我仍然想知道是什么导致了不同系统上的不同结果,但是由于代码不正确,因此相关性较低。

编辑 2:
至于不同系统上的不同结果,我建议阅读AndreyT 的评论

4

3 回答 3

5

在回答您的问题之前,让我们看看在指针操作期间会发生什么。我正在使用一个非常简单的代码来演示这一点:

#include <stdio.h>

int main() {
        int *p;
        int **p2;
        int x = 3;
        p = &x;
        p2 = &p;
        return 0;
}

现在看反汇编:

(gdb) disassemble 
Dump of assembler code for function main:
   0x0000000000400474 <+0>: push   rbp
   0x0000000000400475 <+1>: mov    rbp,rsp
   0x0000000000400478 <+4>: mov    DWORD PTR [rbp-0x14],0x3
   0x000000000040047f <+11>:    lea    rax,[rbp-0x14]
   0x0000000000400483 <+15>:    mov    QWORD PTR [rbp-0x10],rax
   0x0000000000400487 <+19>:    lea    rax,[rbp-0x10]
   0x000000000040048b <+23>:    mov    QWORD PTR [rbp-0x8],rax
=> 0x000000000040048f <+27>:    mov    eax,0x0
   0x0000000000400494 <+32>:    leave  
   0x0000000000400495 <+33>:    ret

拆卸是不言而喻的。但是这里需要补充几点,

我的函数的堆栈帧从这里开始:

   0x0000000000400474 <+0>: push   rbp
   0x0000000000400475 <+1>: mov    rbp,rsp

所以让他们现在拥有的

(gdb) info registers $rbp
rbp            0x7fffffffe110   0x7fffffffe110

在这里,我们将值3放入[rbp - 0x14]'s 地址。让我们看看内存映射

(gdb) x/1xw $rbp - 0x14
0x7fffffffe0fc: 0x00000003

重要的是要注意DWORD使用了 32 位宽的数据类型。所以在旁注中,整数文字3被视为 4 字节单位。

下一条指令用于lea加载前面指令中刚刚保存的值的有效地址。

0x000000000040047f <+11>:   lea    rax,[rbp-0x14]

这意味着 now$rax将具有价值0x7fffffffe0fc

(gdb) p/x $rax
$4 = 0x7fffffffe0fc

接下来我们将使用这个地址保存到内存中

0x0000000000400483 <+15>:   mov    QWORD PTR [rbp-0x10],rax

重要的是要注意QWORD这里使用的 a。因为 64 位系统有 8 字节的本机指针大小。0x14 - 0x10 = 4字节在前面的mov指令中使用。

接下来我们有:

   0x0000000000400487 <+19>:    lea    rax,[rbp-0x10]
   0x000000000040048b <+23>:    mov    QWORD PTR [rbp-0x8],rax

这又是第二次间接。与地址相关的所有值总是QWORD. 重要的是要注意这一点。

现在让我们来看看你的代码。

在调用 swaparray 之前,您有:

=> 0x00000000004004fe <+8>: mov    DWORD PTR [rbp-0x10],0x1
   0x0000000000400505 <+15>:    mov    DWORD PTR [rbp-0xc],0x3
   0x000000000040050c <+22>:    mov    DWORD PTR [rbp-0x20],0x2
   0x0000000000400513 <+29>:    mov    DWORD PTR [rbp-0x1c],0x4
   0x000000000040051a <+36>:    lea    rdx,[rbp-0x20]
   0x000000000040051e <+40>:    lea    rax,[rbp-0x10]
   0x0000000000400522 <+44>:    mov    rsi,rdx
   0x0000000000400525 <+47>:    mov    rdi,rax

这是非常微不足道的。您的数组已初始化,&当数组开头的有效地址加载到$rdiand时,运算符的效果可见$rsi

现在让我们看看它在里面做什么swaparray()

数组的开头保存在$rdiand$rsi中。所以让我们看看他们的内容

(gdb) p/x  $rdi
$2 = 0x7fffffffe100
(gdb) p/x  $rsi
$3 = 0x7fffffffe0f0



   0x00000000004004c8 <+4>: mov    QWORD PTR [rbp-0x18],rdi
   0x00000000004004cc <+8>: mov    QWORD PTR [rbp-0x20],rsi

现在按照以下说明执行第一条语句int *temp = *a

   0x00000000004004d0 <+12>:    mov    rax,QWORD PTR [rbp-0x18]
   0x00000000004004d4 <+16>:    mov    rax,QWORD PTR [rax]
   0x00000000004004d7 <+19>:    mov    QWORD PTR [rbp-0x8],rax

现在到了决定性的时刻,你的*a?

  1. 它加载到$rax存储在[rbp - 0x18]. $rdi保存值的位置。它又保存第一个数组的第一个元素的地址。
  2. 通过使用存储到的地址执行另一个间接$rax获取 aQWARD并将其加载到$rax. 那么它会返回什么呢?它将返回一个QWARDfrom 0x7fffffffe100。这实际上将由保存在那里的两个四字节数量形成一个 8 字节数量。详细说明,

那里的记忆如下。

(gdb) x/2xw $rdi
0x7fffffffe100: 0x00000001  0x00000003

现在如果你拿一个QWORD

(gdb) x/1xg $rdi
0x7fffffffe100: 0x0000000300000001

所以你实际上已经被搞砸了。因为您正在获取不正确的边界。

其余代码可以用类似的方式解释。

现在为什么它在 32 位平台上有所不同?因为在 32 位平台中,本机指针宽度为 4 个字节。所以这里的情况会有所不同。您的语义不正确代码的主要问题源于整数类型宽度和本机指针类型的差异。如果两者都相同,您仍然可以解决您的代码。

但是你永远不应该编写假定本机类型大小的代码。这就是标准适用的原因。这就是你的编译器给你警告的原因。

从语言的角度来看,它的类型不匹配已经在前面的答案中指出,所以我不打算讨论。

于 2012-07-28T21:31:04.283 回答
5
swapArray(&a, &b); 

&a并且&b不是 typeint **而是 type int (*)[2]。顺便说一句,您的编译器足以接受您的程序,但编译器有权拒绝翻译它。

于 2012-07-28T19:29:01.773 回答
3

您不能使用指针技巧交换数组(它们不是指针!)。您要么必须创建指向这些数组的指针并使用指针,要么使用 malloc 等动态分配数组。

我在 64 位系统上得到的结果与你的不同,例如,我得到:

2
3

test: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.8, not stripped

在我的 mac 上使用 clang 我得到一个错误:

test.cpp: In function ‘int main()’:
test.cpp:13: error: cannot convert ‘int (*)[2]’ to ‘int**’ for argument ‘1’ to ‘void swapArray(int**, int**)’

我认为这是未定义的行为,您正在尝试解释可能是垃圾输出的内容。

于 2012-07-28T19:27:39.497 回答