מבוא לתכנות ולמדעי המחשב בשפת C/ניהול זיכרון, מצביעים ומבנים
מצביעים וניהול זיכרון
עריכההשיעור נמצא במסמך pdf זה
דוגמה: מערך של מערכים
עריכהבדוגמה המופיעה במסמך חסר קוד לשיחרור זיכרון. הנה הקוד המלא:
#include <stdio.h>
#include <stdlib.h>
const int LENGTH = 5;
int** makeArray() {
int **arr2d = (int**)malloc(sizeof(int*)*LENGTH),i,j;
for(i=0; i<LENGTH; i++) {
arr2d[i] = (int*)malloc(sizeof(int)*(i+1));
for(j=0; j<=i; j++)
arr2d[i][j] = i*j; /* same as: *(*(arr2d+i)+j) = i*j */
}
return arr2d;
}
int main() {
int **ar = makeArray(),i,j;
for(i=0; i<LENGTH; i++) {
for(j=0; j<=i; j++)
printf("%d ",*(*(ar+i)+j)); /* same as: printf("%d ",ar[i][j]); */
printf("\n");
}
for(i=0; i<LENGTH; ++i)
free(ar[i]);
free(ar);
return 0;
}
שימו לב לסדר שיחרור הזיכרון: ראשית המערכים הפנימיים ורק בסוף המערך הראשי שמצביע עליהם. אם היינו משחררים בהתחלה את המערך הראשי, לא היתה לנו אפשרות להתייחס למערכים הפנימיים והם היו נשארים כמובלעות בזיכרון בלי יכולת לשחרר אותם. זכרון שהוקצא בלי יכולת להתייחס אליו נקרא zombie (מת-חי). ותוכנית שמייצרת זומבים נקראת תוכנית עם דליפות זיכרון.
struct - מבנה
עריכההשיעור שבמסמך מכיל תוכן הקשור למבנים, struct-ים ב c.
הנה הסבר מפורט יותר על מבנים ואיך משתמשים בהם.
ב - C ניתן להגדיר טיפוס המורכב ממספר טיפוסים אחרים. טיפוס כזה נקרא struct או מבנה. לדוגמה:
#include <stdio.h>
struct AA {
int a;
char c;
};
int main() {
struct AA b;
b.a = 14;
b.c = 'F';
printf("%d \n",b.a);
printf("%c \n",b.c);
return 0;
}
פלט:
14 F
הטיפוס struct AA מורכב מ int ו char. כל משתנה מסוג struct AA, מכיל גם מספר וגם תו. בדוגמה זו, הגדרנו משתנה מטיפוס זה בשם b ולכן b מכיל מספר ותו. בכדי לפנות לחלק של המספר, כתבנו b.a ובכדי לפנות לחלק של התו, כתבנו b.c. מכנים את a ו c השדות של ה struct ולעיתים גם כ members שלו.
שימו לב שהטיפוס הוא struct AA ולא רק AA.
האיור הבא מראה איך נראה המשתנה b בזיכרון, כאשר הוא מוקצה על המחסנית:
מערך של מבנים
עריכהמכיוון שמבנה הוא טיפוס לכל דבר, ניתן להגדיר גם מערכים של מבנים. לדוגמה:
#include <stdio.h>
struct AA {
int number;
char character;
};
int main() {
struct AA arr[10];
int i;
for(i=0; i<10; ++i) {
arr[i].number = i;
arr[i].character = (char) ( (int)'A'+ i);
}
for(i=0; i<10; ++i)
printf("arr[%d] = <%d,%c> \n", i, arr[i].number, arr[i].character);
return 0;
}
פלט:
arr[0] = <0,A> arr[1] = <1,B> arr[2] = <2,C> arr[3] = <3,D> arr[4] = <4,E> arr[5] = <5,F> arr[6] = <6,G> arr[7] = <7,H> arr[8] = <8,I> arr[9] = <9,J>
בדוגמה זו, שינינו את שמות השדות ל number ו character, הגדרנו מערך של struct AA, מילאנו את כל ערכיו ואז הדפסנו את תכולתו.
הנה דוגמה נוספת לשימוש במערך של מבנים. בדוגמה זו אנו שומרים נתונים של אנשים שונים:
#include <stdio.h>
#include <stdlib.h>
struct Person {
int id;
char *name;
};
int main() {
char* names[] = {"Asif","Agam","Tzion","Yasmin","Ela","Maya","Yael"};
int N = sizeof(names)/sizeof(char*);
struct Person* persons = (struct Person*) malloc (N*sizeof(struct Person));
int i;
for(i=0; i<N; ++i) {
persons[i].id = 1000+i;
persons[i].name = names[i];
}
for(i=0; i<N; ++i)
printf("%s %d\n",persons[i].name, persons[i].id);
free(persons);
return 0;
}
פלט:
Asif 1000 Agam 1001 Tzion 1002 Yasmin 1003 Ela 1004 Maya 1005 Yael 1006
כמובן, בדוגמה מציאותית, יהיו בכל מבנה שדות רבים נוספים.
העברה של מבנה כפרמטר לפונקציה
עריכהכמו כל משתנה, גם כאשר מעבירים מבנה כפרמטר לפונקציה, נוצר העתק שלו. לדוגמה:
#include <stdio.h>
struct AA {
int number;
char character;
};
void foo(struct AA param) {
printf("in foo: %d %c \n",param.number, param.character);
param.number = 17;
param.character = 'B';
printf("in foo: %d %c \n",param.number, param.character);
}
int main() {
struct AA myStruct;
myStruct.number = 1;
myStruct.character = 'A';
printf("in main: %d %c \n",myStruct.number, myStruct.character);
foo(myStruct);
printf("in main: %d %c \n",myStruct.number, myStruct.character);
return 0;
}
פלט:
in main: 1 A in foo: 1 A in foo: 17 B in main: 1 A
כפי שאפשר לראות מהדוגמה, הפרמטר הוא העתק בלתי תלוי במבנה המקורי. שינוי של ההעתק לא גורם לשינוי של המבנה המקורי.
מצביע למבנה
עריכהמכיוון שמבנה הוא טיפוס לכל דבר, ניתן להגדיר עבורו מצביע. דרך המצביע אפשר להתייחס למבנה ולשדות שלו. לדוגמה:
#include <stdio.h>
struct AA {
int number;
char character;
};
int main() {
struct AA s;
struct AA *p;
p = &s;
(*p).number = 17;
printf("s.number: %d \n",s.number);
// *p.number = 12; // will not compile because interpreted as:
// *(p.number) = 12
p->number = 78; // same as: (*p).number = 78
printf("s.number: %d \n",s.number);
return 0;
}
פלט:
s.number: 17 s.number: 78
כפי שניתן לראות, אפשר לגשת אל שדות המבנה בכתיב מפורש, באופן הבא:
(*p).number
המשתנה המוצבע ע"י p הוא מבנה ו number הוא אחד השדות של אותו מבנה. הסוגריים בכתיב זה הם הכרחיים מכיוון שאחרת הקומפיילר יבין את הביטוי באופן הבא:
*(p.number) = 17
כלומר, כאילו p הוא מבנה בעל שדה בשם number, השדה number הוא מצביע ואנו מעוניינים במה ש number מצביע עליו. הקומפיילר ידווח על שגיאה כי p איננו מבנה.
מכיוון שהכתיב המופרש, עם הסוגריים, קצת מסורבל, C מציעה כתיב מקוצר, שקול:
p->number
זאת הדרך המקובלת לפנות לשדה של מבנה מתוך מצביע למבנה.
מבנים בתוך מבנים
עריכההשדות של מבנה יכולים להיות בעלי כל טיפוס שהמערכת מכירה, בפרט טיפוס של מבנה שכבר הוגדר קודם. לדוגמה:
#include <stdio.h>
#include <stdlib.h>
struct Address {
char *street;
int number;
char *town;
int zipCode;
char *state;
};
void printAddress(struct Address ad) {
printf("%s %d, %s. Zip: %d, %s\n",ad.street,
ad.number,ad.town,ad.zipCode, ad.state);
}
struct Person {
int id;
char *name;
struct Address address;
};
void printPerson(struct Person p) {
printf("%s, id:%d\n",p.name, p.id);
printAddress(p.address);
}
int main() {
struct Address ad;
ad.street = "Hasivim";
ad.number = 8;
ad.town = "Petah Tikva";
ad.zipCode = 3156;
ad.state = "Israel";
struct Person per;
per.name = "Yakir Yakirov";
per.id = 1234567;
per.address = ad;
printPerson(per);
return 0;
}
פלט:
Yakir Yakirov, id:1234567 Hasivim 8, Petah Tikva. Zip: 3156, Israel
typedef
עריכהtypedef היא פקודה ב C המאפשרת לתת שם נרדף לטיפוס קיים. לדוגמה:
#include <stdio.h>
typedef int MyInt;
int main() {
MyInt a;
a = 5;
printf("%d\n",a);
return 0;
}
בדוגמה זו, MyInt הוא שם נוסף לטיפוס int. המשתנה a בתוכנית הוא למעשה משתנה מסוג int.
התחביר של הפקודה הוא:
typedef <שם חדש לאותו הטיפוס> <טיפוס קיים>
הפקודה מאפשרת לתת שמות קצרים לטיפוסים מסובכים. לדוגמה:
typedef int** AOP; // Array of Pointers
int main() {
AOP arr = (AOP) malloc (sizeof(int*) *100);
// ..
אנו נשתמש ב typdef על מנת לתת שם מקוצר לטיפוסים של מבנים. לדוגמה:
struct Person {
int id;
char *name;
};
typedef struct Person Person;
int main() {
Person per;
per.name = "Avi";
Person* p = &per;
//...
המטרה פשוט להשתמש בשם Person עבור הטיפוס struct Person.
קיימת גם כתיבה מקוצרת לאותה מטרה:
typedef struct Person {
int id;
char *name;
} Person;
int main() {
Person per;
per.name = "Avi";
Person* p = &per;
//...
אנו נשתמש בכתיב זה על מנת להמנע מכתיבת המילה struct שוב ושוב.