【C進階】動態內存管理

碼字不易,對你有幫助 點贊/轉發/關注 支持一下作者
微信搜公衆號:不會編程的程序圓
看更多幹貨,獲取第一時間更新

想看更好的排版可以閱讀原文
點擊閱讀原文

思維導圖


目錄


正文


零 簡單瞭解內存區域劃分

一 動態內存函數

1.1 malloc

malloc -> memory allocate

void* malloc (size_t size)

size_t 類型就是 unsigned long

庫函數:stdlib.h

解釋:

分配 size 字節的未初始化內存。

若分配成功,則返回爲任何擁有基礎對齊的對象類型對齊的指針。

size 爲零,則 malloc 的行爲是實現定義的。例如可返回空指針。亦可返回非空指針;但不應當解引用這種指針,而且應將它傳遞給 free 以避免內存泄漏。

**參數:**size - 要分配的字節數

返回值:

成功時,返回指向新分配內存的指針。爲避免內存泄漏,必須用 free()realloc() 解分配返回的指針。

失敗時,返回空指針。

英文文檔:

Allocate memory block

Allocates a block of size bytes of memory, returning a pointer to the beginning of the block.

The content of the newly allocated block of memory is not initialized, remaining with indeterminate values.

If size is zero, the return value depends on the particular library implementation (it may or may not be a null pointer), but the returned pointer shall not be dereferenced.

Parameters

size

Size of the memory block, in bytes.
size_t is an unsigned integral type.

Return Value

On success, a pointer to the memory block allocated by the function.

The type of this pointer is always void*, which can be cast to the desired type of data pointer in order to be dereferenceable.

If the function failed to allocate the requested block of memory, a null pointer is returned.

例1:malloc
#include<stdio.h>
#include<stdlib.h>

int main(void) {

	int size;
	printf("請輸入元素個數:");
	scanf("%d", &size);
	int* arr = (int*)malloc(size * sizeof(int));
	//內存申請失敗返回 空指針
	if (arr == NULL) {
		printf("內存申請失敗!\n");
		return 0;
	}
	for (int i = 0; i < size; i++) {
		arr[i] = i;
		printf("%d\n", i);
	}

	free(arr);
	return 0;
}

1.2 free

void free( void* ptr )

**頭文件:**stdlib.h

解釋:

解分配之前由 malloc()calloc()realloc() 分配的空間

ptr 爲空指針,則函數不進行操作。

ptr 的值不等於之前從 malloc()calloc()realloc() 返回的值,則行爲未定義。

ptr 所指代的內存區域已經被解分配,則行爲未定義,即是說已經以ptr 爲參數調用 free()realloc() ,而且沒有後繼的 malloc()calloc()realloc() 調用以 ptr 爲結果。

若在 free() 返回後通過指針 ptr 訪問內存,則行爲未定義(除非另一個分配函數恰好返回等於 ptr 的值)。

參數: ptr - 指向要解分配的內存的指針

返回值:

**注意:**此函數接收空指針(並對其不處理)以減少特例的數量。不管分配成功與否,分配函數返回的指針都能傳遞給 free()

英文文檔

Deallocate memory block

A block of memory previously allocated by a call to malloc, calloc or realloc is deallocated, making it available again for further allocations.

If ptr does not point to a block of memory allocated with the above functions, it causes undefined behavior.

If ptr is a null pointer, the function does nothing.

Notice that this function does not change the value of ptr itself, hence it still points to the same (now invalid) location.

Paramaters

ptr

Pointer to a memory block previously allocated with malloc, calloc or realloc.

Return Value

none

If ptr does not point to a memory block previously allocated with malloc, calloc or realloc, and is not a null pointer, it causes undefined behavior.

1.3 calloc

void* calloc( size_t num, size_t size )

**頭文件:**stdlib.h

解釋:

num 個對象的數組分配內存,並初始化所有分配存儲中的字節爲零。

若分配成功,會返回指向分配內存塊最低位(首位)字節的指針,它爲任何類型適當地對齊。

size 爲零,則行爲是實現定義的(可返回空指針,或返回不可用於訪問存儲的非空指針)。

參數:

num - 對象數目

size - 每個對象的大小

返回值:

成功時,返回指向新分配內存的指針。爲避免內存泄漏,必須用 free()realloc() 解分配返回的指針。

失敗時,返回空指針。

注意:

因爲對齊需求的緣故,分配的字節數不必等於 num*size

初始化所有位爲零不保證浮點數或指針被各種初始化爲 0.0 或空指針(儘管這在所有常見平臺上爲真)。

英文文檔:

void* calloc (size_t num, size_t size);

Allocate and zero-initialize array

Allocates a block of memory for an array of num elements, each of them size bytes long, and initializes all its bits to zero.

The effective result is the allocation of a zero-initialized memory block of (num*size) bytes.

If size is zero, the return value depends on the particular library implementation (it may or may not be a null pointer), but the returned pointer shall not be dereferenced.

Parameters

  • num

Number of elements to allocate.

  • size

Size of each element.

size_t - is an unsigned integral type.

Return Value

On success, a pointer to the memory block allocated by the function.

The type of this pointer is always void*, which can be cast to the desired type of data pointer in order to be dereferenceable.

If the function failed to allocate the requested block of memory, a null pointer is returned.

例2:calloc
#include<stdio.h>
#include<stdlib.h>

int main(void) {

	int* p1 = (int*)calloc(4, sizeof(int));//分配並清零 4 個 int 的數組
	int* p2 = (int*)calloc(1, sizeof(int[4]));//等價,直接命名數組類型
	int* p3 = (int*)calloc(4, sizeof *p3);//等價,免去重複類型名

	if (p2) {
		for (int n = 0; n < 4; n++)
			printf("p2[%d] == %d\n", n, p2[n]);
	}

	free(p1);
	free(p2);
	free(p3);

	return 0;
}

1.4 realloc

英文文檔:

void* realloc (void* ptr, size_t size)

Reallocate memory block

Changes the size of the memory block pointed to by ptr.

The function may move the memory block to a new location (whose address is returned by the function).

The content of the memory block is preserved up to the lesser of the new and old sizes, even if the block is moved to a new location. If the new size is larger, the value of the newly allocated portion is indeterminate.

In case that ptr is a null pointer, the function behaves like malloc, assigning a new block of size bytes and returning a pointer to its beginning.

C90:

Otherwise, if size is zero, the memory previously allocated at ptr is deallocated as if a call to free was made, and a null pointer is returned.

C99/C11:

If size is zero, the return value depends on the particular library implementation: it may either be a null pointer or some other location that shall not be dereferenced.

If the function fails to allocate the requested block of memory, a null pointer is returned, and the memory block pointed to by argument ptr is not deallocated (it is still valid, and with its contents unchanged).

Parameter

ptr

Pointer to a memory block previously allocated with malloc, calloc or realloc.
Alternatively, this can be a null pointer, in which case a new block is allocated (as if malloc was called).

size

New size for the memory block, in bytes.
size_t is an unsigned integral type.

Return Value

A pointer to the reallocated memory block, which may be either the same as ptr or a new location.
The type of this pointer is void*, which can be cast to the desired type of data pointer in order to be dereferenceable.

C90:

A null-pointer indicates either that size was zero (an thus ptr was deallocated), or that the function did not allocate storage (and thus the block pointed by ptr was not modified).

C99:

A null-pointer indicates that the function failed to allocate storage, and thus the block pointed by ptr was not modified.

例3:realloc
#include<stdio.h>
#include<stdlib.h>

int main(void) {

	int* pa = (int*)malloc(3 * sizeof(int));
	if (pa) {
		printf("%zu bytes allocated. Storing ints:\n", 3 * sizeof(int));
		for (int i = 0; i < 3; i++)
			printf("%d\n", pa[i] = i);
	}
	
	int* pb = (int*)malloc(10 * sizeof(int));
	if (pb) {
		printf("%zu bytes allocated. First 3 ints:\n", 10 * sizeof(int));
		for (int i = 0; i < 3; i++)
			printf("%d\n", pb[i] = i);
	}
	else { // 如果 realloc 返回 NULL,表示擴容失敗,我們需要 free pa指向的地址
		free(pa);
	}
	//如果成功,free pb即可,如果 pb 與 pa 指向的內容不同,那麼 pa 就已經被釋放過了
	free(pb);

	return 0;
}
例3.1:realloc
#include<stdio.h>
#include<stdlib.h>

int main(void) {

	int input, i;
	int count = 0;
	int* numbers = NULL;

	do {
		printf("Ener a number: ");
		scanf("%d", &input);
		count++;

		numbers = (int*)realloc(numbers, count * sizeof(int));

		if (numbers != NULL) {
			numbers[count - 1] = input;
		}
		else {
			free(numbers);
			printf("Error reallocating memory\n");
			exit (1);
		}

	} while (input != 0);

	printf("%zu bytes allocated. Elements as follow:\n", count * sizeof(int));
	for (i = 0; i < count; i++) {
		printf("%d ", numbers[i]);
	}

	free(numbers);

	return 0;
}

1.5 malloc 造成內存泄漏的典例

典例1:由 return 造成的沒有 free
#include<stdio.h>
#include<stdlib.h>

int* Func() {

	int* p = (int*)malloc(sizeof(int) * 10);
	if (p == NULL) {
		return NULL;
	}
	//下面是業務邏輯代碼:
	if (cond1) {
		return p;//滿足條件就return ,p 沒有 free
	}
	if (cond2) {
		return p;//同上
	}
    //執行一些操作
	do_something;

	free(p);

}

