在32位和64位系统上引用指针

时间:2012-07-28 19:22:44

标签: c arrays pointers

以下代码在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只是将指针交换到ab中的第一个元素。因此,在致电swapArray后,a应指向2b应指向1
出于这个原因,a[0]应该产生2,而a[1]应该在2的位置之后引用内存中的下一个字节,其中包含4

有人可以解释一下吗?

修改
感谢您的评论和回答,我现在注意到&a&b的类型为int (*)[]而非int **。这显然使代码不正确(实际上我得到了编译器警告)。但是,令人感兴趣的是,为什么编译器(gcc)只是发出警告而不是错误 我仍然留下了在不同系统上导致不同结果的问题,但由于代码不正确,因此不太相关。

编辑2:
至于不同系统的不同结果,我建议阅读AndreyT's comment

3 个答案:

答案 0 :(得分:5)

swapArray(&a, &b); 

&a&b不属于int **类型,但类型为int (*)[2]。顺便说一句,您的编译器非常友好地接受您的程序,但编译器有权拒绝翻译它。

答案 1 :(得分: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]的地址中。让我们看看内存映射

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

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

下一条指令使用lea加载刚刚保存在早期指令中的值的有效地址。

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

这意味着现在$rax的值为0x7fffffffe0fc

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

接下来,我们将使用

将此地址保存到内存中
0x0000000000400483 <+15>:   mov    QWORD PTR [rbp-0x10],rax

重要的是要注意这里使用的QWORD。因为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

这非常简单。您的数组已初始化,并且当数组开头的有效地址加载到&$rdi时,$rsi运算符的效果可见。

现在让我们看看它在swaparray()内的作用。

数组的开头会保存到$rdi$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中的地址执行另一个间接,以获取QWARD并将其加载到$rax。那会是什么回归?它将从QWARD返回0x7fffffffe100。这实际上将从那里保存的两个四字节数量形成一个8字节的数量。详细说明,
  3. 内存如下所示。

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

    现在,如果您获取QWORD

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

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

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

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

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

    从语言的角度来看,它的类型不匹配已在前面的答案中指出,所以我不会进入那个。

答案 2 :(得分: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上有铿锵声,我收到一个错误:

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

我认为这是未定义的行为,你试图解释可能是垃圾输出。