تخطَّ إلى المحتوى

١٦ يوليو ٢٠٢٦ · آخر تحديث: ١٩ أكتوبر ٢٠٢٦

الفرق بين malloc و new في C++ بالعربي

شارك المقال:

malloc موروثة من لغة C، وnew جزء من لغة C++ نفسها. الاثنان يحجزان ذاكرة على الكومة (heap)، لكن السؤال الحقيقي ليس "أيهما أسرع"، بل: هل تحتاج بايتات، أم تحتاج كائنًا؟

  • malloc تعطيك بايتات خامًا بالحجم الذي طلبته، ولا تعرف ما الذي ستضعه فيها.
  • new تعطيك كائنًا جاهزًا: تحجز الذاكرة ثم تستدعي الباني (constructor). وdelete تستدعي الهادم (destructor) قبل أن تحرّر.

كل الأمثلة هنا مُترجمة ومُشغّلة على GCC 13 بمعيار C++17، والأخطاء مكشوفة بـ AddressSanitizer.

الفرق في مثال واحد

#include <cstdlib>
#include <iostream>
#include <new>
#include <string>

struct User {
    std::string name;
    User() : name("ضيف") { std::cout << "User() استُدعي\n"; }
    ~User() { std::cout << "~User() استُدعي\n"; }
};

int main() {
    User* a = new User;            // يحجز ويستدعي الباني
    std::cout << a->name << "\n";
    delete a;                      // يستدعي الهادم ثم يحرّر
}
User() استُدعي
ضيف
~User() استُدعي

لاحظ أن new User تُرجع User* مباشرة. أما malloc فتُرجع void* وتحتاج تحويلًا صريحًا، ولا تطبع شيئًا لأن الباني لا يُستدعى أصلًا.

لماذا لا تصلح malloc للكائنات: مثال يبدو أنه يعمل

#include <cstdlib>
#include <iostream>
#include <string>

struct User { std::string name; };

int main() {
    User* u = static_cast<User*>(std::malloc(sizeof(User)));
    u->name = "سارة";   // name لم يُبنَ أصلًا
    std::cout << u->name << "\n";
    std::free(u);
}

ترجمناه وشغّلناه عاديًا فطبع سارة وانتهى بنجاح. هذا أخطر ما في الأمر. الحقل name من نوع std::string، وله باني يهيّئ مؤشّراته الداخلية. malloc لم تستدعِه، فالإسناد كتب في كائن بحالة عشوائية. ما ظهر "صحيحًا" هنا كان مصادفة في محتوى الذاكرة. نفس البرنامج مع AddressSanitizer:

g++ -std=c++17 -g -fsanitize=address bad.cpp -o bad && ./bad
==5605==ERROR: AddressSanitizer: SEGV on unknown address ...
    #3 ... in main bad.cpp:9

السطر 9 هو u->name = "سارة". وحتى لو مرّ الإسناد، free لن تستدعي هادم std::string، فأي ذاكرة حجزها النص ستضيع (memory leak).

إن كنت مضطرًا لاستخدام ذاكرة من malloc (مثلًا جاءتك من مكتبة C)، فابنِ الكائن فيها صراحةً بـ placement new، واهدمه يدويًا:

void* raw = std::malloc(sizeof(User));
User* b = new (raw) User;   // يبني الكائن داخل ذاكرة موجودة، ولا يحجز شيئًا
std::cout << b->name << "\n";
b->~User();                 // استدعاء الهادم يدويًا
std::free(raw);             // ثم التحرير بنفس نظام الحجز

هذا نادر في كود التطبيقات العادي، لكنه يوضّح أن new العادية هي فعليًا خطوتان: حجز + بناء.

عند فشل الحجز

void* p = std::malloc(huge);          // تُرجع NULL، ويجب أن تفحصها بنفسك
char* q = new char[huge];             // ترمي std::bad_alloc
char* r = new (std::nothrow) char[huge]; // تُرجع nullptr بدل الاستثناء

جرّبناها بحجم 2^62 بايت (أكبر من أي ذاكرة حقيقية):

malloc: NULL
new: std::bad_alloc: std::bad_alloc
new(nothrow): nullptr

الفرق مهم في التصميم: كود malloc يجب أن يفحص كل استدعاء، وإن نسيت فستستخدم مؤشّرًا فارغًا لاحقًا. أما new فتوقف التنفيذ عند نقطة الفشل نفسها برمي استثناء.

لا تخلط بين النظامين: ثلاث تركيبات خاطئة

int* a = static_cast<int*>(std::malloc(sizeof(int)));
delete a;      // ❌ malloc مع delete

int* b = new int(5);
std::free(b);  // ❌ new مع free

int* c = new int[10];
delete c;      // ❌ new[] مع delete (الصحيح delete[])

