C++ C++;指针奇怪的未定义行为

C++ C++;指针奇怪的未定义行为,c++,pointers,undefined-behavior,C++,Pointers,Undefined Behavior,在我的机器上使用-O2(或-O3)编译并运行这个程序会产生有趣的结果 #include <iostream> using namespace std; int main() { // Pointer to an int in the heap with a value of 5 int *p = new int(5); // Deallocate the memory, but keep a dangling pointer delete p;

在我的机器上使用-O2(或-O3)编译并运行这个程序会产生有趣的结果

#include <iostream>

using namespace std;

int main()
{
    // Pointer to an int in the heap with a value of 5
    int *p = new int(5);
    // Deallocate the memory, but keep a dangling pointer
    delete p;
    // Write 123 to deallocated space
    *p = 123;
    // Allocate a long int in the heap
    long *x = new long(456);

    // Print values and pointers
    cout << "*p: " << *p << endl;
    cout << "*x: " << *x << endl;
    cout << "p:  " << p << endl;
    cout << "x:  " << x << endl;

    cout << endl << "Changing nothing" << endl << endl;

    // Print again without changing anything
    cout << "*p: " << *p << endl;
    cout << "*x: " << *x << endl;
    cout << "p:  " << p << endl;
    cout << "x:  " << x << endl;

    return 0;
}
我所做的是在
p
指向的堆中写入一个已解除分配的
int
,然后分配一个长地址
x
。我的编译器始终将long放在与
p
->
x==p
相同的地址上。 现在,当我取消引用
p
并打印它时,它保留了123的值,即使它已被长456重写<代码>*x然后打印为456。更奇怪的是,后来,在不做任何更改的情况下,打印相同的值会产生预期的结果。我认为这是一种优化技术,它只在打印值
*p
后使用*x时初始化*x,这可以解释它。然而,一个objdump说的是另外一件事。下面是一个被截断并注释的
objdump-d a.out

00000000004008a0 <main>:
  4008a0:   41 54                   push   %r12
  4008a2:   55                      push   %rbp

Most likely the int allocation, where 0x4 is the size (4 bytes)
  4008a3:   bf 04 00 00 00          mov    $0x4,%edi
  4008a8:   53                      push   %rbx
  4008a9:   e8 e2 ff ff ff          callq  400890 <_Znwm@plt>

I have no idea what is going on here, but the pointer p is in 2 registers. Let's call the other one q.
q = p;
  4008ae:   48 89 c3                mov    %rax,%rbx

  4008b1:   48 89 c7                mov    %rax,%rdi

*p = 5;
  4008b4:   c7 00 05 00 00 00       movl   $0x5,(%rax)

delete p;
  4008ba:   e8 51 ff ff ff          callq  400810 <_ZdlPv@plt>

*q = 123;
  4008bf:   c7 03 7b 00 00 00       movl   $0x7b,(%rbx)

The long allocation and some other stuff (?). (8 bytes)
  4008c5:   bf 08 00 00 00          mov    $0x8,%edi
  4008ca:   e8 c1 ff ff ff          callq  400890 <_Znwm@plt>
  4008cf:   44 8b 23                mov    (%rbx),%r12d
  4008d2:   be e4 0b 40 00          mov    $0x400be4,%esi
  4008d7:   bf c0 12 60 00          mov    $0x6012c0,%edi

Initialization of the long before the printing
*p = 456;
  4008dc:   48 c7 00 c8 01 00 00    movq   $0x1c8,(%rax)

  4008e3:   48 89 c5                mov    %rax,%rbp

The printing
  4008e6:   e8 85 ff ff ff          callq  400870 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
........
0000000000 4008a0:
4008a0:41 54推力%r12
4008a2:55%推送rbp
最有可能是int分配,其中0x4是大小(4字节)
4008a3:bf 04 00 mov$0x4,%edi
4008a8:53推送%rbx
4008a9:e8 e2 ff ff ff callq 400890
我不知道这里发生了什么,但是指针p在两个寄存器中。让我们把另一个叫做q。
q=p;
4008ae:48 89 c3 mov%rax,%rbx
4008b1:48 89 c7 mov%rax,%rdi
*p=5;
4008b4:c7 00 05 00动产$0x5,(%rax)
删除p;
4008ba:e8 51 ff ff callq 400810
*q=123;
4008bf:c7 03 7b 00动产$0x7b,(%rbx)
长期分配和其他一些东西(?)。(8字节)
4008c5:bf 08 00 mov$0x8,%edi
4008ca:e8 c1 ff ff ff callq 400890
4008cf:44 8b 23 mov(%rbx),%r12d
4008d2:be e4 0b 40 00 mov$0x400be4,%esi
4008d7:bf c0 12 60 00 mov$0x6012c0,%edi
在打印之前很长一段时间内初始化
*p=456;
4008dc:48 c7 00 c8 01 00 movq$0x1c8,(%rax)
4008e3:48 89 c5 mov%rax%rbp
印刷术
4008e6:e8 85 ff ff callq 400870
........
现在,尽管
*p
已被
long
初始化(
4008dc
)覆盖,但仍打印为123

