2009-03-02 21 views
4

我只寫了幾個星期的C,並沒有花時間擔心自己對malloc()太多了。然而,最近我的一個程序返回了一串快樂的面孔,而不是我期望的真實/錯誤值。C會自動爲我分配內存嗎?

如果我創建這樣一個結構:

typedef struct Cell { 
    struct Cell* subcells; 
} 

再後來初始化像這樣

Cell makeCell(int dim) { 
    Cell newCell; 

    for(int i = 0; i < dim; i++) { 
    newCell.subcells[i] = makeCell(dim -1); 
    } 

    return newCell; //ha ha ha, this is here in my program don't worry! 
} 

難道我要結束了訪問存儲在存儲器中幸福的笑臉的地方,或者是寫在先前存在的單元格上,還是什麼?我的問題是,當我沒有實際使用malloc()編譯適當的內存量時,C如何分配內存?什麼是默認值?

回答

16

指針沒有默認值。你的指針將指向它當前存儲的任何內容。由於您尚未初始化,因此行

newCell.subcells[i] = ... 

有效地訪問內存的某些不確定部分。請記住,子單元[i]是相當於

*(newCell.subcells + i) 

如果左側包含一些垃圾,你最終會加入到i垃圾值,並在該位置不確定訪問內存。正如你說正確的,你必須初始化指針指向一些有效的內存區域:

newCell.subcells = malloc(bytecount) 

該線後,您可以訪問,很多字節。關於其他內存來源,有不同類型的存儲都有其用途。你得到的是什麼類型取決於你擁有什麼樣的對象,以及你告訴編譯器使用哪個存儲類。

  • malloc返回指向沒有類型的對象的指針。您可以將指針指向該內存區域,並且該對象的類型將有效成爲指向對象類型的類型。內存沒有初始化爲任何值,訪問速度通常較慢。如此獲得的物體被稱爲allocated objects
  • 您可以在全局放置對象。他們的記憶將被初始化爲零。對於點,你會得到NULL指針,對於浮點數你也會得到一個合適的零。你可以依靠一個合適的初始值。
  • 如果您有局部變量但使用static存儲類說明符,那麼您將具有與全局對象相同的初始值規則。內存通常以與全局對象相同的方式分配,但這絕不是必需的。
  • 如果您有沒有任何存儲類說明符或auto的局部變量,那麼您的變量將被分配到堆棧上(即使C沒有這樣定義,這是編譯器實際上做的事情)。你可以使用它的地址,在這種情況下編譯器將不得不忽略優化,比如把它放到寄存器當然。
  • 與存儲類說明符register一起使用的局部變量被標記爲具有特殊存儲。因此,你不能再使用它的地址了。在最近的編譯器中,由於其複雜的優化器,通常不需要使用register。如果你真的很專業,那麼如果使用它,你可能會得到一些性能。

對象具有關聯的存儲持續時間,可用於顯示不同的初始化規則(正式地,它們只定義至少對象存在多久)。使用autoregister聲明的對象具有自動存儲持續時間,並且已初始化爲而不是。如果你想讓它們包含某些值,你必須明確地初始化它們。如果你不這樣做,它們將包含編譯器在開始生命週期之前留在堆棧上的任何東西。由malloc(或該家族的另一個功能,如calloc)分配的對象已分配了存儲時間。他們的存儲是而不是初始化。當使用calloc時,例外情況是內存被初始化爲零(「真實」零,即所有字節爲0x00,不考慮任何空指針表示)。用static和全局變量聲明的對象具有靜態存儲持續時間。他們的存儲已初始化爲適合其各自類型的零。請注意,對象不得有類型,但獲取無類型對象的唯一方法是使用分配的存儲。 (C中的對象是「存儲區域」)。

那麼,什麼是什麼?這是固定的代碼。因爲一旦你分配了一塊內存,你就無法再回頭分配多少物品了,最好是總是把這個數存儲在某個地方。我已經將變量dim引入了獲取計數值的結構。

