第10章 指针

指针通常用来帮助函数处理返回值(请参阅第7章的范例)。当函数需要返回多个值时,它通常都是通过指针传递返回值的。

10.1 全局变量

#include <stdio.h>

void f1 (int x, int y, int *sum, int *product)
{
         *sum=x+y;
         *product=x*y;
};

int sum, product;

void main()
{
         f1(123, 456, &sum, &product);
         printf ("sum=%d, product=%d\n", sum, product);
};

经MSVC 2010(启用优化选项/Ox /Ob)编译上述程序,可得到如下所示的指令。

指令清单10.1 Optimizing MSVC 2010 (/Ob0)

COMM     _product:DWORD
COMM     _sum:DWORD
$SG2803 DB     'sum=%d, product=%d', 0aH, 00H
_x$ = 8                                         ; size = 4
_y$ = 12                                        ; size = 4
_sum$ = 16                                      ; size = 4
_product$ = 20                                  ; size = 4

_f1   PROC
      mov  ecx, DWORD PTR _y$[esp-4]
      mov  eax, DWORD PTR _x$[esp-4]
      lea  edx, DWORD PTR [eax+ecx]
      imul eax, ecx
      mov  ecx, DWORD PTR _product$[esp-4]
      push esi
      mov  esi, DWORD PTR _sum$[esp]
      mov  DWORD PTR [esi], edx
      mov  DWORD PTR [ecx], eax
      pop  esi
      ret  0
_f1   ENDP

_main PROC
      push OFFSET _product
      push OFFSET _sum
      push 456                                  ; 000001c8H
      push 123                                  ; 0000007bH
      call _f1
      mov  eax, DWORD PTR _product
      mov  ecx, DWORD PTR _sum
      push eax
      push ecx
      push OFFSET $SG2803
      call DWORD PTR __imp__printf
      add  esp, 28                              ; 0000001cH
      xor  eax, eax
      ret  0
_main ENDP

如图10.1所示,我们使用OllyDbg调试这个程序。

..\TU\1001.tif{}

图10.1 使用OllyDbg观察全局变量的地址传递到f1()函数的过程

首先,全局变量的地址会被传递给f1()函数。我们用右键单击栈中的元素,选中“Follow in dump”,将会在数据段中看到两个变量的实际情况。

在运行第一条指令之前,在BSS(Block Started by Symbol)域内未被初始化赋值的数据会置零,所以这些变量会被清空(ISO可规范,6.7.8节)。变量驻留在数据段。如图10.2所示,我们可以按Alt-M组合键调用memory map进行确认。

..\TU\1002.tif{}

图10.2 使用OllyDbg查看内存映射

按下F7键及跟踪调试f1()函数[1],如图10.3所示。

..\TU\1003.tif{}

图10.3 使用OllyDbug跟踪调试f1()

如上图所示,数据栈里会有0x1C8(456)和0x7B(123)两个值,以及这两个全局变量的地址(指针)。

继续跟踪调试程序,待其执行到f1()函数尾部。如图10.4所示,我们会在左下方的窗口看到全局变量的计算结果。

..\TU\1004.tif{}

图10.4 使用ollyDbg观察f1()函数执行完毕

而后,全局变量的值将被加载到寄存器中,通过栈传递给printf()函数。如图10.5所示。

..\TU\1005.tif{}

图10.5 使用OllyDbg观察printf()函数获取全局变量的地址

10.2 局部变量

下面,我们略微调整下程序,使其通过局部变量完成同样的功能。

指令清单10.2 将和、积变量存储在局部变量里

void main()
{
        int sum, product; // now variables are local in this function

        f1(123, 456, &sum, &product);
        printf ("sum=%d, product=%d\n", sum, product);
};

编译之后,f1()函数的汇编代码与使用全局变量的代码完全相同。而main()函数的汇编代码会有相应的变化。启用/Ox /Ob0的优化选项编译后,会得到如下所示的指令。

指令清单10.3 Optimizing MSVC 2010 (/Ob0)

_product$ = -8                                       ; size = 4
_sum$ = -4                                           ; size = 4
_main   PROC
; Line 10
        sub     esp, 8
; Line 13
        lea     eax, DWORD PTR _product$[esp+8]
        push    eax
        lea     ecx, DWORD PTR _sum$[esp+12]
        push    ecx
        push    456                                  ; 000001c8H
        push    123                                  ; 0000007bH
        call    _f1
; Line 14
        mov     edx, DWORD PTR _product$[esp+24]
        mov     eax, DWORD PTR _sum$[esp+24]
        push    edx
        push    eax
        push    OFFSET $SG2803
        call    DWORD PTR __imp__printf
; Line 15
        xor     eax, eax
        add     esp, 36                              ; 00000024H
        ret     0

接下来,我们使用OllyDbg来调试这个程序。栈内的两个变量在栈内0x2EF854 和 0x2EF858两个地址上。如图10.6所示,我们观察下它们入栈的过程。

..\TU\1006.tif{}

图10.6 使用OllyDbg观察局部变量的入栈过程

在f1()函数启动时,0x2EF854和0x2EF858地址的数据都是随机的脏数据,如图10.7所示。

..\TU\1007.tif{}

图10.7 使用OllyDbg追踪f1()的启动过程

f1()函数结束时的情况如图10.8所示。

现在0x2EF854的值是0xDB18,0x2EF8588的值是0x243。它们都是f1()函数的运算结果。

..\TU\1008.tif{}

图10.8 使用OllyDbg观察f1()执行完毕时的栈内数据

10.3 总结

借助指针,f1()函数可以返回位于任意地址的任意值。这个例子体现了指针的重要作用。C++的引用(指针)/reference的作用与C pointer(指针)相同。本书的51.3节将详细介绍C++的引用。


[1] trace,可以进入被调用的函数里继续进行单步调试。如果使用F8键,则可一步就完成call调用的函数。