C++:指针未初始化,但指向变量

C++: Pointer not initialized but pointing to a variable

本文关键字:变量 初始化 指针 C++      更新时间:2023-10-16
int main() 
{
    int b = 10;
    int* bPointer;
    *bPointer = b;
    cout << *bPointer << endl;
    cout << bPointer;
}

第一个 cout 打印 10,第二个打印 0。指向 0 的指针如何存储一些值?

正如其他注释和答案已经指出的那样,使用未初始化的指针会产生未定义的行为 - 所以你真的不应该这样做。然而,你似乎明白这一点,但你仍然很好奇为什么你会看到你观察到的行为......

由于bPointer没有声明volatile,编译器可以假定*bPointer不能在此函数范围内更改。

基本上,编译器看到这个赋值

*bPointer = b;

然后优化这条线

cout << *bPointer << endl;

变成这样的东西

cout << 10 << endl;

它可以假设是等效的。

编译器甚至可以推断出程序在这一点上结束,从而完全跳过*bPointer赋值;然而,这对我来说似乎有点牵强。

我尝试的所有平台和编译器组合都产生了错误,所以我无法确认我的解释 - 但如果你真的观察到这种行为,那就是我对发生的事情的有根据的猜测。

更新

在使用 -O1 标志进行编译时,我能够使用 g++ (GCC( 4.8.3 重现该行为。下面是相关的程序集1

main:
.LFB975:
        .cfi_startproc
        subq    $8, %rsp
        .cfi_def_cfa_offset 16
        ; Next 3 lines print "10" (via cout)
        movl    $10, %esi
        movl    std::cout, %edi
        call    std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
        ; Next 2 lines print the `endl` (via cout)
        movq    %rax, %rdi
        call    std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)
        ; Next 3 lines print "0" (via cout)
        movl    $0, %esi
        movl    std::cout, %edi
        call    std::basic_ostream<char, std::char_traits<char> >& std::basic_ostream<char, std::char_traits<char> >::_M_insert<void const*>(void const*)
        movl    $0, %eax
        addq    $8, %rsp
        .cfi_def_cfa_offset 8
        ret

如您所见,编译器完全摆脱了这两个变量(即,bbPointer在堆栈上没有任何相应的内存位置(,而只是假设常量值(100(,然后打印。

同样,由于使用未初始化的指针会导致未定义的行为,因此编译器可以自由地做任何它想做的事情......我认为这种特殊行为有点奇怪,但它仍然是"有效的",因为您的指针未初始化。


1如果您有兴趣,程序集是这样生成的:g++ -O1 test.cpp -S -o /dev/stdout | c++filt

它是未定义的行为(使用未初始化的指针(不做这样的事情,它经常导致访问违规和许多其他坏事。

从理论上讲,您可以写入虚拟内存地址的任何地方,并从中检索您写入的值,但现代编译器和操作系统不允许这样做,因为潜在的容易出错的情况。

在这种情况下,许多编译器会导致访问违规或分段错误,您的编译器只是将其传递出去。这可能会在将来导致程序出现许多问题。

正如许多其他人已经说过的那样,您的代码具有未定义的行为。它可以崩溃,它可以工作,它可以工作一段时间然后停止,一切皆有可能。


您可能知道,局部变量存储在堆栈上。(或者在寄存器中,如果进行了优化,但这在这里不太相关。在任何情况下,未初始化的本地 POD(普通旧数据(变量都不保证包含0NULL false或其他一些"默认"值。它将被设置为之前碰巧位于同一堆栈地址的任何垃圾。


例如,当我在本地机器上编译您的程序 32 位时,我得到这样的输出:

10
004EAF18

后者显然是可以写入 4 字节int值并且不会发生任何致命事件的地址,可能是在堆栈或堆上的某个地方。但这只是运气,它可能指向只读段并在写入过程中失败,或者程序可能会稍后崩溃,因为它可能会覆盖例如 argv列表,然后尝试使用它或其他任何东西。

无论如何,这就是大多数人希望它不会崩溃的原因

,也是为什么它不会为我崩溃的原因 - bPointer设置为一些错误但可用的地址,而不是真正NULL,因为零地址几乎在每个操作系统和进程虚拟内存设置中都保留


但事实上,当我在例如 cpp.sh 上尝试您的相同代码时,输出是您看到的奇怪输出:

10
0 

而且,它不会崩溃!有什么收获?

这是优化,正如道文在他们的回答中指出的那样。

如果您转到编译器资源管理器并使用默认值,即没有优化,则输出是以下汇编代码:

; b = 10
    mov     DWORD PTR [rbp-4], 10
; *bPointer = b
    mov     rax, QWORD PTR [rbp-16]
    mov     edx, DWORD PTR [rbp-4]
    mov     DWORD PTR [rax], edx
; cout << *bPointer
    mov     rax, QWORD PTR [rbp-16]
    mov     eax, DWORD PTR [rax]
    mov     esi, eax
    mov     edi, OFFSET FLAT:std::cout
    call    std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
; cout << endl
    mov     esi, OFFSET FLAT:std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)
    mov     rdi, rax
    call    std::basic_ostream<char, std::char_traits<char> >::operator<<(std::basic_ostream<char, std::char_traits<char> >& (*)(std::basic_ostream<char, std::char_traits<char> >&))
; cout << bPointer
    mov     rax, QWORD PTR [rbp-16]
    mov     rsi, rax
    mov     edi, OFFSET FLAT:std::cout
    call    std::basic_ostream<char, std::char_traits<char> >::operator<<(void const*)

但是,如果您添加基本的优化,-O1右上角的"编译器选项"中,它都会被优化掉:

; cout << 10
    mov     esi, 10
    mov     edi, OFFSET FLAT:std::cout
    call    std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
; cout << endl
    mov     rdi, rax
    call    std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)
; cout << 0
    mov     esi, 0
    mov     edi, OFFSET FLAT:std::cout
    call    std::basic_ostream<char, std::char_traits<char> >& std::basic_ostream<char, std::char_traits<char> >::_M_insert<void const*>(void const*)

这就解释了。难题的最后一部分是,由于某种原因,编译器(此处为 GCC(在优化期间将未初始化的指针作为0

可能是因为0和那里的任何值一样好,所以它是未定义的行为。修复错误!

在阅读了这里的一些评论和答案之后,让我印象深刻的一件事是,有多少人似乎认为原始问题中所说的 0 实际上是指针指向的地址。在显示的代码中:

cout << *bPointer << endl;

b指针正在取消引用。因此,观察到的 0 不是地址本身,而是(据说,并且受制于前面关于优化和未定义行为的观点(存储在 bPointer 指向的地址处的数字。我们应该问的问题是,为什么这个数字不是 10,在数字 10 明确写在那里之后:

*bPointer = b;

只能假设有问题的未知内存地址无法写入,或者编译器由于其他人已经建议的原因没有执行您所期望的操作。