Cell makeCell(int dim) { 
    /* automatic storage duration => need to init manually */ 
    Cell newCell; 

    /* note that in case dim is zero, we can either get NULL or a 
    * unique non-null value back from malloc. This depends on the 
    * implementation. */ 
    newCell.subcells = malloc(dim * sizeof(*newCell.subcells)); 
    newCell.dim = dim; 

    /* the following can be used as a check for an out-of-memory 
    * situation: 
    * if(newCell.subcells == NULL && dim > 0) ... */ 
    for(int i = 0; i < dim; i++) { 
    newCell.subcells[i] = makeCell(dim - 1); 
    } 

    return newCell; 
} 

現在,事情是這樣的昏暗= 2:

Cell { 
    subcells => { 
    Cell { 
     subcells => { 
     Cell { subcells => {}, dim = 0 } 
     }, 
     dim = 1 
    }, 
    Cell { 
     subcells => { 
     Cell { subcells => {}, dim = 0 } 
     }, 
     dim = 1 
    } 
    }, 
    dim = 2 
} 

注意,在C,不需要函數的返回值是一個對象。根本不需要存儲。因此,你不能改變它。例如,以下是不可能的:

makeCells(0).dim++ 

您將需要一個「自由功能」,再次釋放分配的內存。因爲分配的對象的存儲不會自動釋放。您必須撥打free以釋放樹中每個subcells指針的內存。這是留給你寫的練習:)

4

改爲在堆上分配了任何未在堆上分配的內容(通過malloc和類似的調用)。因此,在函數結束時,在特定函數中創建的任何內容都不會被破壞。包括返回的對象;當函數調用後展開堆棧時,返回的對象被複制到由調用者函數爲堆棧留出的空間中。

警告:如果你想返回具有指向其他對象的一個​​對象,確保對象指向是在堆上創建的,更好的是,在堆上創建對象,也除非它不打算在其創建的功能中存在。

26

簡短回答:它沒有分配給你。

稍長的答案:subcells指針未初始化的並且可能指向任何地方。這是一個錯誤,你應該永遠不要讓它發生。

更長的答案仍然是:自動變量在堆棧上分配,全局變量由編譯器分配,並經常佔用特殊段或可能在堆中。全局變量默認初始化爲零。自動變量沒有默認值(它們只是獲取內存中的值),程序員負責確保它們具有良好的起始值(儘管許多編譯器會在您忘記時嘗試提示您)。

您的函數中的newCell變量是自動的,並未初始化。你應該解決這個問題。或者給newCell.subcells一個有意義的值,或者指向NULL,直到你爲它分配一些空間。這樣,如果您在爲其分配內存之前嘗試對其進行解引用,則會引發分段違例。

更糟糕的是,您將按值返回Cell,但在嘗試填充subcells陣列時將其分配給Cell *。返回指向堆分配對象的指針,或者將該值分配給本地分配的對象。

這樣做的一個常用成語本來的形式類似

Cell* makeCell(dim){ 
    Cell *newCell = malloc(sizeof(Cell)); 
    // error checking here 
    newCell->subcells = malloc(sizeof(Cell*)*dim); // what if dim=0? 
    // more error checking 
    for (int i=0; i<dim; ++i){ 
    newCell->subCells[i] = makeCell(dim-1); 
    // what error checking do you need here? 
    // depends on your other error checking... 
    } 
    return newCell; 
} 

雖然我已經離開你幾個問題敲定..

而且請注意,你必須追蹤所有的最終需要釋放的內存位...

+0

全局變量被初始化爲零。您省略了文件靜態和函數靜態變量;出於練習的目的,它們更像全局變量(初始化爲零),而不是自動變量。 – 2009-03-02 22:54:01

0

局部變量在堆棧上「分配」。該堆棧是一個預分配的內存量來存放這些局部變量。當函數退出時,變量將停止有效,並且會被接下來的任何內容覆蓋。

