![](/img/trans.png)
[英]Why is my code working when I haven't allocated enough memory using malloc()?
[英]Why can I write and read memory when I haven't allocated space?
我正在嘗試從頭開始以C語言構建自己的哈希表,作為一次練習,我一次只做了一個小步驟。 但我有一個小問題...
我將哈希表結構聲明為指針,這樣我就可以用所需的大小對其進行初始化,並在負載系數較高時增加它的大小。
問題是我正在創建一個僅包含2個元素的表(僅用於測試目的),我僅為這2個元素分配內存,但是我仍然能夠寫入不應該的內存位置。 而且我還可以讀取未寫入的內存位置。
這是我當前的代碼:
#include <stdio.h>
#include <stdlib.h>
#define HASHSIZE 2
typedef char *HashKey;
typedef int HashValue;
typedef struct sHashTable {
HashKey key;
HashValue value;
} HashEntry;
typedef HashEntry *HashTable;
void hashInsert(HashTable table, HashKey key, HashValue value) {
}
void hashInitialize(HashTable *table, int tabSize) {
*table = malloc(sizeof(HashEntry) * tabSize);
if(!*table) {
perror("malloc");
exit(1);
}
(*table)[0].key = "ABC";
(*table)[0].value = 45;
(*table)[1].key = "XYZ";
(*table)[1].value = 82;
(*table)[2].key = "JKL";
(*table)[2].value = 13;
}
int main(void) {
HashTable t1 = NULL;
hashInitialize(&t1, HASHSIZE);
printf("PAIR(%d): %s, %d\n", 0, t1[0].key, t1[0].value);
printf("PAIR(%d): %s, %d\n", 1, t1[1].key, t1[1].value);
printf("PAIR(%d): %s, %d\n", 3, t1[2].key, t1[2].value);
printf("PAIR(%d): %s, %d\n", 3, t1[3].key, t1[3].value);
return 0;
}
您可以輕松地看到我沒有為(*table)[2].key = "JKL";
分配空間(*table)[2].key = "JKL";
也(*table)[2].value = 13;
。 我也不應該在main()
讀取最后2個printfs
中的內存位置。
有人可以向我解釋一下,如果我可以/應該對此做任何事情?
編輯:
好的,我已經意識到上面代碼的一些問題,這很糟……但是我現在有一個類,無法更新我的問題。 有時間我會更新。 對於那個很抱歉。
編輯2:
抱歉,我不應該發布此問題,因為我不想像上面發布的代碼那樣。 我想做的事情略有不同,這使這個問題變得無關緊要。 所以,我只是假設這是我需要一個答案的問題,並接受下面的正確答案之一。 然后,我會發布我的適當問題...
只是不這樣做,這是未定義的行為。
這可能會意外地起作用,因為您寫入/讀取了程序實際上未使用的內存。 否則可能會導致堆損壞,因為您會覆蓋堆管理器為此目的使用的元數據。 或者,您可以覆蓋一些其他不相關的變量,然后很難調試因此而發瘋的程序。 否則可能發生任何其他有害的事情,無論是明顯的還是微妙的但又很嚴重的。
只是不這樣做-只讀/寫您合法分配的內存。
一般而言(針對不同平台的不同實現),當進行基於malloc或類似基於堆的分配調用時,底層庫會將其轉換為系統調用。 當庫執行此操作時,通常會在區域集中分配空間-等於或大於程序請求的數量。
這樣做是為了防止頻繁的系統調用內核進行分配,並防止更快地滿足程序對Heap的請求(這當然不是唯一的原因!!!!!!!!!
落入這樣的安排會導致您正在觀察的問題。 再一次,並非總是需要您的程序能夠寫入未分配的區域而不會每次都崩潰/ seg-fault-這取決於特定二進制文件的內存安排。 嘗試寫入更高的數組偏移量-程序最終將出錯。
至於您應該/不應該做的事情-做出上述答復的人們總結得很好。 我沒有更好的答案,除了應該避免這種問題,而且只有在分配內存時要小心才能做到。
一種理解的方式是通過這個簡單的示例:當您在用戶空間中請求1個字節時,內核必須至少分配整個頁面(例如,在某些Linux系統上為4Kb,這是內核級別上最細粒度的分配)。 為了通過減少頻繁調用來提高效率,內核將整個頁面分配給調用庫-當有更多請求進入時,庫可以分配該庫。因此,將請求寫入或讀取到這樣的區域可能不一定會產生錯誤。 那只是意味着垃圾。
在C中,您可以讀取到任何映射的地址,也可以寫入到任何映射到具有讀寫區域的頁面的地址。
在實踐中,操作系統通常以8K的塊(頁面)形式提供進程內存(但這取決於操作系統)。 然后,C庫管理這些頁面,並維護空閑空間和已分配空間的列表,並在被malloc要求時提供這些塊的用戶地址。
因此,當您從malloc()返回指針時,您指向的是8k頁中可寫的區域。 這個區域可能包含垃圾,或者包含其他已分配的內存,它可能包含用於堆棧變量的內存,甚至可能包含C庫用來管理可用/已分配內存列表的內存!
因此,您可以想象,寫入超出您已分配的范圍的地址確實會導致問題:
所有這些都是調試的難題,因為崩潰通常比損壞發生的時間晚得多。
僅當您從不對應於映射頁面的地址讀取/寫入該地址時,您才會崩潰...例如,從地址0x0(NULL)讀取
Malloc,Free和指針在C語言中非常脆弱(在C ++中則稍差一些),很容易意外地使自己陷入困境
有很多用於內存檢查的第三方工具,它們用檢查代碼來包裝每個內存分配/空閑/訪問。 它們的確會使您的程序變慢,具體取決於應用了多少檢查。
將記憶想象成一塊巨大的大黑板,分成幾個小方塊。 寫入存儲位置等同於擦除正方形並在其中寫入新值。 通常, malloc
的目的不是要使內存(黑板上的方塊)存在。 相反,它是要識別未用於其他任何用途的內存區域(一組正方形),並采取一些措施以確保除非另行通知,否則不會將其用於其他任何用途。 從歷史上看,微處理器將系統的所有內存公開給應用程序是很普遍的。 從理論上講,一段代碼Foo
可以選擇一個任意地址並將其數據存儲在其中,但有兩個主要警告:
較新的系統包括更多監視功能,以跟蹤哪些進程擁有哪些內存區域,並殺死訪問它們不擁有的內存的進程。 在許多這樣的系統中,每個進程往往會先小黑板,如果試圖以malloc
多平方比可用,過程可根據需要給予黑板區域的新塊。 但是,每個過程通常會有一些黑板區域可供使用,而這些區域尚未保留用於任何特定目的。 理論上,代碼可以使用這樣的區域來存儲信息而不必費心先分配信息,並且如果沒有任何事情將內存用於其他任何目的,則這樣的代碼可以工作,但是不能保證不會將此類內存區域用於在一些意想不到的時間達到其他目的。
通常, malloc
將分配比您所需的更多內存以用於對齊目的。 同樣是因為該進程確實具有對堆內存區域的讀/寫訪問權限。 因此,讀取分配區域之外的幾個字節很少會觸發任何錯誤。
但是您仍然不應該這樣做 。 由於您正在寫入的內存可能被視為未占用或實際上已被其他人占用,因此任何事情都可能發生,例如第二和第三鍵/值對稍后會變成垃圾,或者不相關的重要功能會由於您的一些無效數據而崩潰。 “已經踩踏到它malloc
-ed存儲器。
(另外,請使用char[≥4]
作為鍵的類型或malloc
鍵,因為如果不幸的是,該鍵存儲在堆棧中,則以后將變得無效。)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.