1

我只是在 C 语言中查看指针,当我创建各种案例时,我偶然发现了这个:(使用的 IDE - Code::Blocks

编译器 - GNU GCC)

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int a=2;
    int *pa;
    pa=&a;

    printf("1 %u\n", &a );
    printf("2 %u\n", pa );
    printf("3 %d\n", a );

    printf("4 %u\n", &(pa));
    //printf("\n4 %u\n", &(*pa)); // output not as expected 

    printf("End \n");
    return 0;
}

这里的输出是:

1 2686748

2 2686748

3 2

4 2686744

结尾

现在,当我将第四个 printf 更改为:

printf("\n4 %u\n", &(*pa)); 

输出变为:

1 2686744

2 2686744

3 2

4 2686744

结尾

在第二部分中,*pa 应该给出2并且&(*pa)应该给出2686748但是这里之前的值已经改变了!

预期输出应为(对于 &(*pa)):2686748、2686748、2、2686748

请解释一下为什么我没有得到预期的输出以及我哪里出错了?

我故意不在 printf() 中使用 %p 所以请不要让我用它替换 %u 或 %d 。

如果需要,这里是指向 IDEone 的链接(那里也会生成类似的输出):

程序 -这里只是删除/插入注释,你会明白,首先包括两个 printf,然后或者注释掉其中的一个。

忽略包含 stdlib.h 我懒得删除它:P

请尽可能简单:)

组件:

First for:

     printf("4 %u\n", &(pa));

   // printf("\n4 %u\n", &(*pa)); 


0x00401334  push   %ebp

0x00401335  mov    %esp,%ebp

0x00401337  and    $0xfffffff0,%esp

0x0040133A  sub    $0x20,%esp

0x0040133D  call   0x401970 <__main>

0x00401342  movl   $0x2,0x1c(%esp)

0x0040134A  lea    0x1c(%esp),%eax

0x0040134E  mov    %eax,0x18(%esp)

0x00401352  lea    0x1c(%esp),%eax

0x00401356  mov    %eax,0x4(%esp)

0x0040135A  movl   $0x403024,(%esp)

0x00401361  call   0x401be8 <printf>

0x00401366  mov    0x18(%esp),%eax

0x0040136A  mov    %eax,0x4(%esp)

0x0040136E  movl   $0x40302a,(%esp)

0x00401375  call   0x401be8 <printf>

0x0040137A  mov    0x1c(%esp),%eax

0x0040137E  mov    %eax,0x4(%esp)

0x00401382  movl   $0x403030,(%esp)

0x00401389  call   0x401be8 <printf>

**here>** 0x0040138E    lea    0x18(%esp),%eax

0x00401392  mov    %eax,0x4(%esp)

0x00401396  movl   $0x403036,(%esp)

0x0040139D  call   0x401be8 <printf>

0x004013A2  movl   $0x40303c,(%esp)

0x004013A9  call   0x401be0 <puts>

0x004013AE  mov    $0x0,%eax

0x004013B3  leave

0x004013B4  ret



Second for :

   // printf("4 %u\n", &(pa));

    printf("\n4 %u\n", &(*pa));



0x00401334  push   %ebp

0x00401335  mov    %esp,%ebp

0x00401337  and    $0xfffffff0,%esp

0x0040133A  sub    $0x20,%esp

0x0040133D  call   0x401970 <__main>

0x00401342  movl   $0x2,0x18(%esp)

0x0040134A  lea    0x18(%esp),%eax

0x0040134E  mov    %eax,0x1c(%esp)

0x00401352  lea    0x18(%esp),%eax

0x00401356  mov    %eax,0x4(%esp)

0x0040135A  movl   $0x403024,(%esp)

0x00401361  call   0x401be8 <printf>

0x00401366  mov    0x1c(%esp),%eax

0x0040136A  mov    %eax,0x4(%esp)

0x0040136E  movl   $0x40302a,(%esp)

0x00401375  call   0x401be8 <printf>

0x0040137A  mov    0x18(%esp),%eax

0x0040137E  mov    %eax,0x4(%esp)

0x00401382  movl   $0x403030,(%esp)

0x00401389  call   0x401be8 <printf>

**here>** 0x0040138E    mov    0x1c(%esp),%eax

0x00401392  mov    %eax,0x4(%esp)

0x00401396  movl   $0x403036,(%esp)

0x0040139D  call   0x401be8 <printf>

0x004013A2  movl   $0x40303d,(%esp)

0x004013A9  call   0x401be0 <puts>

0x004013AE  mov    $0x0,%eax

0x004013B3  leave

0x004013B4  ret


Third one for:

    printf("4 %u\n", &(pa));

    printf("\n4 %u\n", &(*pa));


0x00401334  push   %ebp

0x00401335  mov    %esp,%ebp

0x00401337  and    $0xfffffff0,%esp

0x0040133A  sub    $0x20,%esp

0x0040133D  call   0x401980 <__main>

0x00401342  movl   $0x2,0x1c(%esp)

0x0040134A  lea    0x1c(%esp),%eax

0x0040134E  mov    %eax,0x18(%esp)

0x00401352  lea    0x1c(%esp),%eax

0x00401356  mov    %eax,0x4(%esp)

0x0040135A  movl   $0x403024,(%esp)

0x00401361  call   0x401bf8 <printf>

0x00401366  mov    0x18(%esp),%eax

0x0040136A  mov    %eax,0x4(%esp)

0x0040136E  movl   $0x40302a,(%esp)

0x00401375  call   0x401bf8 <printf>

0x0040137A  mov    0x1c(%esp),%eax

0x0040137E  mov    %eax,0x4(%esp)

0x00401382  movl   $0x403030,(%esp)

0x00401389  call   0x401bf8 <printf>

