簡體   English   中英

如果未初始化,為什么局部變量在 C 中有未確定的值?

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM