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* 이름으로 선언될 수 있습니다. 또는 int *이름; 두 스타일 모두 정확하며 상호 교환 가능합니다.
int num = 5; 메인 함수를 포함하는 함수 내부에서는 해당 변수가 스택에 저장되고, 함수 실행이 완료되면 변수가 제거됩니다.
하지만 이제 힙에 메모리를 동적으로 할당하겠습니다. 그런 다음 필요한 메모리 양을 완전히 제어할 수 있으며 할당을 해제할 때까지 지속됩니다.
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->[],[],[],[] [],[],[],[] [],[],[],[] [],[],[],[]
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.
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 }
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.
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; }
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; }
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; }
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.
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)
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; }
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; }
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 중국어 웹사이트의 기타 관련 기사를 참조하세요!