Lesson ১১পড়তে ৭ মিনিট লাগবে

ডাইনামিক মেমোরি (Dynamic Memory)

স্ট্যাক (stack) হলো আপনার নিজের ছোট এবং স্বয়ংক্রিয় টেবিল বা ডেস্কের মতো। অন্যদিকে হিপ (heap) হলো একটি বিশাল বড় গুদামঘর বা ওয়্যারহাউস — যা বড় ঠিকই, কিন্তু এর সমস্ত কিছু আপনাকে নিজেকেই সামলাতে হবে

আপনার ডেস্ক বনাম ওয়্যারহাউস (Your Desk vs. The Warehouse)

যখন আপনি int x = 5;-এর মতো কোনো ভ্যারিয়েবল (variable) ডিক্লেয়ার (declare) করেন, তখন তা মূলত একটি স্ট্যাক বা stack-এ গিয়ে জমা হয় — একে আপনার নিজের কাজের ডেস্কের (desk) মতো কল্পনা করে নিতে পারেন। এটি বেশ দ্রুত বা ফাস্ট (fast), পরিপাটি বা টাইডি (tidy) এবং আপনার রুম থেকে বেরিয়ে যাওয়ার সাথে সাথেই (অর্থাৎ ফাংশনটি রিটার্ন বা return করার সাথে সাথেই) এটি স্বয়ংক্রিয়ভাবে বা অটোমেটিকভাবে পরিষ্কার (automatically cleaned up) হয়ে যায়। তবে এই ডেস্কটির সাইজ অনেকটাই ছোট হয়ে থাকে। আপনি চাইলেই এর মধ্যে এক মিলিয়ন বা দশ লক্ষ স্টিকি নোট (sticky notes) একসাথে রাখতে পারবেন না。

অন্যদিকে হিপ (heap) হলো ঠিক একটি বিশাল বড় গুদামঘর বা ওয়্যারহাউসের (warehouse) মতো। এতে প্রচুর জায়গা বা স্পেস (space) রয়েছে, তবে এর মধ্যে একটি ছোট শর্তও (catch) রয়েছে: আপনাকে নিজেকেই (you) এর তাক বা শেলফগুলো (shelves) রিজার্ভ (reserve) করতে হবে, আপনাকেই সেগুলোতে লেবেল (label) লাগাতে হবে এবং আপনার কাজ শেষ হওয়ার পর ওই জিনিসগুলোকে আপনাকেই আবার আগের জায়গায় ফিরিয়ে রাখতে হবে। কিন্তু যদি আপনি এটি পরিষ্কার বা ক্লিন আপ (clean up) করতে ভুলে যান? অভিনন্দন (Congratulations), আপনি এইমাত্র একটি মেমোরি লিক (memory leak) তৈরি করেছেন।

চারটি ফাংশন (The Four Functions)

এই বিশাল গুদামঘরটি (warehouse) সামলানোর জন্য সি (C) মূলত আপনাকে এই ৪টি টুল (tools) দিয়ে থাকে:

  • malloc — মেমোরির একটি নির্দিষ্ট পরিমাণ জায়গা বা চাঙ্ক (chunk) রিজার্ভ (reserve) করা (এটি আনইনিশিয়ালাইজড বা uninitialized অবস্থায় থাকে — যার ভেতরে কিছু আবর্জনা বা গারবেজ ভ্যালুও (garbage) থাকতে পারে)
  • calloc — মেমোরি রিজার্ভ বা সংরক্ষণ (reserve) করা এবং একই সাথে সেগুলোকে জিরো-ইনিশিয়ালাইজড (zero-initialize) করা বা এর প্রতিটি মানকে 0 করে দেওয়া
  • realloc — আগে থেকে অ্যালোকেট (allocated) করা বা বরাদ্দ করা কোনো চাঙ্ককে (chunk) নতুন করে রিসাইজ (resize) করা বা আকার পরিবর্তন করা
  • free — মেমোরিটিকে (memory) আবার সিস্টেমের (system) কাছে ফেরত দেওয়া (return)

এখানকার এই ৪টি ফাংশনই মূলত <stdlib.h>-এর ভেতরে থাকে।

একটি সিঙ্গেল ইন্টিজারের (Single Integer) জন্য মেমোরি অ্যালোকেট বা বরাদ্দ (Allocating) করা

