簡體   English   中英

C中函數和變量的內存分配

[英]Memory allocation of functions and variables in C

依賴於C編譯器和編譯器標志的版本,可以在函數中的任何位置初始化變量(據我所知)。

我習慣將所有變量放在函數的頂部,但討論開始時關於變量的內存使用,如果在函數的任何其他位置定義的話。

下面我寫了兩個簡短的例子,我想知道是否有人能解釋我(或驗證)如何分配內存。

示例1:變量y是在可能的返回語句之后定義的,有可能這個變量不會被用於這個原因,據我所知這沒關系,代碼(內存分配)將是如果變量放在函數的頂部,則相同。 它是否正確?

示例2:變量x在循環中初始化,這意味着此變量的范圍僅在此循環內,但此變量的內存使用情況如何? 如果放在功能的頂部會有什么不同嗎? 或者只是在函數調用的堆棧上初始化?

編輯:總結一個主要問題:減少變量的范圍或更改第一次使用的位置(所以在其他任何地方而不是頂部)對內存使用有影響嗎?

代碼示例1

static void Function(void){
 uint8_t x = 0;

 //code changing x
 if(x == 2)
 {
  return;
 }

 uint8_t y  = 0;    
 //more code changing y
}

代碼示例2

static void LoopFunction(void){
 uint8_t i = 0;

 for(i =0; i < 100; i ++)
 {
  uint8_t x = i;
  // do some calculations
  uartTxLine("%d", x); 
 }

 //more code
}

我習慣把所有變量都放在函數的頂部

過去在舊版本的C中需要這樣,但現代編譯器放棄了這一要求。 只要他們在第一次使用時知道變量的類型,編譯器就會獲得所需的所有信息。

我想知道是否有人能解釋我如何分配內存。

編譯器決定如何在自動存儲區域中分配內存。 實現不僅限於為每個變量聲明一個單獨位置的方法。 允許它們重用超出范圍的變量的位置,以及在某個點之后不再使用的變量。

在第一個示例中,允許變量y使用以前由變量x占用的空間,因為y的第一個使用點是在x的最后一個使用點之后。

在第二個示例中,循環中用於x的空間可以重用於您可以在// more code區域中聲明的其他變量。

基本上,故事就是這樣的。 在原始匯編程序中調用函數時,可以自定義在進入函數時將函數使用的所有內容存儲在堆棧中,並在離開時將其清理干凈。 某些CPU和ABI可能具有涉及自動堆疊參數的調用約定。

可能因為這個原因,C和許多其他舊語言都要求必須在函數的頂部(或在作用域的頂部)聲明所有變量,以便{ }反映堆棧上的push / pop。

在80年代/ 90年代左右,編譯器開始有效地優化這些代碼,因為它們只會在首次使用時為本地變量分配空間,並在沒有進一步使用時對其進行解除分配。 無論聲明變量的位置 - 優化編譯器都無關緊要。

大約在同一時間,C ++解除了C所具有的變量聲明限制,並允許變量在任何地方聲明。 但是,C實際上並沒有在1999年之前用更新的C99標准解決這個問題。 在現代C中,您可以在任何地方聲明變量。

因此,除非您使用的是一個令人難以置信的古老編譯器,否則您的兩個示例之間絕對沒有性能差異。 然而,盡可能地縮小變量的范圍被認為是良好的編程習慣 - 盡管不應以犧牲可讀性為代價。

雖然這只是一種風格問題,但我個人更喜歡這樣編寫你的函數:

(請注意,uint8_t使用了錯誤的printf格式說明符)

#include <inttypes.h>

static void LoopFunction (void)
{
  for(uint8_t i=0; i < 100; i++)
  {
    uint8_t x = i;
    // do some calculations
    uartTxLine("%" PRIu8, x); 
  }

 //more code
}

舊C只允許在塊的頂部聲明(和初始化)變量。 您可以在塊內的任何位置初始化一個新塊(一對{}字符),這樣您就可以使用它們在代碼旁邊聲明變量:

... /* inside a block */
{ int x = 3;
    /* use x */
} /* x is not adressabel past this point */

你可以在switch語句, if語句和whiledo語句(你可以在哪里初始化一個新塊)中執行此操作

現在,您可以在允許語句的任何地方聲明變量,並且該變量的范圍從聲明點到您已聲明它的內部嵌套塊的末尾。

編譯器決定何時為局部變量分配存儲,因此,您可以在創建堆棧幀時分配所有這些(這是gcc方式,因為它只分配一次局部變量)或者當您輸入定義塊時(例如,Microsoft C就是這樣做的。)在運行時分配空間需要在運行時推進堆棧指針,所以如果每堆棧幀只執行一次,則可以節省cpu周期(但浪費內存位置)。 這里重要的是你不允許引用其作用域定義之外的變量位置,所以如果你試圖這樣做,你將得到未定義的行為 我發現了一個很長時間在互聯網上運行的舊bug,因為沒有人花時間使用Microsoft-C編譯器(在核心轉儲中失敗)編譯該程序,而不是通常使用GCC編譯它。 代碼在代碼的其他部分通過引用使用在內部作用域中定義的局部變量(if語句的then部分)(因為所有內容都在main函數上,堆棧框架始終存在)Microsoft-C只是在退出if語句時重新分配空間,但是GCC等到main完成時才這樣做。 只需在變量聲明中添加一個static修飾符(使其成為全局)就可以解決這個問題,而不再需要進行重構。

int main()
{
    struct bla_bla *pointer_to_x;
    ...
    if (something) {
        struct bla_bla x;
        ...
        pointer_to_x = &x;
    }
    /* x does not exist (but it did in gcc) */
    do_something_to_bla_bla(pointer_to_x); /* wrong, x doesn't exist */
} /* main */

當改為:

int main()
{
    struct bla_bla *pointer_to_x;
    ...
    if (something) {
        static struct bla_bla x;  /* now global ---even if scoped */
        ...
        pointer_to_x = &x;
    }
    /* x is not visible, but exists, so pointer_to_x continues to be valid */
    do_something_to_bla_bla(pointer_to_x); /* correct now */
} /* main */

暫無
暫無

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

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