Heim > Artikel > Backend-Entwicklung > Speicherverwaltung, Zeiger und Funktionszeiger in C verstehen
Bei der C-Programmierung sind eine effiziente Speicherverwaltung und die Verwendung von Zeigern entscheidend für die Erstellung robuster und leistungsstarker Anwendungen. Dieses Handbuch bietet einen umfassenden Überblick über verschiedene Speichertypen, Zeiger, Referenzen, dynamische Speicherzuweisung und Funktionszeiger sowie Beispiele, die Ihnen bei der Beherrschung dieser Konzepte helfen. Egal, ob Sie C-Neuling sind oder Ihr Verständnis vertiefen möchten, dieser Leitfaden behandelt wichtige Themen zur Verbesserung Ihrer Programmierkenntnisse.
Es gibt 5 verschiedene Arten von Speicher, die Ihnen beim Programmieren in C begegnen werden.
1. Textsegment
Ihr kompilierter Code wird hier gespeichert. Die Maschinencodeanweisungen für Ihr Programm. Das Textsegment ist schreibgeschützt, sodass Sie keine Daten ändern, aber darauf zugreifen können. Weiter unten werden wir über Funktionszeiger sprechen. Diese Zeiger verweisen auf eine Funktion in diesem Segment.
2. Initialisiertes Datensegment
Globale und statische Variablen werden hier mit spezifischen Werten gespeichert, bevor das Programm ausgeführt wird, und bleiben während des gesamten Programms zugänglich.
Der Unterschied zwischen statischen und globalen Variablen besteht im Gültigkeitsbereich. Auf statische Variablen kann in der Funktion oder im Block zugegriffen werden. Sie sind definiert, auf globale Variablen kann jedoch von überall in Ihrem Programm aus zugegriffen werden.
Normale Variablen werden entfernt, nachdem die Ausführung einer Funktion abgeschlossen ist, während statische Variablen bestehen bleiben.
3. Unitialisiertes Datensegment (BSS)
Im Grunde dasselbe wie das initialisierte Datensegment, besteht jedoch aus Variablen, denen kein spezifischer Wert zugewiesen ist. Sie haben standardmäßig den Wert 0.
4. Haufen
Hier haben Sie dynamischen Speicher, den Sie als Programmierer zur Laufzeit verwalten können. Sie können Speicher zuweisen und Speicher mit Funktionen wie malloc, calloc, realloc und free freigeben.
5. Stapel
Sie sind wahrscheinlich einigermaßen mit dem Stapel vertraut. Der Stapelspeicher verwaltet Funktionsausführungen, indem er lokale Variablen, Argumente und Rückgabewerte speichert. Der Speicher im Stapel wird entfernt, nachdem die Ausführung der Funktion abgeschlossen ist.
In C gibt es verschiedene Datentypen und die häufigsten sind int, float, double und char. Ich werde nicht viel über Datentypen sprechen, aber das Wichtigste ist zu wissen, wie viele Bytes ein bestimmter Datentyp im Speicher hat. Hier ist eine Liste, denken Sie daran.
Int: 4 Bytes,
Float: 4 Bytes,
Doppelt: 8 Bytes,
Zeichen: 1 Byte.
Sie können die Größe eines Datentyps mit der Methode sizeof() überprüfen.
Wenn Sie eine Variable wie;
zuweisen
int number = 5;
Das System speichert den Wert 5 im Speicher. Aber wo in der Erinnerung?
Im Speicher befinden sich tatsächlich Adressen, und so können Sie den Überblick über die von Ihnen gespeicherten Werte behalten.
Eine Referenz ist die Adresse einer Variablen. Ziemlich cool, oder?
Um auf die Referenz einer Variablen zuzugreifen, verwenden Sie & gefolgt vom Variablennamen.
Um den Verweis auf die Konsole auszudrucken, verwenden wir den p-Formatbezeichner.
int number = 5; printf(“%d”, number); // prints out 5 printf(“%p”, &number); // prints out the ref: 0x7ffd8379a74c
Wahrscheinlich bekommen Sie eine andere Adresse ausgedruckt.
Um nun den Überblick über diese Referenz zu behalten, verwenden Sie einen Zeiger, um die Referenz zu speichern.
Erstellen Sie eine Zeigervariable mit *.
int number; int* pointer = &number; printf(“%p”, pointer); // 0x7ffd8379a74c
Um den Wert von einem Zeiger zu erhalten, können Sie eine Dereferenzierung verwenden. Um einen Wert zu dereferenzieren, verwenden Sie * vor dem Zeiger. Daher wird * sowohl zum Erstellen eines Zeigers als auch zum Dereferenzieren verwendet, jedoch in unterschiedlichen Kontexten.
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
Das ist leistungsstark, da Sie mit Zeigern Werte als Referenz übergeben und keine Werte kopieren können. Dies ist speichereffizient und leistungsstark.
Wenn Sie Werte als Argumente in einer Hochsprache an eine Funktion übergeben, kopieren Sie die Werte, aber in C können Sie eine Referenz senden und den Wert direkt im Speicher bearbeiten.
#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; }
Ein Zeiger kann als int* name deklariert werden; oder int *name; beide Stile sind korrekt und austauschbar.
Wenn Sie eine Variable wie int num = 5 deklarieren; Innerhalb einer Funktion, einschließlich der Hauptfunktion, wird diese Variable im Stapel gespeichert und wenn die Funktion mit der Ausführung fertig ist, wird die Variable entfernt.
Aber jetzt werden wir Speicher dynamisch im Heap zuweisen. Dann haben wir die volle Kontrolle darüber, wie viel Speicher wir benötigen, und dieser bleibt bestehen, bis wir ihn freigeben.
Wir können die Funktionen malloc oder calloc verwenden, um Speicher zuzuweisen.
Malloc benötigt einen Parameter, der die Größe des zuzuweisenden Speichers in Bytes darstellt.
Calloc benötigt zwei Parameter: die Anzahl der Elemente und wie viel Speicher in Bytes jedes Element belegt.
malloc(size)
calloc(Menge, Größe)
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!
Das obige ist der detaillierte Inhalt vonSpeicherverwaltung, Zeiger und Funktionszeiger in C verstehen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!