#include <stdio.h>
#include <stdlib.h>
int main() {
// হিপের (heap) ওপর একটি int-এর জন্য জায়গা বা স্পেস অ্যালোকেট (Allocate) করা
int *p = (int *)malloc(sizeof(int));
if (p == NULL) {
printf("Memory allocation failed!\n");
return 1;
}
*p = 42;
printf("Value: %d\n", *p);
printf("Address: %p\n", (void *)p);
// আপনি যে মেমোরিটি মূলত malloc করেছেন, তা সবসময় ফ্রি (free) করে দেবেন!
free(p);
return 0;
}
Output
Value: 42
Address: 0x55a3b2c04260

malloc — কাঁচা বা র মেমোরি (malloc — Raw Memory)

এই malloc(size) মূলত size পরিমাণ বাইটকে (bytes) অ্যালোকেট (allocates) করে বা বরাদ্দ করে এবং এর প্রথম বাইটকে বা ফার্স্ট বাইটকে নির্দেশ করতে একটি পয়েন্টার (pointer) রিটার্ন (returns) করে। এখানকার এই মেমোরিগুলো মূলত অবশ্যই ইনিশিয়ালাইজড বা initialized নয় — অর্থাৎ এর ভেতরে আগের যেকোনো গারবেজ ভ্যালু বা আবর্জনাও (garbage) থাকতে পারে। ব্যাপারটি অনেকটা এমন কোনো স্টোরেজ লকার (storage locker) ভাড়া নেওয়ার মতো যেখানে এর আগের ভাড়াটেরা যাওয়ার আগে তাদের জিনিসপত্র পরিষ্কার বা ক্লিন আউট (cleaned out) করে দিয়ে যায়নি।

calloc — ক্লিন বা পরিষ্কার মেমোরি (calloc — Clean Memory)

calloc(count, size) মূলত size বাইট আকারের মোট count সংখ্যক আইটেমের জন্য জায়গা (space) বরাদ্দ করে এবং এর প্রতিটি বাইটকে (every byte) শুন্য বা জিরো (zero) হিসেবে সেট (sets) করে দেয়। এটি অনেকটা এমন একটি লকার (locker) ভাড়া নেওয়ার মতো যাকে একেবারেই নতুন করে পরিষ্কার বা ফ্রেশলি সোয়েপ্ট (freshly swept) করা হয়েছে। আপনার যখনই জিরো-ইনিশিয়ালাইজড (zero-initialized) কোনো অ্যারের (array) প্রয়োজন হবে তখনই আপনি মূলত এটিকে ব্যবহার করতে পারবেন।

malloc বনাম calloc-এর সাহায্যে ডাইনামিক অ্যারে (Dynamic Array with malloc vs calloc)

