C++: تخصيص الذاكرة الديناميكي
آخر تحديث: 2026-08-26
في الدروس السابقة، كانت المتغيرات والمصفوفات تُخصص على المكدس — ثابتة الحجم، تُحرر تلقائيًا عند انتهاء الدالة.
لكن إذا كنت تحتاج تخزين كمية غير محددة من البيانات (مثلًا، المستخدم يدخل 1000 عدد صحيح، لكنك لا تعرف وقت الترجمة)، فأنت تحتاج تخصيص الذاكرة الديناميكي — تخصيص ذاكرة على الكومة.
1. المكدس مقابل الكومة
(1) 1.1 منطقتا الذاكرة
| المقارنة | المكدس | الكومة |
|---|---|---|
| وقت التخصيص | يُحدد وقت الترجمة | يُحدد وقت التشغيل |
| الحجم | ثابت (يُحدد وقت الترجمة) | متغير (يُطلب وقت التشغيل) |
| وقت التحرير | تلقائي (عند انتهاء الدالة) | يدوي (باستخدام delete) |
| الكفاءة | عالية | منخفضة |
| حالات الاستخدام | المتغيرات المحلية، المصفوفات | البيانات ذات الحجم الديناميكي |
(2) 1.2 تشبيه من الحياة
| تشبيه من الحياة | المقابل البرمجي |
|---|---|
| مطعم الوجبات السريعة (قائمة ثابتة، الطعام جاهز فور الطلب) | المكدس |
| بوفيه مفتوح (خذ بقدر ما تريد) | الكومة |
2. new و delete
(1) 2.1 تخصيص الذاكرة بـ new
الصياغة:
▶ مثال 2: إدارة الذاكرة الديناميكية (صعوبة ⭐)
Type* pointerName = new Type;
الخرج:
(Program output)
مثال:
#include <iostream>
int main() {
// Allocate an int on the heap
int* p = new int; // p Pointing to int on the heap
*p = 5; // Assign value to int on the heap
std::cout << "*p = " << *p << std::endl; // 5
return 0;
}
💡 نقطة مهمة: new تُرجع مؤشرًا — يشير إلى الذاكرة المخصصة على الكومة.
(2) 2.2 تحرير الذاكرة بـ delete
الصياغة:
delete pointerName;
مثال:
#include <iostream>
int main() {
int* p = new int;
*p = 5;
std::cout << "*p = " << *p << std::endl;
delete p; // ✅ Free heap memory
p = nullptr; // ✅ Good practice:Set to nullptr after freeing
return 0;
}
💡 القاعدة الذهبية: new و delete يجب أن يأتيا معًا دائمًا — كل new يحتاج delete.
(3) 2.3 نسيان delete يسبب تسرب الذاكرة
مثال خطأ:
#include <iostream>
void foo() {
int* p = new int;
*p = 5;
// ❌ Forgot to write delete p;
} // Function ends, p is destroyed, but int on heap is not freed (memory leak)
int main() {
for (int i = 0; i < 1000000; i++) {
foo(); // Each call leaks 4 bytes
}
// Program memory usage keeps growing, may eventually crash
return 0;
}
💡 تسرب الذاكرة: ذاكرة مخصصة على الكومة لم تُحرر أبدًا ولم يعد يمكن الوصول إليها (لأن المؤشر إليها دُمر).
3. المصفوفات الديناميكية
(1) 3.1 تخصيص مصفوفات بـ new
الصياغة:
Type* pointerName = new Type[size];
مثال:
#include <iostream>
int main() {
int n;
std::cout << "Please enter the array size: ";
std::cin >> n;
// Allocate an array of n ints on the heap
int* arr = new int[n];
// Using the array
for (int i = 0; i < n; i++) {
arr[i] = i + 1;
}
// Output the array
for (int i = 0; i < n; i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
// Free array(Note:Use delete[],not delete)
delete[] arr;
return 0;
}
💡 نقطة مهمة: لتحرير مصفوفة، استخدم delete[] (بأقواس مربعة)، وليس delete!
(2) 3.2 new و delete يجب أن يتطابقا
| التخصيص | التحرير |
|---|---|
new int |
delete p; |
new int[10] |
delete[] p; |
مثال خطأ:
int* p = new int[10];
delete p; // ❌ Error: Should use delete[] p;
4. المؤشرات المعلقة
(1) 4.1 ما هو المؤشر المعلق؟
المؤشر المعلق هو مؤشر يشير إلى ذاكرة مُحررة بالفعل.
#include <iostream>
int main() {
int* p = new int(5);
delete p; // Free memory
// ❌ Dangerous: p is now a dangling pointer
std::cout << *p << std::endl; // Undefined behavior! May output garbage or crash
return 0;
}
💡 الحل: اضبط المؤشر على nullptr فورًا بعد التحرير:
delete p;
p = nullptr; // ✅ Good practice
5. تطبيق: فرز مصفوفة ديناميكية
▶ مثال 1: فرز الاختيار مع مصفوفة ديناميكية (صعوبة ⭐⭐)
#include <iostream>
// Selection Sort
void selectionSort(int* arr, int n) {
for (int i = 0; i < n - 1; i++) {
int minIdx = i;
for (int j = i + 1; j < n; j++) {
if (arr[j] < arr[minIdx]) {
minIdx = j;
}
}
// Swap
int temp = arr[i];
arr[i] = arr[minIdx];
arr[minIdx] = temp;
}
}
int main() {
int n;
std::cout << "Please enter the array size: ";
std::cin >> n;
int* arr = new int[n]; // Dynamically allocate array
std::cout << "Please enter " << n << " Integer: " << std::endl;
for (int i = 0; i < n; i++) {
std::cin >> arr[i];
}
selectionSort(arr, n); // Sort
std::cout << "After sorting: " << std::endl;
for (int i = 0; i < n; i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
delete[] arr; // Free memory
arr = nullptr;
return 0;
}
الخرج:
Please enter the array size:
Please enter Integer:
After sorting:
نتيجة التشغيل:
Please enter the array size: 5
Please enter 5 Integer:
5 3 1 4 2
After sorting:
1 2 3 4 5
6. الأخطاء الشائعة
(1) 6.1 الحذف المزدوج
مثال خطأ:
int* p = new int(5);
delete p;
delete p; // ❌ Error: Double free of same memory (undefined behavior)
الإصلاح: اضبط على nullptr فورًا بعد التحرير (delete nullptr آمن).
delete p;
p = nullptr;
(2) 6.2 نسيان delete
مثال خطأ:
int* arr = new int[10];
// ❌ Forgot to write delete[] arr;
العاقبة: تسرب الذاكرة.
❓ أسئلة شائعة
▶ مثال 3: مصفوفة ديناميكية (صعوبة ⭐)
#include <iostream>
int main() {
int size = 5;
int* arr = new int[size];
for (int i = 0; i < size; i++) {
arr[i] = i * 10;
}
for (int i = 0; i < size; i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
delete[] arr;
return 0;
}
الخرج:
0 10 20 30 40
new int[n] تخصص مصفوفة، delete[] تحرر مصفوفة. لا تنسَ []!
- المكدس: يُخصص وقت الترجمة، يُحرر تلقائيًا، حجم ثابت
- الكومة: يُخصص وقت التشغيل، يُحرر يدويًا (
delete)، حجم متغير newتخصص ذاكرة،deleteتحرر ذاكرةnew[]تخصص مصفوفات،delete[]تحرر مصفوفات- تسرب الذاكرة: نسيان
delete، عواقب خطيرة - المؤشر المعلق: يشير إلى ذاكرة مُحررة، خطير
📖 ملخص
- new/delete: تخصيص/تحرير الذاكرة يدويًا
- new[]/delete[]: تخصيص/تحرير ذاكرة المصفوفات
- تسرب الذاكرة: نسيان التحرير يسبب هدر الموارد
- المؤشرات الذكية: يُوصى باستخدام unique_ptr/shared_ptr كبديل
📝 تمارين
-
أساسي (صعوبة ⭐): استخدم
newلتخصيصdouble، أسند له3.14، أخرجه، ثم حرره. -
متوسط (صعوبة ⭐⭐): دع المستخدم يدخل
n، استخدمnewلتخصيص مصفوفةintبحجمnديناميكيًا، أدخلnأعداد صحيحة، أوجد القيمة العظمى، ثم حرر الذاكرة. -
تحدي (صعوبة ⭐⭐⭐): نفّذ صنف "مصفوفة ديناميكية" باستخدام الذاكرة الديناميكية (ستتعلم الأصناف لاحقًا؛ استخدم struct الآن):
-
يتضمن:
int* data(مؤشر لمصفوفة الكومة)،int size(الحجم الحالي)،int capacity(السعة) -
نفّذ دالة
push_back: إذا كانsize == capacity، أعد تخصيص كتلة ذاكرة أكبر (newمصفوفة أكبر، انسخ البيانات، حرر القديمة) -
اختبر: أضف 100 عنصر
- new تخصص ذاكرة الكومة وتُرجع مؤشرًا
- delete تحرر الذاكرة المخصصة بـ new
- new[] تخصص مصفوفات، delete[] تحرر مصفوفات
- تسرب الذاكرة: استخدام new بدون delete
- فشل التخصيص: new تُلقي استثناء std::bad_alloc
7. 🚀 الخطوة التالية
الآن بعد أن تعلمت تخصيص الذاكرة الديناميكي، لنتابع إلى المؤشرات والمراجع: تطبيق شامل (الدرس 27) — تعزيز كل ما تعلمته من خلال أمثلة متكاملة!