[英]Why local variables have undetermined values in C if not initialized?
在C
- Linux OS
,當調用函數時,程序集的結尾部分會創建一個堆棧幀,並且局部變量引用基指針。 我的問題是當我們在沒有初始化的情況下打印變量時,是什么讓變量保持不確定的值。 我的理論是,當我們使用的變量,在OS
帶來的page
對應的局部變量的地址,並在地址page
可能有一定的價值,使局部變量的值。 那是對的嗎?
我們來看一個簡單程序的反匯編:
#include <stdio.h>
int main() {
unsigned int i;
unsigned int j = 1;
printf("%u\n", j);
printf("%u\n", i);
}
使用 GCC-11.1 進行默認優化的反匯編是:
.file "char.c"
.text
.section .rodata
.LC0:
.string "%u\n"
.text
.globl main
.type main, @function
/*So, till here is meta data and other stuff. We're interested in what's bottom*/
main:
.LFB0:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movl $1, -8(%rbp)
movl -8(%rbp), %eax /*See, it wrote 1 into -8(%rbp), which represents the variable j, but didn't assign anything anything to -4(%rbp), which represents the variable i*/
movl %eax, %esi
leaq .LC0(%rip), %rax
movq %rax, %rdi
movl $0, %eax
call printf@PLT
movl -4(%rbp), %eax /* Now we load -4(%rbp), which is i, into %eax for printing. Whatever is at -4(%rbp) gets printed. So, it's undetermined */
movl %eax, %esi
leaq .LC0(%rip), %rax
movq %rax, %rdi
movl $0, %eax
call printf@PLT
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 11.1.0-3ubuntu1) 11.1.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:
閱讀反匯編中的注釋以獲取解釋。
顯然,在某些情況下,編譯器甚至可能不會費心將未初始化的變量加載到寄存器中(不是在這種情況下,可能取決於編譯器、優化和情況),而是使用寄存器中的任何內容。 曾經看到有人這么說,我沒有查過ISO標准,也沒有驗證過。 你是如何開始在標准中找到這些東西的? 很大。
考慮編譯器編譯正確初始化對象的程序:
int x = 3;
printf("%d\n", x);
int y = 4+x*7;
printf("%d\n", y);
這可能會導致匯編代碼:
Store 3 in X. // "X" refers to the stack location assigned for x.
Load address of "%d\n" into R0. // R0 is the register used for passing the first argument.
Load from X into R1. // R1 is the register for the second argument.
Call printf.
Load 4 into R1. // Start the 4 of 4+x*7.
Load from X into R2 // Get x to calculate with it.
Multiply R2 by 7. // Make x*7.
Add R2 to R1. // Finish 4+x*7.
Load address of "%d\n" into R0.
Call printf.
這是一個工作程序。 現在假設我們沒有初始化x
並且有int x;
反而。 由於x
未初始化,因此規則說它沒有確定的值。 這意味着允許編譯器省略所有獲取x
值的指令。 因此,讓我們使用工作匯編代碼並刪除所有獲取x
值的指令:
Load address of "%d\n" into R0. // R0 is the register used for passing the first argument.
Call printf.
Load 4 into R1. // Start the 4 of 4+x*7.
Multiply R2 by 7. // Make x*7.
Add R2 to R1. // Finish 4+x*7.
Load address of "%d\n" into R0.
Call printf.
在這個程序中,第一個printf
打印R1
任何內容,因為x
的值從未加載到R1
。 並且x*7
的計算使用R2
任何內容,因為x
的值從未加載到R2
。 所以這個程序可能會為第一個printf
打印“37”,因為在R1
碰巧有一個 37,但它可能會為第二個printf
打印“4”,因為在R2
碰巧有一個 0。 所以這個程序的輸出“看起來像” x
在某一時刻的值為 37,而在另一時刻的值為 0。 該程序的行為就好像x
沒有任何固定值。
這是一個非常簡化的例子。 實際上,當編譯器在優化期間刪除代碼時,它會刪除更多代碼。 例如,如果它知道x
沒有被初始化,它可能不僅會移除x
的負載,還會移除乘以 7 的結果。但是,這個例子是為了說明原理:當存在未初始化的值時,編譯器可以從根本上改變生成的代碼。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.