0x0040138E  lea    0x18(%esp),%eax

0x00401392  mov    %eax,0x4(%esp)

0x00401396  movl   $0x403036,(%esp)

0x0040139D  call   0x401bf8 <printf>

**here>** 0x004013A2    mov    0x18(%esp),%eax

0x004013A6  mov    %eax,0x4(%esp)

0x004013AA  movl   $0x40303c,(%esp)

0x004013B1  call   0x401bf8 <printf>

0x004013B6  movl   $0x403043,(%esp)

0x004013BD  call   0x401bf0 <puts>

0x004013C2  mov    $0x0,%eax

0x004013C7  leave

0x004013C8  ret
4

6 回答 6

4

应该是编译器的优化

如果不使用&pa,所有的用法pa都等同于&a.

所以编译器pa完全删除了变量,它不再使用空间。

于 2013-10-27T08:40:00.080 回答
3

Here in the 2nd part the the *pa should have given 2 and &(*pa) should have given 2686748 but here the previous values have been altered!

谁告诉你每次运行程序时,每次运行都会得到完全相同的内存位置?也%p用于打印地址。

传统上,堆栈区域与堆区域相邻,并以相反的方向增长;当堆栈指针遇到堆指针时,空闲内存已耗尽。(使用现代大地址空间和虚拟内存技术,它们几乎可以放置在任何地方,但它们通常仍会朝相反的方向增长。)

堆栈区包含程序堆栈,一个 LIFO 结构,通常位于内存的较高部分。在标准 PC x86 计算机体系结构上,它向地址零增长;在其他一些架构上,它的增长方向相反。“堆栈指针”寄存器跟踪堆栈的顶部;每次将值“推入”堆栈时都会对其进行调整。

堆栈,其中存储自动变量,以及每次调用函数时保存的信息。每次调用函数时,返回的地址和调用者环境的某些信息,例如一些机器寄存器,都保存在堆栈中。然后,新调用的函数在堆栈上为其自动和临时变量分配空间。

所以重点是变量可以分配到任何地方,具体取决于每次运行。

图片

于 2013-10-27T08:39:15.283 回答
0

我解释以下单个步骤:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int a=2;
    int *pa;
    pa=&a; // (1)

    printf("1  %u\n", &a ); // outputs the address of a
    printf("2  %u\n", pa ); // outputs the address of a as well due to (1)
    printf("3  %d\n", a ); // outputs the value of a

    printf("4  %u\n", &(pa)); // outputs the value of the pointer variable pa
    printf("4a %u\n", &(*pa)); // Outputs the address of the mempry pa points to, so it is esssentially pa. Output as expected!

    printf("End\n");
    return 0;
}
于 2013-10-27T09:00:49.160 回答
0

预期输出应为(对于 &(*pa)):2686748、2686748、2、2686748

请解释一下为什么我没有得到预期的输出以及我哪里出错了?

从 OP 我假设你的结果是可重现的,所以当你多次运行它时你得到完全相同的输出。正确的?因此,您的操作系统不会进行地址(至少堆栈)随机化。

我同意 BeniBela 的理论,即这是由于编译器优化。在您的第二个程序中,优化了局部变量 pa。这相当于:

int main()
{
    int a=2;

    printf("1 %u\n", &a );
    printf("2 %u\n", &a );
    printf("3 %d\n", a );    
    printf("\n4 %u\n", &(a)); // output not as expected 

    printf("End \n");
    return 0;
}

因此,您的第一个和第二个程序的堆栈框架布局会有所不同。但是函数帧的大小可能不会改变,因为 gcc 默认情况下堆栈是 16 字节对齐的。

更新:从反汇编来看,堆栈帧是这样的:

第一个

           +-------+
0x0028FF00 |       |  <== (%esp)
           +-------+
0x0028FF04 |       |
           +-------+
0x0028FF08 |       |
           +-------+
0x0028FF0c |       |
           +-------+
0x0028FF10 |       |
           +-------+
0x0028FF14 |       |
           +-------+
0x0028FF18 |       |  <== pa
           +-------+
0x0028FF1c |   2   |  <== a
           +-------+
0x0028FF20 |       |
           +-------+

然后是第二个:

           +-------+
0x0028FF00 |       |  <== (%esp)
           +-------+
0x0028FF04 |       |
           +-------+
0x0028FF08 |       |
           +-------+
0x0028FF0c |       |
           +-------+
0x0028FF10 |       |
           +-------+
0x0028FF14 |       |
           +-------+
0x0028FF18 |   2   |  <== a
           +-------+
0x0028FF1c |       |
           +-------+
0x0028FF20 |       |
于 2013-10-27T12:28:48.210 回答
0

&a将指针地址打印到a

pa还会打印指向 的指针地址a,因为 的值pa是地址(*pa是取消引用的值)

a应该打印 2,原因很明显

&(pa)将打印地址pa

&(*pa)是取消引用的 pa 的地址(的地址a

pa             &(pa)           *(&pa)         *pa           a             &a
(a's address)  (pa's address)  (address of a) (value of a)  (value of a)  (address of a)

所以基本上,这是预期的行为。

于 2013-10-27T08:44:11.470 回答
0

第一个程序采用 的地址pa,但第二个没有。当你不取一个对象的地址时,C 实现不需要为它保留实际的内存空间,优化可能会避免将它放入内存中。该对象可能仅存在于寄存器中,甚至可以通过其他优化完全消除。

因此,是否获取地址(并打印,这迫使它在程序外部可见)会改变实现必须使用的内存。这导致内存中事物的排列方式不同,从而导致地址不同。

(当然,我们不向您保证这些地址。实现可以根据自己的选择以各种方式自由排列这些对象。)

于 2013-10-27T09:36:08.407 回答