首頁 >後端開發 >C++ >了解 C 中的記憶體管理、指標和函數指針

了解 C 中的記憶體管理、指標和函數指針

王林
王林原創
2024-07-17 00:10:111273瀏覽

Understanding Memory Management, Pointers, and Function Pointers in C

在 C 程式設計中,高效的記憶體管理和指標的使用對於創建健全且高效能的應用程式至關重要。本指南全面概述了不同類型的記憶體、指針、引用、動態記憶體分配和函數指針,並提供了範例來幫助您掌握這些概念。無論您是 C 語言新手還是希望加深理解,本指南都涵蓋了增強您的編碼技能的基本主題。

不同類型的記憶。

在使用 C 語言程式設計時,您會遇到 5 種不同類型的記憶體。

1。文字片段

您編譯的程式碼儲存在這裡。您的程式的機器碼指令。文字段是唯讀的,因此您無法更改任何數據,但可以存取它。接下來我們將討論函數指標。這些指標指向該段中的一個函數。

2。初始化資料段

全域變數和靜態變數儲存在此處,在程式運行之前具有特定值,並在整個程式中保持可存取性。

靜態變數和全域變數之間的區別在於作用域,靜態變數可以在函數或區塊中訪問,它是已定義的,但全域變數可以從程式中的任何位置存取。

函數執行完畢後,普通變數將被刪除,而靜態變數仍然保留。

3。統一資料段(BSS)

與初始化資料段基本上相同,但由沒有分配特定值的變數組成。預設情況下它們的值為 0。

4。堆

這裡有動態內存,程式設計師可以在運行時管理它。您可以使用 malloc、calloc、realloc 和 free 等函數分配記憶體和釋放記憶體。

5。堆疊

您可能對堆疊有些熟悉。堆疊記憶體透過儲存局部變數、參數和返回值來管理函數執行。函數執行完畢後,棧中的記憶體將被刪除。

資料類型和儲存量。

在 C 中,有不同的資料類型,最常見的是 int、float、double 和 char。我不會過多談論資料類型,但重要的是要知道特定資料類型在記憶體中有多少位元組。這是一個清單,請記住。

整數:4 個位元組,
浮點數:4 個位元組,
雙精確度:8 位元組,
字元:1 個位元組。

您可以使用 sizeof() 方法檢查資料類型的大小。

指針和參考文獻。

當您指派一個變數時;

int number = 5; 

系統會將值 5 儲存在記憶體中。但記憶在哪裡?

記憶體中實際上存在位址,這就是您可以追蹤所儲存的值的方式。

引用是變數的位址。很酷吧?

要存取變數的引用,請使用 & 後接變數名稱。

要列印控制台的引用,我們使用 p 格式說明符。

int number = 5;

printf(“%d”, number);
// prints out 5 

printf(“%p”, &number);
// prints out the ref: 0x7ffd8379a74c

您可能會列印出另一個位址。

現在要追蹤該引用,請使用指標來儲存引用。

使用*.建立指標變數

int number; 

int* pointer = &number;

printf(“%p”, pointer);
// 0x7ffd8379a74c

要從指標取得值,可以使用取消引用。若要取消引用某個值,請在指標前使用 *。所以 * 既可以用來創建指針,也可以取消引用它,但是在不同的上下文中。

int number = 5; // create variable.

int* pointer = &number //store the reference in the pointer

printf(“%p”, pointer); // print the reference
// 0x7ffd8379a74c

printf(“%d”, *pointer); // dereference the value. 
// 5

這很強大,因為使用指針,您可以透過引用傳遞值,而不是複製值。這是內存高效且高效能的。

當您在高階語言中將值作為參數傳遞給函數時,您會複製這些值,但在 C 中,您可以發送參考並直接在記憶體中操作該值。

#include <stdio.h>

void flipValues(int *a, int *b) { // receives two pointers of type int
  int temp = *a; // dereference pointer a and store it’s value in temp
  *a = *b; // dereference pointer b and store in pointer a
  *b = temp; // dereference b and change value to the value of temp
}

int main(void) {
  int a = 20;
  int b = 10;
  flipValues(&a, &b); // pass the references of a and b
  printf("a is now %d and b is %d", a, b);
  // a is now 10 and b is 20
  return 0;
}

指標可以宣告為 int* name ;或 int *名稱;兩種風格都是正確的並且可以互換。

分配和釋放動態記憶體。

當你宣告一個變數時,如 int num = 5;在包含主函數在內的函數內部,該變數儲存在堆疊中,當函數執行完畢時,變數將會被刪除。

但是現在我們將在堆中動態分配記憶體。然後我們就可以完全控制我們需要多少內存,並且它將持續存在,直到我們釋放它為止。

