首页 > 后端开发 > C++ > 正文

了解 C 中的内存管理、指针和函数指针

王林
发布: 2024-07-17 00:10:11
原创
1072 人浏览过

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中文网其他相关文章!

来源:dev.to
本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责声明 Sitemap
PHP中文网:公益在线PHP培训,帮助PHP学习者快速成长!