簡體   English   中英

為什么這個簡短的程序准確地產生此輸出?

[英]Why exactly does this short program produce this output?

我度過了一個緩慢的周末,所以出於興趣,我今天開始閱讀KN King的“ C編程:一種現代方法”一書,並開始進行第二章的練習。 練習之一是:

編寫一個程序,聲明幾個int和float變量-而不初始化它們-然后輸出它們的值。

我對此的小解決方案如下,包括輸出。 其實這並不是真正的問題,我只是很好奇它為什么會這樣做,特別是因為我對底層語言的了解並不多。

我在GitHub上快速尋找了一些其他預制的解決方案,希望可以對其進行評論或其他操作,但是它是如此簡單的問題,實際上沒有任何問題。 KN King自己的站點建議輸出的模式取決於(引用)“許多因素”,但不再贅述。 這反映在我的輸出與King的輸出不同。

#include <stdio.h>

int main()
{
    int num1, num2, num3;    
    float flo1, flo2, flo3;

    printf("Our integers are %d, %d, %d\n", num1, num2, num3); 
    printf("Our floats are %g, %g, %g\n", flo1, flo2, flo3);

    return 0;
}   

輸出如下:

C:\C\Intro\exercises>a
Our integers are 0, 16, 0
Our floats are 2.8026e-045, 0, 1.73639e-038

再說一次,不是什么大問題,只是很好奇它在做什么,可能是在硬件級別。

嚴格來說,您的代碼具有未定義的行為 ,這意味着它可以做任何想做的事。

實際上,您的變量存在於堆棧中,但沒有初始化。 這可能意味着它們在編譯器將變量放置到的位置獲取堆棧恰好包含的任何值。 這些值很可能是在流程生命周期中較早(即在其啟動期間)被調用的例程中遺留下來的。

首先,讓我們考慮一個非常簡單的編譯器如何處理此代碼。 當它看到int num1, num2, num3; 在函數內部,可能會在堆棧上為其留出空間。 堆棧通常是編譯器如何實現具有自動存儲持續時間的對象(特別是在函數內部定義的,不是static或不是線程局部的變量)。 每當調用新函數時,編譯器都會編寫代碼以在堆棧上為其局部變量和其他信息騰出空間。 同樣,還為float flo1, flo2, flo3;分配了空間float flo1, flo2, flo3;

然后,當編譯器看到printf("Our integers are %d, %d, %d\\n", num1, num2, num3); ,它將生成代碼以加載num1num2num3的值,並將它們傳遞給printf 這些值是從為這些對象分配的內存中加載的。 那記憶里有什么? 很好,此源代碼沒有為這些對象分配任何值,因此該內存中的數據與main例程啟動時存在的數據相同。

那是什么記憶? 通常,當操作系統為進程提供通用內存時,它會清除內存(將其中的所有字節設置為零),以便它不會透露以前使用該內存的任何程序的任何數據。 那么為什么printf語句不打印零?

main實際上不是程序的開始。 在可以執行main之前,必須先設置C環境。 運行C程序要求初始化您可能調用的庫例程使用的任何數據(例如printf )。 同樣,當main例程返回時,它必須要返回一些東西,該東西將采用返回值並將其作為進程退出狀態傳遞給系統。 該代碼還負責關閉打開的文件並執行其他一些清理工作。 通常,當您鏈接C程序時,一個額外的“啟動”例程會鏈接到您的可執行文件中。 操作系統啟動程序時,它將首先調用此“啟動”例程,然后啟動例程將設置C環境,然后調用main

因此,當您打印num1num2num3flo1flo2flo3 ,為它們分配的內存已由“啟動”例程使用,並且其中包含“啟動”例程碰巧留下的所有數據。

這就是為什么您會看到此源代碼打印的各種值的一種解釋。

另一方面,讓我們考慮一個更復雜的編譯器。 一個更復雜的編譯器分析代碼,可以看到未初始化就使用了變量。 它將向用戶發出警告,並且它也知道這違反了C中的各種規則。特別是,當您使用既沒有初始化也沒有自動存儲期限的對象(對於技術/原因)。

為了幫助優化,復雜的編譯器提供了處理未定義行為的特殊方法。 例如,如果編譯器看到如下代碼:

if (some test)
    FunctionA();
else
{
    Some undefined behavior here…
    FunctionB();
}

編譯器可以通過“選擇”如何定義未定義的行為來對此進行優化。 它可以定義更改程序的行為,就像編寫程序一樣:

if (some test)
    FunctionA();
else
{
    FunctionA();
}

因為那是未定義行為的有效實例。 然后可以進行優化以簡化為:

FunctionA();

有時在代碼中會出現這樣的情況,因為程序員是在為可移植到各種環境而編寫的,並且碰巧some test在特定的編譯器中的確不可能是錯誤的,並且這種優化產生了正確而簡單的代碼。 類似的情況也可能出現,其中編譯器已經以其他方式轉換代碼,而上面的代碼並不是因為它是在源代碼中按原樣編寫的,而是由編譯器在其內部轉換期間生成的。 例如,對於第一次迭代,一般的中間迭代和最后一次迭代,編譯器可能會將循環拆分為單獨的代碼,並且some test在最后一次迭代中可能始終是正確的,即使在上下文中並非總是如此。程序員寫的。

這意味着,當您使用未定義的行為(不僅根據C標准未定義,而且未由C實現定義)時,它可能會以您不希望的方式進行轉換。

我使用LLVM和Clang版本測試了此代碼,並且編譯器通過不為變量分配任何內存並且不從內存加載它們以傳遞給printf 相反,它只是在沒有為這些參數做任何准備的情況下調用了printf 在我使用的平台中,這些參數在寄存器中傳遞。 因此,結果是printf打印出那些寄存器中的任何值。 與內存一樣,這將是早期軟件恰好保留在該內存中的所有數據。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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