Malloc 和 Calloc。

我們可以使用函數malloc或calloc來分配記憶體。

Malloc 採用一個參數,表示要分配的記憶體大小(以位元組為單位)。

Calloc 採用兩個參數,即項目數量和每個項目所佔用的記憶體量(以位元組為單位)。

malloc(大小)
calloc(數量, 大小)

calloc initializes all allocated memory to 0, while malloc leaves the memory uninitialized, making malloc slightly more efficient.

You can use sizeof to indicate how much memory you need. In the example we use malloc to allocate space for 4 integers.

int* data; 
data = malloc(sizeof(int) * 4);

Here is a visualization:

Pointer->[],[],[],[] [],[],[],[] [],[],[],[] [],[],[],[]

  1. Each bracket is a byte so here we see 16 bytes in memory.
  2. The malloc function allocates 4 x 4 bytes in memory, resulting in 16 bytes.
  3. int* data is a pointer of type int, so it points to the 4 first bytes in memory.

Therefore, pointer + 1 moves the pointer one step to the right, referring to the next integer in memory, which is 4 bytes away.

int* data; 
  data = malloc(sizeof(int) * 4);

  *data = 10; // change first value to 10.
  *(data + 1) = 20; // change second value to 20.
  *(data + 2) = 30;
  *(data + 3) = 40;

  for(int i = 0; i < 4; i++) {
      printf("%d\n", *(data + i));
  }

This is how an array work!

When you declare an array, the array name is a pointer to its first element.

int numbers[] = { 10, 20, 30, 40 };
  printf("%p\n", &numbers); 
  printf("%p", &numbers[0]);
  // 0x7ffe91c73c80
  // 0x7ffe91c73c80

As shown, the address of the array name is the same as the address of its first element.

Realloc

Sometimes you want to reallocate memory. A normal use case is when you need more memory then you initially allocated with malloc or calloc.

The realloc function takes 2 parameters. A pointer to where your data currently is located, and the size of memory you need.

int* pointer2 = realloc(*pointer1, size);

The realloc function will first check if the size of data can be stored at the current address and otherwise it will find another address to store it.

It’s unlikely but if there is not enough memory to reallocate the function will return null.

int *ptr1, *ptr2;

// Allocate memory
ptr1 = malloc(4);

// Attempt to resize the memory
ptr2 = realloc(ptr1, 8);

// Check whether realloc is able to resize the memory or not
if (ptr2 == NULL) {
  // If reallocation fails
  printf("Failed. Unable to resize memory");
} else {
  // If reallocation is sucessful
  printf("Success. 8 bytes reallocated at address %p \n", ptr2);
  ptr1 = ptr2;  // Update ptr1 to point to the newly allocated memory
}

Free memory

After you have allocated memory and don’t use it anymore. Deallocate it with the free() function with a pointer to the data to be freed.

After that, it is considered good practice to set the pointer to null so that the address is no longer referenced so that you don’t accidentally use that pointer again.

int *ptr;
ptr = malloc(sizeof(*ptr));

free(ptr);
ptr = NULL;

Remember that the same memory is used in your whole program and other programs running on the computer.

If you don’t free the memory it’s called data leak and you occupy memory for nothing. And if you accidentally change a pointer you have freed you can delete data from another part of your program or another program.

Create a Vector (dynamic array).

You can use your current knowledge to create a dynamic array.

As you may know, you can use structs to group data and are powerful to create data structures.

#include <stdio.h>
#include <stdlib.h>

struct List {
  int *data; // Pointer to the list data
  int elements; // Number  of elements in the list 
  int capacity; // How much memmory the list can hold
};

void append(struct List *myList, int item);
void print(struct List *myList);
void free_list(struct List *myList);

int main() {
    struct List list;
    // initialize the list with no elements and capazity of 5 elements
    list.elements = 0;
    list.capacity = 5;
    list.data = malloc(list.capacity * sizeof(int));
    // Error handeling for allocation data
    if (list.data == NULL) {
    printf("Memory allocation failed");
    return 1; // Exit the program with an error code
    }
    // append 10 elements
    for(int i = 0; i < 10; i++) {
      append(&list, i + 1);
    };
    // Print the list 
    print(&list);
    // Free the list data
    free_list(&list);

    return 0;
}

// This function adds an item to a list
void append(struct List *list, int item) {
    // If the list is full then resize the list with thedouble amount
    if (list->elements == list->capacity) {
    list->capacity *= 2;
    list->data = realloc( list->data, list->capacity * sizeof(int) );
    }
    // Add the item to the end of the list
    list->data[list->elements] = item;
    list->elements++;
}

