微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

C++ 声明和初始化/变量未初始化为默认值

如何解决C++ 声明和初始化/变量未初始化为默认值

案例 1->

int a;
std :: cout << a << endl; // prints 0

案例 2->

int a;
std :: cout << &a << " " << a << endl; // 0x7ffc057370f4 32764

每当我打印变量的地址时,它们都没有初始化为认值,为什么会这样。 我认为 a 的值在 case 2 中是垃圾值,但每次运行代码时它都会显示 32764,5,6,7 这些仍然是垃圾值吗?

解决方法

C++ 中的变量未初始化为默认值,因此无法确定该值。您可以阅读更多相关信息here

,

恐怕接受的答案没有触及问题的要点: 为什么

int a;
std :: cout << a << endl; // prints 0

总是打印 0,就好像 a 被初始化为其默认值一样,而在

int a;
std :: cout << &a << " " << a << endl; // 0x7ffc057370f4 32764

编译器为 a 生成了一些垃圾值。

是的,在这两种情况下,我们都有一个未定义行为的示例,a 的任何值都是可能的,那么为什么在情况 1 中总是 0?

首先要记住,只要程序的含义保持不变,C/C++ 编译器就可以随意修改源代码。所以,如果你写

int a;
std :: cout << a << endl; // prints 0

编译器可以自由假设 a 不需要与任何实际 RAM 单元相关联。你不读它,也不写信给a。因此编译器可以自由地在其寄存器之一中为 a 分配内存。在这种情况下,a 没有地址,在功能上相当于“命名的、无地址的临时”这样奇怪的东西。但是,在案例 2 中,您要求编译器打印 a 的地址。在这种情况下,编译器无法忽略请求并为分配 a 的内存生成代码,即使 a 的值可能是垃圾。

下一个因素是优化。您可以在 Debug 编译模式下完全关闭它,也可以在 Release 模式下打开积极优化。因此,无论您将其编译为 Debug 还是 Release,您都可以预期您的简单代码的行为会有所不同。此外,由于它是未定义的行为,如果使用不同的编译器甚至同一编译器的不同版本进行编译,您的代码可能会以不同的方式运行。

我准备了一个更容易分析的程序版本:

#include <iostream>

int f()
{
    int a;
    return a;  // prints 0
}

int g()
{
    int a;
    return reinterpret_cast<long long int>(&a) + a;  // prints 0
}

int main() { std::cout << f() << " " << g() << "\n"; }

函数 gf 的不同之处在于它使用未初始化的变量 a 的地址。我在 Godbolt Compiler Explorer 中对其进行了测试:https://godbolt.org/z/os8b583ss 您可以在各种编译器和各种优化选项之间切换。请自己做实验。对于 Debug 和 gcc 或 clang,使用 -O0-g,对于 Release 使用 -O3

对于最新的(主干)gcc,我们有以下等价的汇编:

f():
        xorl    %eax,%eax
        ret
g():
        leaq    -4(%rsp),%rax
        addl    -4(%rsp),%eax
        ret
main:
        subq    $24,%rsp
        xorl    %esi,%esi
        movl    $_ZSt4cout,%edi
        call    std::basic_ostream<char,std::char_traits<char> >::operator<<(int)
        leaq    12(%rsp),%rsi
        movl    $_ZSt4cout,%edi
        addl    12(%rsp),%esi
        call    std::basic_ostream<char,std::char_traits<char> >::operator<<(int)
        xorl    %eax,%eax
        addq    $24,%rsp
        ret

请注意,f() 被简化为将 eax 寄存器设置为零(对于整数 a 的任何值,a xor a 等于 0)。 eax 是此函数返回其值的寄存器。因此在 Release 中为 0。嗯,实际上,不,编译器甚至更智能:它从不调用 f()!相反,它会将调用 operator<< 中使用的 esi 寄存器清零。同样,g 被读取 12(%rsp) 代替,一次作为值,一次作为地址。这会为 a 生成一个随机值,而为 &a 生成一个相当相似的值。 AFIK,它们有点随机,让黑客攻击我们的代码变得更加困难。

现在调试中的代码相同:

f():
        pushq   %rbp
        movq    %rsp,%rbp
        movl    -4(%rbp),%eax
        popq    %rbp
        ret
g():
        pushq   %rbp
        movq    %rsp,%rbp
        leaq    -4(%rbp),%rax
        movl    %eax,%edx
        movl    -4(%rbp),%eax
        addl    %edx,%eax
        popq    %rbp
        ret
main:
        pushq   %rbp
        movq    %rsp,%rbp
        call    f()
        movl    %eax,std::char_traits<char> >::operator<<(int)
        call    g()
        movl    %eax,std::char_traits<char> >::operator<<(int)
        movl    $0,%eax
        popq    %rbp
        ret

您现在可以清楚地看到,即使不知道 386 程序集(我也不知道),在调试模式 (-g) 下,编译器根本不执行任何优化。在 f() 中,它读取 a(低于帧指针寄存器值 -4(%rbp) 的 4 个字节)并将其移动到“结果寄存器”eax。在 g() 中,也是如此,但 a 作为值读取一次,作为地址读取一次。此外,f()g() 都在 main() 中被调用。在这种编译器模式下,程序会为 a 生成“随机”结果(请自行尝试!)。

为了让事情变得更有趣,这里是在 Release 中由 clang (trunk) 编译的 f()

f():                                  # @f()
        retq
g():                                  # @g()
        retq

你能看到吗?这些函数对 clang 来说是微不足道的,以至于它没有为它们生成任何代码。此外,它没有将对应于 a 的寄存器清零,因此,与 g++ 不同,clang 为 a 生成一个随机值(在 Release 和 Debug 中)。

您可以进一步进行实验,发现 clang 为 f 生成的内容取决于 f 还是 g 在 main 中首先被调用。

现在您应该对什么是未定义行为有了更好的了解。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。