هل يمكن استخدام الموضع الجديد للمصفوفات بطريقة محمولة؟

StackOverflow https://stackoverflow.com/questions/15254

سؤال

هل من الممكن بالفعل الاستفادة من الموضع الجديد في التعليمات البرمجية المحمولة عند استخدامه للمصفوفات؟

يبدو أن المؤشر الذي استرجعته من new[] ليس دائمًا هو نفس العنوان الذي قمت بتمريره (5.3.4، الملاحظة 12 في المعيار يبدو أنها تؤكد صحة هذا)، لكنني لا أرى كيف يمكنك ذلك يمكن تخصيص مخزن مؤقت للمصفوفة للدخول إذا كان هذا هو الحال.

يوضح المثال التالي المشكلة.تم تجميع هذا المثال باستخدام Visual Studio، مما يؤدي إلى تلف الذاكرة:

#include <new>
#include <stdio.h>

class A
{
    public:

    A() : data(0) {}
    virtual ~A() {}
    int data;
};

int main()
{
    const int NUMELEMENTS=20;

    char *pBuffer = new char[NUMELEMENTS*sizeof(A)];
    A *pA = new(pBuffer) A[NUMELEMENTS];

    // With VC++, pA will be four bytes higher than pBuffer
    printf("Buffer address: %x, Array address: %x\n", pBuffer, pA);

    // Debug runtime will assert here due to heap corruption
    delete[] pBuffer;

    return 0;
}

بالنظر إلى الذاكرة، يبدو أن المترجم يستخدم البايتات الأربعة الأولى من المخزن المؤقت لتخزين عدد العناصر الموجودة فيه.وهذا يعني أنه بسبب المخزن المؤقت فقط sizeof(A)*NUMELEMENTS كبير، تتم كتابة العنصر الأخير في المصفوفة في كومة غير مخصصة.

لذا فإن السؤال هو هل يمكنك معرفة مقدار النفقات الإضافية التي يحتاجها التنفيذ الخاص بك من أجل استخدام الموضع الجديد[] بأمان؟من الناحية المثالية، أحتاج إلى تقنية يمكن نقلها بين المترجمين المختلفين.لاحظ أنه، على الأقل في حالة VC، يبدو أن النفقات العامة تختلف باختلاف الفئات.على سبيل المثال، إذا قمت بإزالة المدمر الظاهري في المثال، فإن العنوان الذي تم إرجاعه من new[] هو نفس العنوان الذي قمت بتمريره.

هل كانت مفيدة؟

المحلول

أنا شخصياً أختار عدم استخدام الموضع الجديد في المصفوفة وبدلاً من ذلك استخدام الموضع الجديد في كل عنصر في المصفوفة على حدة.على سبيل المثال:

int main(int argc, char* argv[])
{
  const int NUMELEMENTS=20;

  char *pBuffer = new char[NUMELEMENTS*sizeof(A)];
  A *pA = (A*)pBuffer;

  for(int i = 0; i < NUMELEMENTS; ++i)
  {
    pA[i] = new (pA + i) A();
  }

  printf("Buffer address: %x, Array address: %x\n", pBuffer, pA);

  // dont forget to destroy!
  for(int i = 0; i < NUMELEMENTS; ++i)
  {
    pA[i].~A();
  }    

  delete[] pBuffer;

  return 0;
}

بغض النظر عن الطريقة التي تستخدمها، تأكد من تدمير كل عنصر من هذه العناصر الموجودة في المصفوفة يدويًا قبل حذف pBuffer، حيث قد ينتهي بك الأمر إلى حدوث تسريبات؛)

ملحوظة:لم أقم بتجميع هذا، ولكن أعتقد أنه يجب أن يعمل (أنا على جهاز لا يحتوي على مترجم C++ مثبت).لا يزال يشير إلى هذه النقطة :) آمل أن يساعد بطريقة ما!


يحرر:

السبب وراء الحاجة إلى تتبع عدد العناصر هو أنه يمكنه التكرار من خلالها عند استدعاء الحذف على المصفوفة والتأكد من استدعاء أدوات التدمير على كل كائن.إذا لم يكن يعرف عددهم فلن يتمكن من القيام بذلك.

نصائح أخرى

@ديريك

5.3.4، القسم 12 يتحدث عن الحمل العام لتخصيص المصفوفة، وما لم أخطئ في قراءته، يبدو أنه يوحي لي أنه من الصحيح للمترجم إضافته في الموضع الجديد أيضًا:

قد يتم تطبيق هذا الحمل في كافة التعبيرات الجديدة للمصفوفة، بما في ذلك تلك التي تشير إلى مشغل وظيفة المكتبة new[](std::size_t, void*) ووظائف تخصيص المواضع الأخرى.قد يختلف مقدار النفقات العامة من استدعاء جديد إلى آخر.

ومع ذلك، أعتقد أن VC كان المترجم الوحيد الذي سبب لي مشكلة في هذا، ومن بينه دول مجلس التعاون الخليجي وCodewarrior وProDG.ومع ذلك، يجب أن أتحقق مرة أخرى للتأكد.

شكرا على الردود.كان استخدام الموضع الجديد لكل عنصر في المصفوفة هو الحل الذي انتهى بي الأمر باستخدامه عندما واجهت هذا (آسف، كان ينبغي أن أذكر ذلك في السؤال).لقد شعرت أنه لا بد أن هناك شيئًا ما كنت أفتقده بشأن القيام بذلك مع الموضع الجديد[].كما هو الحال، يبدو أن الموضع الجديد[] غير قابل للاستخدام بشكل أساسي بفضل المعيار الذي يسمح للمترجم بإضافة حمل إضافي غير محدد إلى المصفوفة.لا أرى كيف يمكنك استخدامه بأمان وسهولة.

لست واضحًا حقًا سبب احتياجها إلى البيانات الإضافية، حيث أنك لن تستدعي حذف [] على المصفوفة على أي حال، لذلك لا أفهم تمامًا سبب حاجتها إلى معرفة عدد العناصر الموجودة فيها.

@جوامع

لست واضحًا حقًا سبب احتياجها إلى البيانات الإضافية، حيث أنك لن تستدعي حذف [] على المصفوفة على أي حال، لذلك لا أفهم تمامًا سبب حاجتها إلى معرفة عدد العناصر الموجودة فيها.

بعد التفكير في هذا الأمر، أنا أتفق معك.لا يوجد سبب يجعل الموضع الجديد يحتاج إلى تخزين عدد العناصر، لأنه لا يوجد حذف للموضع.نظرًا لعدم وجود حذف للموضع، فلا يوجد سبب للموضع الجديد لتخزين عدد العناصر.

لقد اختبرت هذا أيضًا مع gcc على جهاز Mac الخاص بي، باستخدام فئة مع أداة تدمير.على نظامي، كان الموضع الجديد لا تغيير المؤشر.هذا يجعلني أتساءل عما إذا كانت هذه مشكلة VC++، وما إذا كان هذا قد ينتهك المعيار (المعيار لا يعالج هذا على وجه التحديد، بقدر ما أستطيع أن أجد).

الموضع الجديد في حد ذاته هو أمر محمول، لكن الافتراضات التي تقوم بها حول ما يفعله بكتلة معينة من الذاكرة ليست محمولة.كما قيل من قبل، إذا كنت مترجمًا وتم إعطاؤك جزءًا من الذاكرة، فكيف ستعرف كيفية تخصيص مصفوفة وتدمير كل عنصر بشكل صحيح إذا كان كل ما لديك هو مؤشر؟(راجع واجهة حذف عامل التشغيل[].)

يحرر:

ويوجد بالفعل حذف للموضع، ويتم استدعاؤه فقط عندما يطرح المُنشئ استثناءً أثناء تخصيص مصفوفة بموضع جديد[].

ما إذا كان new[] يحتاج بالفعل إلى تتبع عدد العناصر بطريقة أو بأخرى هو أمر متروك للمعيار، مما يترك الأمر للمترجم.لسوء الحظ، في هذه الحالة.

أعتقد أن دول مجلس التعاون الخليجي تفعل نفس الشيء مثل MSVC، ولكن بالطبع هذا لا يجعلها "محمولة".

أعتقد أنه يمكنك حل المشكلة عندما يكون NUMELEMENTS بالفعل ثابتًا لوقت الترجمة، كما يلي:

typedef A Arr[NUMELEMENTS];

A* p = new (buffer) Arr;

يجب أن يستخدم هذا الموضع العددي الجديد.

على غرار الطريقة التي تستخدم بها عنصرًا واحدًا لحساب حجم موضع واحد، استخدم مصفوفة من تلك العناصر لحساب الحجم المطلوب لمصفوفة.

إذا كنت تريد الحجم لإجراء عمليات حسابية أخرى حيث قد لا يكون عدد العناصر معروفًا، فيمكنك استخدام sizeof(A[1]) والضرب بعدد العناصر المطلوبة.

على سبيل المثال

char *pBuffer = new char[ sizeof(A[NUMELEMENTS]) ];
A *pA = (A*)pBuffer;

for(int i = 0; i < NUMELEMENTS; ++i)
{
    pA[i] = new (pA + i) A();
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top