#include <stdio.h>
#include <stdlib.h>
int main() {
int n = 5;
// malloc: আনইনিশিয়ালাইজড বা uninitialized (গারবেজ ভ্যালু বা আবর্জনা!) (garbage values!)
int *arr1 = (int *)malloc(n * sizeof(int));
// calloc: জিরো-ইনিশিয়ালাইজড (zero-initialized)
int *arr2 = (int *)calloc(n, sizeof(int));
if (!arr1 || !arr2) {
printf("Allocation failed!\n");
return 1;
}
// এখানকার এই malloc অ্যারেটিকে (array) পূর্ণ বা ফিল (Fill) করুন
for (int i = 0; i < n; i++) {
arr1[i] = (i + 1) * 10;
}
printf("malloc array: ");
for (int i = 0; i < n; i++) printf("%d ", arr1[i]);
printf("\ncalloc array: ");
for (int i = 0; i < n; i++) printf("%d ", arr2[i]);
printf("\n");
free(arr1);
free(arr2);
return 0;
}
Output
malloc array: 10 20 30 40 50
calloc array: 0 0 0 0 0
Note: এখানকার প্রতিটি malloc-এর জন্যই মূলত একটি free প্রয়োজন পড়ে! যেকোনো সি (C) প্রোগ্রামে মূলত এই মেমোরি লিকই (Memory leaks) হলো সবচেয়ে বড় বা ১ নং বাগ (#1 bug)। যদি আপনি যেকোনো মেমোরি (memory) অ্যালোকেট (allocate) করার পর এর পয়েন্টারটি (pointer) হারিয়ে ফেলেন, তবে প্রোগ্রামটি বা program শেষ না হওয়া পর্যন্ত এখানকার ওই মেমোরিটিও আর কোথাও খুঁজে পাওয়া যাবে না। তাই এই malloc মূলত এর বদলে কোনো NULL রিটার্ন (returns) করছে কি না তা সবসময় ভালোভাবে চেক (check) করে নেবেন — যার মানে হলো সিস্টেমটির (system) মেমোরি ফুরিয়ে গেছে বা শেষ হয়ে গেছে।

realloc — বড় এবং ছোট করা (realloc — Growing and Shrinking)

যদি আপনি ৫টি আইটেমের (items) জন্য জায়গা (space) বরাদ্দ বা অ্যালোকেট (allocated) করার পর হঠাৎ করে দেখেন যে আপনার এবার ১০টির প্রয়োজন, তবে আপনি তখন কী করবেন? আর এখানেই মূলত ওই realloc-এর কাজ শুরু হয়। এটি মূলত আপনার আগে থেকে অ্যালোকেট বা বরাদ্দকৃত মেমোরি চাঙ্কটিকে (allocation) নিয়ে সেটির আকার পরিবর্তন বা রিসাইজ (resizes) করে দেয় — তবে যদি এখানকার বর্তমান ব্লকটি (current block) আর কোনোভাবেই তার নিজের জায়গাতেই আর বড় হতে না পারে (grow in-place), সেক্ষেত্রে এটি ওই ডেটাগুলোকে বা data অন্য কোনো নতুন লোকেশন বা জায়গায় (new location) সরিয়ে নিয়ে যেতে পারে।

ব্যাপারটিকে গুদামের ম্যানেজারের (warehouse manager) কাছে গিয়ে বলার মতো কল্পনা করে নিতে পারেন: "আমার আরো একটি বড় তাক (bigger shelf) দরকার।" তখন তারা হয়তো আপনার বর্তমান তাকটিকেই (current shelf) আরেকটু বড় করে (extend) দিতে পারে, অথবা হয়তো আপনার জিনিসগুলোকে রুমের (room) অন্য প্রান্তের একটি বড় তাকের (bigger one) মধ্যে রেখে এসে আপনাকে এর নতুন ঠিকানাটি (new address) ধরিয়ে দিতে পারে।

realloc ব্যবহার করে একটি অ্যারেকে বড় করা (Growing an Array with realloc)

#include <stdio.h>
#include <stdlib.h>
int main() {
int capacity = 3;
int *arr = (int *)malloc(capacity * sizeof(int));
if (!arr) return 1;
// এর প্রাথমিক বা ইনিশিয়াল (initial) অ্যারেটিকে ফিল (Fill) বা পূরণ করুন
arr[0] = 10; arr[1] = 20; arr[2] = 30;
printf("Before realloc: ");
for (int i = 0; i < capacity; i++) printf("%d ", arr[i]);
printf("(capacity: %d)\n", capacity);
// এর ক্যাপাসিটি (capacity) বা ধারণক্ষমতা দ্বিগুণ (Double) করুন
capacity *= 2;
int *temp = (int *)realloc(arr, capacity * sizeof(int));
if (!temp) {
free(arr); // এখানকার realloc-টি ফেইল (failed) করেছে বা ব্যর্থ হয়েছে; তবে এর অরিজিনাল বা আসল অ্যারেটি এখনো ভালো (valid) আছে
return 1;
}
arr = temp;
// নতুন স্লটগুলো (slots) পূর্ণ করুন
arr[3] = 40; arr[4] = 50; arr[5] = 60;
printf("After realloc: ");
for (int i = 0; i < capacity; i++) printf("%d ", arr[i]);
printf("(capacity: %d)\n", capacity);
free(arr);
return 0;
}
Output
Before realloc: 10 20 30 (capacity: 3)
After realloc:  10 20 30 40 50 60 (capacity: 6)

ড্যাংলিং পয়েন্টার (Dangling Pointers)

একবার free(p) কল (call) করার পর, এখানকার ওই মেমোরিটি বা memory আগের মতো আবার সিস্টেমের (system) কাছে ফিরে যায় — কিন্তু p এর কাছে তখনও ওই পুরোনো অ্যাড্রেসটি বা ঠিকানাটিই (old address) থেকে যায়। অর্থাৎ মেমোরি ফ্রি করার পরও ওই p কে ব্যবহার করাটা মূলত অনেকটা আপনার পুরনো অ্যাপার্টমেন্ট বা বাসা থেকে (old apartment) চলে যাওয়ার পরও আবার সেখানে ফিরে গিয়ে ওই সোফায় বসার চেষ্টা করার মতোই একটি ব্যাপার। এমনকি ওই সোফাটি তখনো হয়তো সেখানেই থাকতে পারে... অথবা ততদিনে হয়তো নতুন অন্য কেউ সেখানে থাকতে শুরু করতে পারে। আর এটিকে মূলত একটি ড্যাংলিং পয়েন্টার (dangling pointer) বলা হয় — যা এমন কোনো একটি মেমোরিকে (memory) পয়েন্ট (points) করে বা নির্দেশ করে যা আসলে আর আপনার নয়।

সেরা অভ্যাস বা বেস্ট প্র্যাকটিস (Best practice): যেকোনো মেমোরি ফ্রি (freeing) করার পর আপনার পয়েন্টারকে (pointer) NULL হিসেবে সেট করে দেওয়া।

প্রপার বা সঠিক ক্লিনআপ প্যাটার্ন (Proper Cleanup Pattern)

#include <stdio.h>
#include <stdlib.h>
int main() {
int *data = (int *)malloc(100 * sizeof(int));
if (!data) {
printf("Allocation failed\n");
return 1;
}
// ... ডেটা বা data ব্যবহার করুন ...
data[0] = 999;
printf("data[0] = %d\n", data[0]);
// প্রপার বা সঠিক ক্লিনআপ (cleanup)
free(data);
data = NULL; // যেকোনো ড্যাংলিং পয়েন্টারকে (dangling pointer) আটকে দেয় (Prevent)
// সেইফ বা নিরাপদ (Safe): ব্যবহার করার আগেই চেক (checking) করে নেওয়া
if (data != NULL) {
printf("data[0] = %d\n", data[0]);
} else {
printf("data has been freed\n");
}
return 0;
}
Output
data[0] = 999
data has been freed

স্ট্যাক (Stack) বনাম হিপ (Heap) — কখন কোনটি ব্যবহার করবেন (Stack vs Heap — When to Use Which)

স্ট্যাক বা stack (সাধারণ ভ্যারিয়েবলগুলো বা normal variables) ব্যবহার করবেন মূলত যখন:

  • আপনি এর কম্পাইলের (compile time) সময়ই এর আকার বা সাইজ (size) জানবেন
  • আপনার ডেটা বা data অনেক ছোট (small) থাকবে (যেমন কয়েকটি int, বা একটি অনেক ছোট অ্যারে বা small array)
  • আপনার এই ডেটাটির (data) আয়ু শুধুমাত্র একটি ফাংশনের (function) ভেতরেই সীমাবদ্ধ থাকবে

হিপ বা heap (malloc/calloc) ব্যবহার করবেন মূলত যখন:

  • রানটাইম (runtime) পর্যন্ত আপনি এর আকার বা সাইজটি (size) সম্পর্কে কিছুই জানবেন না (যেটি মূলত ইউজার ইনপুট বা user input-এর ওপর ডিসাইড বা decide করবে)
  • ডেটাটি (data) অনেক বেশি বড় (large) হবে (যেমন হাজার হাজার এলিমেন্ট বা elements)
  • ডেটাটি মূলত যে ফাংশনটি (function) তৈরি (created) করেছে, তার চেয়েও বেশি সময় ধরে ব্যবহার করতে হবে বা বাঁচিয়ে (outlive) রাখতে হবে
Note: কখনোই একটি পয়েন্টারের (pointer) ভেতরের জিনিসগুলোকে realloc করার জন্য arr = realloc(arr, newSize) ব্যবহার করবেন না। যদি কোনো কারণে আপনার এই realloc ফেইল (fails) করে বা ব্যর্থ হয়, তবে এটি সাথে সাথে NULL রিটার্ন (returns) করবে এবং এর ফলে আপনার ওই আসল (original) মেমোরির মধ্যকার একমাত্র রেফারেন্সটিও (reference) হারাবে — যার ফলে সাথে সাথেই একটি ইনট্যান্ট বা তাৎক্ষণিক মেমোরি লিক (instant memory leak) তৈরি হবে! তাই এর স্থানে সবসময় একটি টেম্প বা temp (টেম্পোরারি বা temporary) পয়েন্টার ব্যবহার করুন।
চ্যালেঞ্জ

ছোট কুইজ

malloc এবং calloc এর মধ্যকার প্রধান বা মূল পার্থক্যটি (key difference) কী?
Pointer Arithmetic & ArraysStructs & Typedef