void print(struct List *list) {
    for (int i = 0; i < list->elements; i++) {
    printf("%d ", list->data[i]);
    } 
}

void free_list(struct List *list) {
    free(list->data);
    list->data = NULL;
}

Static data

Global variables.

When declaring a variable above the main function they are stored in the data segment and are allocated in memory before the program starts and persists throughout the program and is accessible from all functions and blocks.

#include <stdio.h>
int globalVar = 10; // Stored in initilized data segment
int unitilizedGlobalVar; // Stored in uninitialized data segment

int main() {
    printf("global variable %d\n", globalVar);            printf("global uninitilized variable %d", unitilizedGlobalVar);
    // global variable 10
    // global uninitilized variable 0
    return 0;
}

Static variables

They work the same as global ones but are defined in a certain block but they are also allocated before the program starts and persist throughout the program. This is a good way of keeping the state of a variable. Use the keyword static when declaring the variable.

Here is a silly example where you have a function keeping the state of how many fruits you find in a garden using a static variable.

#include <stdio.h>

void countingFruits(int n) {
    // the variable is static and will remain through function calls and you can store the state of the variable
    static int totalFruits;
    totalFruits += n;
    printf( "Total fruits: %d\n", totalFruits);
}

void pickingApples(int garden[], int size) {
    // search for apples
    int totalApples = 0;
    for(int i = 0; i < size; i++) {
        if(garden[i] == 1) {
            totalApples++;
        }
    }
    countingFruits(totalApples);
}

void pickingOranges(int garden[], int size) {
    // search for oranges
    int totalOranges = 0;
    for(int i = 0; i < size; i++) {
        if(garden[i] == 2) {
            totalOranges++;
        }
    }
    countingFruits(totalOranges);
}

int main() {
    // A garden where you pick fruits, 0 is no fruit and 1 is apple and 2 is orange
    int garden[] = {0, 0, 1, 0, 1, 2,
                    2, 0, 1, 1, 0, 0,
                    2, 0, 2, 0, 0, 1
                    };
    // the length of the  garden
    int size = sizeof(garden) / sizeof(garden[0]);
    pickingApples(garden, size); 
    // now the total fruits is 5
    pickingOranges(garden, size);
    // now the total fruits is 9
    return 0;
}

Understanding Function Pointers in C.

Function pointers are a powerful feature in C that allow you to store the address of a function and call that function through the pointer. They are particularly useful for implementing callback functions and passing functions as arguments to other functions.

Function pointers reference functions in the text segment of memory. The text segment is where the compiled machine code of your program is stored.

Defining a Function Pointer

You define a function pointer by specifying the return type, followed by an asterisk * and the name of the pointer in parentheses, and finally the parameter types. This declaration specifies the signature of the function the pointer can point to.

int(*funcPointer)(int, int)

Using a Function Pointer

To use a function pointer, you assign it the address of a function with a matching signature and then call the function through the pointer.

You can call a function of the same signature from the function pointer

#include <stdio.h>

void greet() {
    printf("Hello!\n");
}

int main() {
    // Declare a function pointer and initialize it to point to the 'greet' function
    void (*funcPtr)();
    funcPtr = greet;

    // Call the function using the function pointer
    funcPtr();

    return 0;
}

Passing Function Pointers as Parameters

Function pointers can be passed as parameters to other functions, allowing for flexible and reusable code. This is commonly used for callbacks.

#include <stdio.h>

// Callback 1
void add(int a, int b) {
    int sum  = a + b;
    printf("%d + %d = %d\n", a, b, sum);
}

// Callback 2
void multiply(int a, int b) {
    int sum  = a * b;
    printf("%d x %d = %d\n", a, b, sum);
}

// Math function recieving a callback
void math(int a, int b, void (*callback)(int, int)) {
    // Call the callback function
    callback(a, b);
}

int main() {
    int a = 2;
    int b = 3;

    // Call math with add callback
    math(a, b, add);

    // Call math with multiply callback
    math(a, b, multiply);

    return 0;
}

Explanation of the Callback Example

Callback Functions: Define two functions, add and multiply, that will be used as callbacks. Each function takes two integers as parameters and prints the result of their respective operations.

Math Function: Define a function math that takes two integers and a function pointer (callback) as parameters. This function calls the callback function with the provided integers.

Main Function: In the main function, call math with different callback functions (add and multiply) to demonstrate how different operations can be performed using the same math function.

The output of the program is:

2 + 3 = 5
2 x 3 = 6

Thanks for reading and happy coding!

以上是了解 C 中的記憶體管理、指標和函數指針的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
上一篇:理解指針下一篇:理解指針