C++:指针未初始化,但指向变量
C++: Pointer not initialized but pointing to a variable
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
如您所见,编译器完全摆脱了这两个变量(即,b
和bPointer
在堆栈上没有任何相应的内存位置(,而只是假设常量值(10
和0
(,然后打印。
同样,由于使用未初始化的指针会导致未定义的行为,因此编译器可以自由地做任何它想做的事情......我认为这种特殊行为有点奇怪,但它仍然是"有效的",因为您的指针未初始化。
1如果您有兴趣,程序集是这样生成的:g++ -O1 test.cpp -S -o /dev/stdout | c++filt
它是未定义的行为(使用未初始化的指针(不做这样的事情,它经常导致访问违规和许多其他坏事。
从理论上讲,您可以写入虚拟内存地址的任何地方,并从中检索您写入的值,但现代编译器和操作系统不允许这样做,因为潜在的容易出错的情况。
在这种情况下,许多编译器会导致访问违规或分段错误,您的编译器只是将其传递出去。这可能会在将来导致程序出现许多问题。
正如许多其他人已经说过的那样,您的代码具有未定义的行为。它可以崩溃,它可以工作,它可以工作一段时间然后停止,一切皆有可能。
您可能知道,局部变量存储在堆栈上。(或者在寄存器中,如果进行了优化,但这在这里不太相关。在任何情况下,未初始化的本地 POD(普通旧数据(变量都不保证包含0
或NULL
或 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;
只能假设有问题的未知内存地址无法写入,或者编译器由于其他人已经建议的原因没有执行您所期望的操作。
- 为什么C++有不同的变量初始化方式?
- 静态 constexpr 成员变量初始化
- C++不同的变量初始化
- 全局和局部变量初始化与 constexpr 的差异背后的基本原理
- 是变量初始化失败吗?
- 视觉C++:在 DLL 加载期间,全局变量初始化顺序是否具有确定性?
- 类静态变量初始化顺序
- 使用 constinit 变量初始化 constexpr 变量
- 是否可以在不修改父类的情况下将成员变量初始化推迟到继承的类?
- 使用全局变量初始化不同编译单元中的其他全局变量
- 使用默认构造函数引用成员变量初始化错误
- 宏的 if 语句中的变量初始化
- 不稳定的C :每行适应性变化多变量初始化
- 同一函数中的静态函数变量初始化顺序
- C 语言中的静态变量初始化
- 错误:调用'begin(long double [nPoints])'没有匹配函数;使用硬编码的 int 与整数变量初始化向量
- 类POD成员变量初始化
- 尝试捕获类变量初始化的范围
- 共享库中 __attribute__((构造函数)) 的全局/静态变量初始化问题
- 多变量初始化编译器支持