- گروه شماره ۲۵
- معین آعلی - ۴۰۱۱۰۵۵۶۱
- ثمین اکبری - ۴۰۱۱۰۵۵۹۴
به کمک malloc حافظه ی مورد نیاز برای یک instance از این ساختار را تخصیص میدهیم.
برای فیلدهای instance ایجاد شده مقادیری را اختصاص میدهیم و آنها را چاپ میکنیم.
در نهایت
به کمک free حافظهی گرفته شده را آزاد میکنیم:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct MyStruct {
int a;
int b;
char name[20];
};
int main() {
struct MyStruct* ptr = (struct MyStruct*) malloc(sizeof(struct MyStruct));
if (ptr == NULL) {
printf("Memory allocation failed!\n");
return 1;
}
printf("%p\n", ptr);
ptr->a = 10;
ptr->b = 20;
strcpy(ptr->name, "Moeein");
printf("a: %d\n", ptr->a);
printf("b: %d\n", ptr->b);
printf("name: %s\n", ptr->name);
free(ptr);
return 0;
}خروجی:
خروجی malloc
در واقع آدرس آن
instance
در حافظه است.
حال به کمک دستور
ps -o user,vsz,rss,pmem,fname -e
میخواهیم وضعیت حافظهی پردازهها را بررسی کنیم:
ستونهای مشخص شده:
- ستون
user: کاربری که سازنده این پردازه است. - ستون
vsz: اندازهی حافظه مجازی پردازه. - ستون
rss: اندازهی حافظه فیزیکی پردازه که روی رم قرار دارد. - ستون
pmem: نشان میدهد کهrssچند درصد از حافظه کل سیستم است. - ستون
fname: نام پردازه را نشان میدهد.
حافظهی یک پردازه در معماریهای امروزی به اجزای زیر تقسیم بندی میشود:
-
بخش
textکه کد زبان ماشین پردازه را نگه داری میکند. -
بخش
dataکه متغیرهای سراسری پردازه در آن قرار می گیرد، برخی از متغیرها دارای مقدار اولیه هستند و برخی خیر. دسته ی دوم در بخشی به نامbssواقع میشوند. -
بخش
heapکه شامل حافظههایی است که به صورت پویا اختصاص داده میشوند. -
بخش
stackکه متغیرهای محلی، پارامترهای توابع و مقادیر بازگشتی آن ها در آن قرار دارند.
ابتدا به کمک دستور زیر،محل قرارگیری دستور
ls
را در فایلسیستم پیدا میکنیم:
حال به کمک دستور
size
بررسی میکنیم که کدهای آن به چه بخشهایی اختصاص دارند:
واحد اعداد فوق بایت هستند.
به کمک دستور ldd کتابخانههای مشترکی که توسط دستور ls استفاده شده است را مشاهده میکنیم:
همچنین خروجی برای برنامهی nano
:
کد دادهشده در انتهای دستور
man etext
:
#include <stdio.h>
#include <stdlib.h>
extern char etext, edata, end;
int main(void){
printf("First address past:\n");
printf(" program text (etext) %10p\n", &etext);
printf(" initialized data (edata) %10p\n", &edata);
printf(" uninitialized data (end) %10p\n", &end);
exit(EXIT_SUCCESS);
};نتیجه اجرا:
واضحا مقدار end بزرگتتر از edata و آن هم بزرگتر از etext است.
پس ساختار بالا صحیح است.
نمادهای etext، edata و end متغیر معمولی نیستند، بلکه برچسبهایی (symbols) هستند که لینکر هنگام ساخت برنامه ایجاد میکند و به آدرس پایان بخشهای مختلف حافظه برنامه اشاره دارند (etext پایان بخش کد، edata پایان بخش دادههای مقداردهیشده و end پایان دادههای مقداردهینشده یا BSS). چون این نمادها توسط لینکر تعریف میشوند و نه در فایل سورس، در کد با extern اعلام میشوند تا کامپایلر بداند نوعشان چیست و هشدار ندهد. معمولاً نوع char برای آنها انتخاب میشود تا بتوان با گرفتن آدرسشان و انجام عملیات روی پوینتر به راحتی اندازهی بخشهای حافظه را محاسبه کرد.
حال میخواهیم با استفاده از سیستمکال
sbrk
ادرس انتهای هیپ را مشاهده کنیم. سپس با
malloc
مقداری حافظه اختصاص میدهیم و تغییرات انتهای هیپ را چاپ میکنیم:
#include <iostream>
#include <unistd.h>
#include <cstdlib>
int main() {
int* k = (int*)malloc(1024);
int* ptr = (int*)sbrk(0);
int diff = ptr - k;
printf("start of program : %p\nafter malloc : %p\ndifference : %d\n", k, ptr, diff);
free(k);
return 0;
}نتیجهی اجرا:
سپس تابع بازگشتیای مینویسیم که خودش را تا عمق ۱۰۰ صدا بزند و هر بار یک متغیر لوکال داخل خودش بسازد و آدرس آن را چاپ کند.
#include <stdio.h>
void recursive(int depth, int max_depth) {
int i;
printf("depth: %d, address: %p\n", depth, (void*)&i);
if (depth < max_depth) {
recursive_func(depth + 1, max_depth);
}
}
int main(void) {
recursive(1, 100);
return 0;
}نتیجه:
از آنجا که stack در بسیاری از معماریها (مثل x86/x86_64) به سمت آدرسهای پایینتر رشد میکند، آدرس متغیر i در هر بار فراخوانی کمتر از دفعه قبلی خواهد شد.