int main(void) {

	int* a = Func();

	return 0;
}
典例2:malloc 後沒有 free,再次 malloc
int* Func() {
	
	int* p = (int*)malloc(sizeof(int) * 10);
	p = (int*)malloc(sizeof(int*) * 20);//第一次 malloc 的內存沒有 free
	if (p) {
		return NULL;
	}
	do_something;

	free(p);

}
典例3:指針運算後改變造成 free 失敗
void test(){
    int* p = (int*)malloc(4 * sizeof(int));
    p++;
    free(p);
}
典例4:多次 free 同一塊內存
void test(){
    int* p = (int*)malloc(4 * sizeof(int));
    free(p);
    free(p);
}
典例5:對 NULL 進行解引用操作
void test(){
	int *p = (int *)malloc(INT_MAX/4);
	*p = 20;//如果p的值是NULL,就會有問題
	free(p);
}
典例6:對動態開闢空間的越界訪問
void test(){
	int i = 0;
	int *p = (int *)malloc(10*sizeof(int));
	if(NULL == p){
	exit(EXIT_FAILURE);
	}
	for(i=0; i<=10; i++){
	*(p+i) = i;//當i是10的時候越界訪問
	}
	free(p);
}

二 筆試題

1

void GetMemory(char* p)
{
    //形參是實參的拷貝,形參指針不會改變實參指針的指向
	p = (char*)malloc(100);
}
void Test(void)
{
	char* str = NULL;
	GetMemory(str);
	strcpy(str, "hello world");
	printf("%s", str);
}
//請問運行Test 函數會有什麼樣的結果?
程序崩潰
  1. malloc 之後要判空
  2. 沒有 free

2

char* GetMemory(void)
{
	char p[] = "hello world";
	return p;
    //函數結束,p 的地址內的內容是未知的
}
void Test(void)
{
	char* str = NULL;
	str = GetMemory();
	printf("%s", str);
}

3

void GetMemory(char** p, int num)
{
	*p = (char*)malloc(num);
}
void Test(void)
{
	char* str = NULL;
	GetMemory(&str, 100);
	strcpy(str, "hello");
	printf("%s", str);
}
//沒有 free 

4

void Test(void)
{
	char* str = (char*)malloc(100);
	strcpy(str, "hello");
	free(str);
	if (str != NULL)
	{
		strcpy(str, "world");
		printf(str);
	}
}
// 訪問非法內存
// free 操作不會改變 str 內容

三 柔性數組

柔性數組(flexible array):C99 中,結構中的最後一個元素允許是未知大小的數組,這就叫做『柔性數組』成員。

1. 特點

  • 結構中的柔性數組成員前面必須至少一個其他成員。
  • sizeof 返回的這種結構大小不包括柔性數組的內存。
  • 包含柔性數組成員的結構用malloc ()函數進行內存的動態分配,並且分配的內存應該大於結構的大小,以適應柔性數組的預期大小

2. 使用方法

1. 定義結構體
typedef struct Test {
	int i;
	int a[];// 也可寫成 int a[0]
}Test;
2. 使用
int main(void) {

	Test* test = (Test*)malloc(sizeof(Test) + sizeof(int) * 100);
    
	for (int i = 0; i < 100; i++) {
		test->a[i] = i;
	}
	free(test);
	return 0;
}
3. 反例

如果我們不用 “柔性數組”,而這樣 定義結構體和使用 可不可以實現自定義數組大小呢?

typedef struct Test {
	int i;
	int* a;// 修改
}Test;

int main(void) {

	Test* test = (Test*)malloc(sizeof(Test) + sizeof(int) * 100);

	for (int i = 0; i < 100; i++) {
		test->a[i] = i;
	}
	
    free(test);
	return 0;
}

這樣顯然是不行的,那我們應該如何寫呢?

正確的寫法是 malloc 兩次

typedef struct Test {
	int i;
	int* a;
}Test;

int main(void) {

	Test* test = (Test*)malloc(sizeof(Test));
	test->a = (int*)malloc(sizeof(int) * 100);

	for (int i = 0; i < 100; i++) {
		test->a[i] = i;
	}
	
	free(test->a);
	free(test);

	return 0;
}

malloc 兩次 free 也得兩次,所以這種寫法還是十分麻煩的。

補:printf(str)

int main(void) {

	char* str = "Hello";

	printf(str);

	return 0;
}

這個程序會正常輸出 str 的內容。因爲 char* 的類型,而 printf format 的格式是 char const* const ,C語言的對類型之間的標準比較模糊,所以沒有報錯,起始這種寫法類似於:

printf("Hello");

參考資料:cppreference.com cplusplus.com

在 Github 上看更全的目錄:

https://github.com/hairrrrr/C-CrashCourse

以後的這個系列的代碼都會上傳上去,歡迎 star


以上就是本次的內容。

如果文章有錯誤歡迎指正和補充,感謝!

最後,如果你還有什麼問題或者想知道到的,可以在評論區告訴我呦,我可以在後面的文章加上你們的真知灼見​​。

關注我,看更多幹貨!

我是程序圓,我們下次再見。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章