Yah*_*hia 5 c assembly cpu-architecture
我想知道变量的初始化方式:
#include <stdio.h>
int main( void )
{
int ghosts[3];
for(int i =0 ; i < 3 ; i++)
printf("%d\n",ghosts[i]);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
这给我带来了随机值,例如 -12 2631 131 ..它们来自哪里?
例如,x86-64 Linux 上的 GCC:https ://godbolt.org/z/MooEE3ncc
我有一个猜测来回答我的问题,无论如何它都可能是错误的:
内存的寄存器在“清空”后获得 0 和 1 之间的随机电压,这些值“四舍五入”为 0 或 1,并且这些随机值取决于在某事上?!也许寄存器的制作方式?也许内存的容量会以某种方式发挥作用?甚至可能是温度?!!
每次运行新程序时,您的计算机不会重新启动或重新启动。您的程序可以使用的内存或寄存器中的每一位存储空间都有一个先前指令留下的值,无论是在该程序中还是在启动该程序之前的操作系统中。
如果是这样的话,例如对于微控制器来说,是的,在加电的电压波动期间,存储的每一位都可能稳定在 0 或 1 状态,除了设计为在某种状态下加电的存储之外。(DRAM 在上电时更有可能为 0,因为其电容器已放电)。但您还希望有内部 CPU 逻辑,在从复位向量(内存地址)获取和执行代码的第一条指令之前,进行一些归零或将事物设置为保证状态;系统设计者通常会在该物理地址处安排 ROM,而不是 RAM,这样他们就可以在那里放置非随机字节的机器代码。在该地址执行的代码可能应该为所有寄存器假设随机值。
但是您正在编写一个在操作系统下运行的简单用户空间程序,而不是在微控制器、嵌入式系统或主流主板的固件下运行,因此当任何东西加载您的程序时,加电随机性早已成为过去。
现代操作系统在进程启动时将寄存器归零,并将分配给用户空间(包括堆栈空间)的内存页归零,以避免内核数据和来自其他进程的数据的信息泄漏。因此,这些值必须来自进程中较早发生的事情,可能来自之前运行main并使用一些堆栈空间的动态链接器代码。
读取从未初始化或分配的局部变量的值实际上并不是未定义的行为(在这种情况下,因为它无法被声明register int ghosts[3],这是一个错误(Godbolt),因为ghosts[i]有效地使用了地址)请参阅 (为什么)正在使用未初始化的变量未定义的行为? 在这种情况下,C 标准必须说明的就是该值是不确定的。 因此,正如您所期望的那样,它确实归结为实现细节。
当您在没有优化的情况下进行编译时,编译器甚至不会注意到 UB,因为它们不会跟踪 C 语句之间的使用情况。(这意味着一切都被视为有点像volatile,仅根据语句需要将值加载到寄存器中,然后再次存储。)
在我添加到您的问题的示例 Godbolt 链接中,请注意,它-Wall不会在 处产生任何警告-O0,并且只是从它为数组选择的堆栈内存中读取数据,而无需写入它。 因此,您的代码正在观察函数启动时内存中的任何陈旧值。 (但正如我所说,这肯定是之前通过 C 启动代码或动态链接在该程序中编写的。)
使用gcc -O2 -Wall,我们得到了我们期望的警告:warning: 'ghosts' is used uninitialized [-Wuninitialized],但它仍然从堆栈空间读取而不写入。
有时GCC会发明一个0而不是读取未初始化的堆栈空间,但在这种情况下不会发生。关于它如何编译的保证为零,编译器会看到使用未初始化的“错误”,并且可以发明它想要的任何值,例如读取一些它从未写入的寄存器而不是该内存。例如,由于您正在调用 printf,GCC 可能在调用之间未初始化 ESI printf,因为这是ghost[i]在 x86-64 System V 调用约定中作为第二个参数传递的位置。
大多数现代 CPU(包括 x86)没有任何会导致添加指令错误的“陷阱表示”,即使有,C 标准也不能保证不确定值不是陷阱表示。但 IA-64 确实有一个来自不良推测负载的 Not A Thing 寄存器结果,如果您尝试读取它,就会陷入困境。请参阅陷阱表示问答的评论- Raymond Chen 的文章: ia64 上未初始化的垃圾可能是致命的。
ISO C 规则关于 UB 读取候选变量的未初始化变量register可能就是为了解决这个问题,但是启用优化后,如果地址稍后发生,您仍然可能会遇到这种情况,除非编译器采取措施避免它。但 ISO C缺陷报告 N1208建议说,即使对于没有陷阱表示的类型,不确定值也可以是“表现得如同陷阱表示的值”。因此,该标准的一部分似乎并未完全涵盖 IA-64 等 ISA,即真正的编译器的工作方式。
另一种情况并不完全是“陷阱表示”:请注意,只有某些对象表示(位模式)_Bool在主流 ABI 中有效,违反这一点可能会导致程序崩溃:Does the C++ standard allowed for an uninitialized bool to crash a program ?
这是一个 C++ 问题,但我验证了如果你写 ;,GCC 将返回垃圾,而不会将其布尔化为 0/1 _Bool b[2]。return b[0]; https://godbolt.org/z/jMr98547o。我认为 ISO C 只要求未初始化的对象具有某种对象表示(位模式),而不要求它是该对象的有效表示(否则这将是编译器错误)。对于大多数整数类型,每个位模式都是有效的并且表示一个整数值。除了读取未初始化的内存之外,您还可以使用(unsigned char*)或memcpy将坏字节写入_Bool.
如以下问答所示,在优化编译时,多次读取同一未初始化变量可能会产生不同的结果:
这个答案的其他部分主要是关于当编译器没有真正“注意到”UB 时,未优化代码中的值来自何处。
存储器的寄存器被“清空”后会获得 0 到 1 之间的随机电压,
没有什么那么神秘的。您只是看到上次使用这些内存位置时写入的内容。
当内存被释放时,它并没有被清除或清空。系统只知道它是空闲的,下次有人需要内存时,它就会被移交,旧的内容仍然存在。就像买了一辆旧车,打开手套箱,里面的东西并不神秘,只是惊喜地发现了一个打火机和一只袜子。
有时,在调试环境中,释放的内存会被清除为某个可识别的值,以便轻松识别您正在处理未初始化的内存。例如 0xcccccccccccc 或 0xdeadbeefDeadBeef
也许有一个更好的比喻。你在一家自助餐厅吃饭,餐厅从不清洗盘子,当顾客吃完后,他们会把盘子放回“免费”堆上。当您去取餐时,您可以从空闲的一堆中拿起顶盘。你应该清洁盘子,否则你会得到以前顾客留下的东西
该数组ghosts未初始化,并且因为它是在函数内部声明的,但实际上并非如此static(正式地,它具有自动存储持续时间),因此其值是不确定的。
这意味着您可以读取任何值,并且不保证任何特定值。
| 归档时间: |
|
| 查看次数: |
764 次 |
| 最近记录: |