الثلاثة سلوك غير معرَّف. الخبر الجيد أن أدواتك تمسكها إن طلبت منها:

  • وقت الترجمة: GCC 13 مع -Wall يحذّر في الحالات الثلاث، مثلًا:
warning: 'void operator delete(void*, long unsigned int)' called on pointer returned
from a mismatched allocation function [-Wmismatched-new-delete]
  • وقت التشغيل: AddressSanitizer يوقف البرنامج ويسمّي الخطأ بدقة:
ERROR: AddressSanitizer: alloc-dealloc-mismatch (malloc vs operator delete)
ERROR: AddressSanitizer: alloc-dealloc-mismatch (operator new [] vs operator delete)

القاعدة: malloc/calloc/realloc مع free، وnew مع delete، وnew[] مع delete[].

جدول المقارنة

malloc / freenew / delete
ما تعطيكبايتات خام بحجم تحسبه أنت (sizeof)كائن مبني من النوع المطلوب
الباني والهادملا يُستدعيانيُستدعيان تلقائيًا
نوع القيمة المُرجَعةvoid* يحتاج تحويلًاT* مباشرة
عند الفشلتُرجع NULLترمي std::bad_alloc (أو nullptr مع nothrow)
تغيير الحجمrealloc (للبيانات البسيطة فقط)لا يوجد؛ استخدم std::vector
المصفوفاتنفس الدالةnew[] وdelete[] منفصلان

إذن، أيهما تستخدم؟

في كود C++ حديث، غالبًا لا هذا ولا ذاك مباشرة:

#include <memory>
#include <vector>

auto u = std::make_unique<User>();   // بدل new User، ولا تحتاج delete أبدًا
std::vector<int> nums(10);           // بدل new int[10] وmalloc
nums.resize(20);                     // بدل realloc

std::make_unique تستدعي new في الداخل، لكنها تضمن استدعاء delete حين يخرج المؤشّر من نطاقه، حتى لو رُمي استثناء في المنتصف. هذه فكرة RAII المشروحة في مبدأ RAII، وأنواع المؤشّرات في المؤشّرات الذكية.

وكقاعدة قرار:

  • تكتب كود C++ عاديًا؟ std::make_unique وstd::vector وstd::string. لا new ولا malloc في الغالب.
  • تحتاج new صريحة؟ حين تبني بنية بيانات منخفضة المستوى بنفسك، وحتى هنا غلّف المؤشّر في unique_ptr فورًا.
  • تحتاج malloc؟ فقط على الحدود مع مكتبة C تحرّر الذاكرة بـ free أو تتوقعها منك، ولبيانات بسيطة بلا باني.

ولأن هذه الأخطاء "تعمل" أحيانًا قبل أن تنهار، اجعل -Wall -fsanitize=address جزءًا من بناء التطوير عندك. وإن انتهى بك الأمر بانهيار، فخطوات تتبّعه في حل Segmentation fault. أكمل إدارة الذاكرة من الأساس في درس المراجع وإدارة الذاكرة ثم المؤشّرات الذكية.

📚 مصادر رسمية للتعمّق: freeCodeCamp — مصدر تعلّم البرمجة

الأسئلة الشائعة

هل يمكن استخدام malloc بكود C++؟

نعم، والكود يُترجَم. لكنها لا تبني الكائن، فلا تصلح وحدها لأي صنف فيه باني أو أعضاء مثل std::string وstd::vector. استخدامها المعقول في C++ هو التعامل مع مكتبة C تتوقع ذاكرة من malloc أو تحرّرها بـ free.

ماذا يحدث لو حرّرت ذاكرة malloc بـ delete أو العكس؟

سلوك غير معرَّف (undefined behavior): قد يعمل البرنامج اليوم ويتعطل غدًا. GCC 13 يحذّر منه وقت الترجمة بـ -Wmismatched-new-delete، وAddressSanitizer يوقف البرنامج برسالة alloc-dealloc-mismatch.

هل new أبطأ من malloc؟

التخصيص نفسه متقارب جدًا، وفي التطبيقات الشائعة تستدعي new في النهاية مخصّص ذاكرة شبيهًا بـ malloc. الفرق الحقيقي هو استدعاء الباني، وهو عمل كنت ستحتاجه على أي حال لتهيئة الكائن. لا تختر malloc بحجة الأداء.

ما بديل realloc في C++؟

لا يوجد عامل مقابل لـ realloc مع new. استخدم std::vector: دالة resize تكبّر أو تصغّر المصفوفة وتنقل العناصر بأمان وتستدعي البواني والهوادم، وهذا ما لا تفعله realloc مع الكائنات.

اقرأ أيضًا

تصفّح كل المقالات في المدوّنة، أو ابدأ التعلّم من المسارات و خرائط الطريق.