ホームページ >バックエンド開発 >C++ >C のメモリ管理、ポインタ、関数ポインタを理解する

C のメモリ管理、ポインタ、関数ポインタを理解する

王林
王林オリジナル
2024-07-17 00:10:111269ブラウズ

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 *name;どちらのスタイルも正しく、交換可能です。

動的メモリの割り当てと割り当て解除。

int num = 5; のように変数を宣言した場合main 関数を含む関数内では、その変数はスタックに格納され、関数の実行が完了すると変数は削除されます。

しかし、今度はヒープ内にメモリを動的に割り当てます。そうすれば、必要なメモリの量を完全に制御できるようになり、割り当てを解除するまで保持されます。

マロックとカロック。

関数 malloc または calloc を使用してメモリを割り当てることができます。

Malloc は、割り当てるメモリのサイズをバイト単位で表すパラメータを 1 つ受け取ります。

Calloc は、項目の量と各項目が占めるメモリ量 (バイト単位) の 2 つのパラメータを取ります。

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 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。