在你的情況下,代碼沒有做任何事,因爲它沒有返回你的結果。此外,當範圍退出時,指向堆棧上的對象的指針也將停止有效,所以我想在您的確切情況下(您似乎正在執行鏈接列表),您將需要使用malloc()。

3

我的問題是,當我沒有真正malloc()編輯適當數量的內存時,C如何分配內存?什麼是默認值?

不分配內存。您必須明確地在堆棧上或動態地創建它。

在您的示例中,子單元指向未定義的位置,這是一個錯誤。你的函數應該在某個時候返回一個指向Cell結構體的指針。

0

我會最終訪問存儲在內存中的快樂的面孔,或者可能寫在以前存在的單元格,或什麼?

你很幸運,你有一張開心的臉。在那些倒黴的日子之一,它可能已經消滅你的系統清潔;)

我的問題是,如何Ç分配內存時,我有沒有真正的malloc()的內存適量?

它沒有。然而,當你定義Cell newCell時,會發生什麼,subCells指針被初始化爲垃圾值。這可能是0(在這種情況下,你會崩潰)或一些足夠大的整數,使它看起來像一個實際的內存地址。在這種情況下,編譯器會高興地獲取所有駐留在其中的值並將其返回給您。

默認設置是什麼?

這是行爲,如果你沒有初始化你的變量。而你的makeCell功能看起來有點欠發達。

0

真的有三個部分可以分配東西 - 數據,堆棧&堆。

在你提到的情況下,它將被分配在堆棧上。在堆棧上分配一些東西的問題是它只在函數的持續時間內有效。一旦你的函數返回,這個內存就會被回收。所以,如果你返回一個指向堆棧上分配的東西的指針,那個指針將是無效的。如果您返回實際的對象(不是指針),則會自動爲調用函數使用該對象的副本。

如果您已將其聲明爲全局變量(例如在頭文件中或函數外部),它將分配在內存的數據部分中。本節中的內存在程序啓動時自動分配,並在完成時自動釋放。

如果你使用malloc()在堆上分配內存,那麼只要你想使用它 - 直到你調用free()它釋放的時候。這使您可以根據需要靈活地分配和釋放內存(而不是使用全局分配在前的全局變量,而只在程序終止時釋放)。

0

我要假裝我的電腦在這裏,閱讀此代碼...

typedef struct Cell { 
    struct Cell* subcells; 
} 

這告訴我:

  • 我們有一個叫做細胞
  • 結構類型它包含一個叫做子單元的指針
  • 指針應該是某種類型的結構單元格

它不告訴我指針是指向一個Cell還是一個Cell數組。當一個新的Cell被創建時,該指針的值是未定義的,直到給它賦值。在定義它們之前使用指針是個壞消息。

Cell makeCell(int dim) { 
    Cell newCell; 

新的Cell結構,帶有未定義的子單元指針。所有這些都會保留一小塊內存,稱爲cell結構體的大小。它不會改變那個記憶中的值 - 它們可以是任何東西。

for(int i = 0; i < dim; i++) { 
    newCell.subcells[i] = makeCell(dim -1); 

爲了得到newCell.subcells [I]中,計算是由用i從子電池,以抵消,然後就是解除引用。具體來說,這意味着該值是從該內存地址中提取的。舉例來說,我== 0 ...然後,我們將取消引用子單元格指針本身(無偏移量)。由於子單元是未定義的,它可能是任何東西。字面上任何事情!所以,這會要求從內存中完全隨機的地方獲取價值。結果沒有任何保證。它可能會打印某些內容,可能會崩潰。這絕對不應該這樣做。

} 

    return newCell; 
} 

無論何時您使用指針,在取消引用前確保將其設置爲值是很重要的。鼓勵你的編譯器給你任何警告,許多現代編譯器都可以捕捉到這類事情。你也可以指定可愛的默認值,如0xdeadbeef(yup!這是一個十六進制的數字,它也是一個單詞,所以它看起來很有趣),以便它們脫穎而出。 (printf的%p選項有助於顯示指針,作爲調試的一種粗略形式,調試器程序也可以很好地顯示它們。)