我希望我在这里讲得通,谢谢你的帮助

我要说清楚: 我试图弄清楚幕后发生了什么,编译器做了什么,以及为什么生成的编译代码与输出不一致。我知道这是未定义的行为,任何事情都可能发生。但这意味着编译器可以生成任何代码,而不是CPU将生成指令。欢迎提出任何想法。

别担心,我不打算在任何地方使用它;)


编辑:在我朋友的机器(OS X)上,即使进行优化,它也会产生预期的结果。

正如您所提到的,这可能是由于编译器强制执行的优化。如果使用-O0编译,那么它将为值打印456。由于p被删除,x被立即分配,x将指向p所指向的同一地址(可能并非总是相同的情况,但在您的测试中最有可能是相同的情况)。因此,*p和*x应该取消引用相同的值。 如果更改打印语句的顺序,则始终会为值打印456。我已更改了代码中前两条cout语句的顺序,如下所示:

#include <iostream>

using namespace std;

int main()
{
    // Pointer to an int in the heap with a value of 5
    int *p = new int(5);
    // Deallocate the memory, but keep a dangling pointer
    delete p;
    // Write 123 to deallocated space
    *p = 123;
    // Allocate a long int in the heap
    long *x = new long(456);

    // Print values and pointers
    cout << "*x: " << *x << endl;
    cout << "*p: " << *p << endl;
    cout << "p:  " << p << endl;
    cout << "x:  " << x << endl;

    cout << endl << "Changing nothing" << endl << endl;

    // Print again without changing anything
    cout << "*p: " << *p << endl;
    cout << "*x: " << *x << endl;
    cout << "p:  " << p << endl;
    cout << "x:  " << x << endl;

    return 0;
}
#包括
使用名称空间std;
int main()
{
//指向堆中int值为5的指针
int*p=新的int(5);
//释放内存,但保留悬空指针
删除p;
//将123写入释放的空间
*p=123;
//在堆中分配一个长int
长*x=新长(456);
//打印值和指针

cout您不会在自己的源代码中找到答案,也不会在编译器对其执行的操作中找到答案,即使您从编译器生成了程序集输出

未定义的情况发生在C-runtime内存分配器上,该分配器已经编译好二进制代码,并链接到您的测试应用程序上。当您调用new时,运行库将决定指针指向何处。不能保证new/delete/new意味着第二个new将给您相同的地址,这完全是实现dep亲爱的


如果您真的想知道,那么您需要使用完整的源代码(包括new的源代码)进行构建,然后阅读它是如何实现的和/或在调试器中逐步执行,以查看发生了什么。

您很快就停止查看反汇编输出了(或者至少你没有发布与你的问题相关的接下来几行内容)。它们可能看起来像:

movl    %r12d, %esi
movq    %rax, %rdi
call    _ZNSolsEi
movq    %rax, %rdi
call    _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
rbx
r12
是必须在GCC在Linux上使用的x64 ABI中的函数调用之间保留的寄存器。分配
long
后,您会看到以下指令:

mov    (%rbx),%r12d
先前在指令流中使用的
rbx
包括:

mov    %rax,%rbx       ; store the `p` pointer in `rbx`

...

movl   $0x7b,(%rbx)    ; store 123 where `p` pointed (even though it has been freed before)

... 

mov    (%rbx),%r12d    ; read that value - 123 - back and into `r12`

然后你会在我上面发布的代码片段中看到,这是一个反汇编,它没有进入你的问题中,并且对应于
的一部分,你是否试图理解未定义的行为。享受这个过程。我是否遗漏了一个问题?另外…[…]它会产生预期的结果“hehe good one=)@luk32噢,对了:D我没有提出问题。我只是想弄清楚幕后会发生什么。到目前为止,这没有任何意义D@sammko我强烈建议你们大胆地说,你们应该寻求帮助,了解汇编程序,或者你们想要的任何东西。否则,人们会说它是UB,不需要有意义和解释把它擦掉,他们就完全正确了。把奇怪的东西集中起来就是集中注意力
mov    %rax,%rbx       ; store the `p` pointer in `rbx`

...

movl   $0x7b,(%rbx)    ; store 123 where `p` pointed (even though it has been freed before)

... 

mov    (%rbx),%r12d    ; read that value - 123 - back and into `r12`
movl    %r12d, %esi    ; put 123 into `esi`, which is used to pass an argument to a function call