كيف يمكن لـ C وC++ تخزين كائنات كبيرة على المكدس؟

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

  •  07-07-2019
  •  | 
  •  

سؤال

أحاول معرفة كيفية تخزين C وC++ للكائنات الكبيرة على المكدس.عادةً ما يكون حجم المكدس عددًا صحيحًا، لذلك لا أفهم كيف يتم تخزين الكائنات الأكبر هناك.هل يشغلون ببساطة "فتحات" متعددة للمكدس؟

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

المحلول

ومكدس هو قطعة من الذاكرة. نقاط مؤشر مكدس إلى الأعلى. يمكن دفع القيم على المكدس وبرزت لاستردادها.

وعلى سبيل المثال إذا كان لدينا وظيفة وهو ما يسمى مع معلمتين (1 بايت الحجم والآخر 2 بايت الحجم، مجرد افتراض لدينا PC 8 بت).

ويتم دفع كلا على كومة هذا يتحرك المؤشر كومة:

03: par2 byte2
02: par2 byte1
01: par1

والآن يتم استدعاء الدالة وضعت المخاطبة العائد على كومة:

05: ret byte2
04: ret byte1
03: par2 byte2
02: par2 byte1
01: par1

وOK، ضمن الدالة لدينا 2 المتغيرات المحلية؛ واحدة من 2 بايت واحد 4. للحصول على هذه محجوز وضع على المكدس، ولكن علينا أولا حفظ مؤشر مكدس حتى نعرف أين تبدأ المتغيرات عن طريق العد حتى وجدت المعلمات من قبل العد التنازلي.

11: var2 byte4
10: var2 byte3
09: var2 byte2
08: var2 byte1
07: var1 byte2
06: var1 byte1
    ---------
05: ret byte2
04: ret byte1
03: par2 byte2
02: par2 byte1
01: par1

وكما ترون، يمكنك وضع أي شيء على كومة طالما لديك اليسار الفضاء. وإلا سوف تحصل على الظواهر التي تعطي هذا الموقع اسمها.

نصائح أخرى


المكدس والكومة ليسا مختلفين كما تعتقد!


صحيح أن بعض أنظمة التشغيل لديها قيود على المكدس.(بعض هؤلاء أيضًا لديهم قيود سيئة على الكومة أيضًا!)

ولكن هذا ليس عام 1985 بعد الآن.

في هذه الأيام، أقوم بتشغيل Linux!

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

أكبر الاختلافات بين كومة و كومة نكون:

  1. كومة تقوم التخصيصات فقط بإزاحة المؤشر (وربما تخصيص صفحات ذاكرة جديدة إذا أصبح حجم المكدس كبيرًا بدرجة كافية). كومة يجب عليه البحث في هياكل البيانات الخاصة به للعثور على كتلة ذاكرة مناسبة.(وربما تخصيص صفحات ذاكرة جديدة أيضًا.)
  2. كومة يخرج عن النطاق عندما تنتهي الكتلة الحالية. كومة يخرج عن النطاق عند استدعاء الحذف/المجاني.
  3. كومة يمكن الحصول على مجزأة. كومة لا يتم تجزئتها أبدًا.

في نظام التشغيل Linux، كلاهما كومة و كومة تتم إدارتها من خلال الذاكرة الافتراضية.

فيما يتعلق بوقت التخصيص، حتى البحث في الكومة من خلال الذاكرة المجزأة بشكل سيئ لا يمكن أن يكون بمثابة شمعة لرسم الخرائط في صفحات جديدة من الذاكرة. من حيث الوقت، فإن الاختلافات لا تذكر!

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

(جديد سوف يستدعي المُنشئ ، والذي من المفترض أن يستخدم صفحات الذاكرة تلك ...)


يمكنك تدمير نظام VM عن طريق إنشاء وتدمير كائنات كبيرة على أي منهما كومة أو ال كومة.يعتمد الأمر على نظام التشغيل/المترجم الخاص بك فيما إذا كان يمكن للنظام استعادة الذاكرة أم لا.إذا لم يتم استعادتها، فقد تتمكن الكومة من إعادة استخدامها.(على افتراض أنه لم يتم إعادة توجيهه من قبل شخص آخر مالوك () في هذه الأثناء.) وبالمثل، إذا لم تتم استعادة المكدس، فسيتم إعادة استخدامه.

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


من كل هذه الأشياء، أنا قلق بشأن تجزئة الذاكرة أكثر من غيرها!

دائمًا ما يكون العمر الافتراضي (عندما يخرج عن النطاق) هو العامل الحاسم.

ولكن عند تشغيل البرامج لفترات طويلة من الوقت، تؤدي التجزئة إلى إنشاء بصمة ذاكرة متزايدة تدريجيًا.التبادل المستمر يقتلني في النهاية!




تم التعديل لإضافة:


يا رجل، لقد أفسدت!

شيء ما لم يكن يضيف هنا...لقد اعتقدت إما أنني * كنت بعيدًا عن القاعدة.أو كان الجميع.أو على الأرجح كلاهما.أو ربما لا.

مهما كانت الإجابة، كان علي أن أعرف ما الذي يحدث!

...وهذا سوف يكون طويلا.تحمل معي...


لقد أمضيت معظم السنوات الـ 12 الماضية في العمل تحت نظام Linux.وقبل ذلك بحوالي 10 سنوات تحت نكهات مختلفة من يونكس.وجهة نظري حول أجهزة الكمبيوتر متحيزة إلى حد ما.لقد أفسدت!

لقد فعلت القليل مع ويندوز، ولكن ليس بما فيه الكفاية للتحدث بشكل موثوق.ولا، بشكل مأساوي، مع نظام التشغيل Mac OS/Darwin أيضًا ...على الرغم من أن Mac OS/Darwin/BSD قريب بدرجة كافية بحيث تنتقل بعض معرفتي.


باستخدام مؤشرات 32 بت، نفدت مساحة العنوان البالغة 4 جيجابايت (2^32).

من الناحية العملية، كومة+كومة مجتمعة هي عادةً ما يقتصر حجمها على ما بين 2-4 غيغابايت حيث تحتاج إلى تعيين أشياء أخرى هناك.

(هناك ذاكرة مشتركة، ومكتبات مشتركة، وملفات معينة للذاكرة، والصورة القابلة للتنفيذ التي تقوم بتشغيلها دائمًا ما تكون رائعة، وما إلى ذلك)


ضمن Linux/Unix/MacOS/Darwin/BSD، يمكنك تقييد ملفات كومة أو ال كومة إلى أي قيم تعسفية تريدها في وقت التشغيل.ولكن في النهاية هناك حدود صارمة للنظام.

هذا هو التمييز (في tcsh) لـ "حد" ضد "الحد -ح".أو (في باش) من "أوليميت-سا" ضد "أوليميت-ها".أو برمجيا، من rlim_cur ضد rlim_max في حد البنية.


الآن نصل إلى الجزء الممتع.بالنسبة إلى كود مارتن يورك.(شكرًا لك مارتن!مثال جيد.من الجيد دائمًا تجربة الأشياء!)

مارتن من المفترض أن يعمل على جهاز Mac.(حديثة إلى حد ما.بناء المترجم الخاص به أحدث مني!)

بالتأكيد، لن يتم تشغيل الكود الخاص به على جهاز Mac الخاص به افتراضيًا.ولكن سيتم تشغيله على ما يرام إذا قام بالاستدعاء أولاً "حجم مكدس غير محدود" (تشش) أو "ulimit -Ss غير محدود" (سحق).


لب الموضوع:


اختبار على صندوق نواة Linux RH9 2.4.x قديم (قديم)، مع تخصيص كميات كبيرة من كومة   أو   كومة, ، أي منهما بحد ذاته يصل إلى ما بين 2 و 3 جيجابايت.(للأسف، تبلغ مساحة ذاكرة الوصول العشوائي + SWAP الخاصة بالجهاز أقل بقليل من 3.5 جيجابايت.إنه نظام تشغيل 32 بت.وهذا هو لا العملية الوحيدة قيد التشغيل.نكتفي بما لدينا...)

لذلك ليس هناك حقا أي قيود على كومة الحجم مقابل كومة الحجم في نظام التشغيل Linux، بخلاف الحجم الاصطناعي...


لكن:


على جهاز Mac، يوجد حد صارم لحجم المكدس وهو 65532 كيلو بايت.يتعلق الأمر بكيفية وضع الأشياء في الذاكرة.


عادة، أنت تفكر في وجود نظام مثالي كومة في أحد طرفي مساحة عنوان الذاكرة، كومة في الآخر، ويبنون تجاه بعضهم البعض.عندما يجتمعون، نفدت الذاكرة.

يبدو أن أجهزة Mac تلتصق بها مكتبات النظام المشتركة بينهما بإزاحة ثابتة تحد من كلا الجانبين.لا يزال بإمكانك الركض كود مارتن يورك مع "حجم مكدس غير محدود"، نظرًا لأنه يخصص فقط ما يقرب من 8 MiB (<64 MiB) من البيانات. لكنه سوف ينفد كومة قبل وقت طويل من نفاده كومة.

أنا على لينكس.أنا لن. آسف يا طفل.وهنا النيكل.اذهب واحصل على نظام تشغيل أفضل.

هناك حلول بديلة لنظام التشغيل Mac.لكنها تصبح قبيحة وفوضوية وتتضمن تعديل معلمات النواة أو الرابط.

على المدى الطويل، ما لم تفعل شركة Apple شيئًا غبيًا حقًا، فإن مساحات العناوين ذات 64 بت ستجعل هذا الأمر برمته المتعلق بتقييد المكدس عفا عليه الزمن في وقت قريب جدًا الآن.


الانتقال إلى التجزئة:


في أي وقت تقوم فيه بدفع شيء ما إلى كومة تم إلحاقه بالنهاية.ويتم إزالته (إرجاعه) عند خروج الكتلة الحالية.

ونتيجة لذلك، لا توجد ثقوب في كومة.إنها كلها كتلة صلبة كبيرة من الذاكرة المستخدمة.مع وجود مساحة صغيرة غير مستخدمة في النهاية، كلها جاهزة لإعادة الاستخدام.

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

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


الذي يطرح مبادلة السحق:


  • إذا كان لديك بالفعل كمية كبيرة من الكومة المخصصة/قيد الاستخدام.
  • إذا كان لديك الكثير من الثقوب المجزأة المنتشرة.
  • وإذا كان لديك عدد كبير من المخصصات الصغيرة.

بعد ذلك، يمكنك الحصول على عدد كبير من المتغيرات، كلها مستخدمة في منطقة محلية صغيرة من الكود، والمنتشرة عبر عدد كبير من صفحات الذاكرة الافتراضية.(كما هو الحال في أنك تستخدم 4 بايت على هذه الصفحة التي يبلغ حجمها 2 كيلو بايت، و8 بايت على الصفحة التي يبلغ حجمها 2 كيلو بايت، وهكذا لعدد كبير من الصفحات...)

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

ومن ناحية أخرى، لو تم إجراء هذه التخصيصات الصغيرة على كومة, ، فستكون جميعها موجودة في امتداد متجاور من الذاكرة.سيلزم تحميل عدد أقل من صفحات ذاكرة VM.(4+8+...<2 ألف للفوز.)

ملاحظة جانبية:سبب لفت الانتباه إلى هذا ينبع من مهندس كهربائي أعرفه والذي أصر على تخصيص جميع المصفوفات على HEAP.كنا نقوم بحسابات المصفوفة للرسومات.* الكثير * من 3 أو 4 صفائف عناصر.كانت إدارة الجديد/الحذف بمفردها بمثابة كابوس.حتى تجريدها بعيدا في الفصول الدراسية كان سببا في الحزن!


الموضوع التالي.خيوط:


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

يمكنك تغيير ذلك باستخدام pthread_attr_setstacksize().على الرغم من اعتماده على تطبيق الترابط الخاص بك، إذا كانت سلاسل الرسائل المتعددة تشترك في نفس مساحة العنوان 32 بت، ستكون الأكوام الفردية الكبيرة لكل خيط مشكلة! ليس هناك مساحة كبيرة!مرة أخرى، سيساعدك الانتقال إلى مساحات العناوين 64 بت (أنظمة التشغيل).

pthread_t       threadData;
pthread_attr_t  threadAttributes;

pthread_attr_init( & threadAttributes );
ASSERT_IS( 0, pthread_attr_setdetachstate( & threadAttributes,
                                             PTHREAD_CREATE_DETACHED ) );

ASSERT_IS( 0, pthread_attr_setstacksize  ( & threadAttributes,
                                             128 * 1024 * 1024 ) );

ASSERT_IS( 0, pthread_create ( & threadData,
                               & threadAttributes,
                               & runthread,
                               NULL ) );

بالنسبة إلى مارتن يورك إطارات المكدس:


ربما أنت وأنا نفكر في أشياء مختلفة؟

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

لم أر قط أي قيود على حجم ملف إطار المكدس.هناك قيود على كومة ككل، ولكن هذا كل شيء إطارات المكدس مجموع.

هناك رسم تخطيطي لطيف ومناقشة إطارات المكدس على ويكي.


وفي ملاحظة أخيرة:


ضمن Linux/Unix/MacOS/Darwin/BSD، من الممكن تغيير الحد الأقصى كومة قيود الحجم برمجيا كذلك حد(تشش) أو ulimit(سحق):

struct rlimit  limits;
limits.rlim_cur = RLIM_INFINITY;
limits.rlim_max = RLIM_INFINITY;
ASSERT_IS( 0, setrlimit( RLIMIT_STACK, & limits ) );

فقط لا تحاول ضبطه على INFINITY على جهاز Mac...وقم بتغييره قبل أن تحاول استخدامه.؛-)


قراءة متعمقة:



وPush وpop تعليمات عادة لا تستخدم لتخزين المتغيرات إطار مكدس المحلية. في بداية الدالة، يتم تعيين إطار مكدس من قبل decrementing مؤشر كومة من قبل عدد من وحدات البايت (محاذاة إلى حجم الكلمة) التي تتطلبها المتغيرات المحلية والدالة. هذا يخصص مبلغ المطلوب من الفضاء "على كومة" من أجل هذه القيم. ثم يتم الوصول إلى جميع المتغيرات المحلية عبر مؤشر إلى هذا الإطار كومة (ebp على x86) و.

ومكدس هو كتلة كبيرة من الذاكرة التي تخزن المتغيرات المحلية والمعلومات للعودة من المكالمات وظيفة، وما إلى ذلك الحجم الفعلي من المكدس يختلف بشكل كبير على نظام التشغيل. على سبيل المثال، عند إنشاء موضوع جديد على ويندوز، و الافتراضي حجم 1 MB .

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

لا ينقسم كومة إلى أجزاء لعدد صحيح الحجم. انها مجرد مجموعة مسطحة بايت. لقد فهرستها من قبل "صحيح" من نوع size_t (وليس كثافة العمليات). إذا قمت بإنشاء كائن كومة الكبيرة التي تناسبها في الفضاء المتاحة حاليا، فإنه يستخدم فقط تلك المساحة التي كتبها الاهتزاز حتى (أو لأسفل) مؤشر المكدس.

وكما أشار آخرون إلى أنه من الأفضل استخدام كومة عن الأجسام الكبيرة، وليس المكدس. هذا يتجنب المشاكل كومة تجاوز.

تعديل: إذا كنت تستخدم تطبيق 64 بت ونظام التشغيل الخاص بك ووقت التشغيل المكتبات لطيفة لك (انظر آخر mrree)، وبعد ذلك يجب أن يكون على ما يرام لتخصيص الكائنات المؤقتة كبيرة على كومة. إذا كان التطبيق الخاص بك هو 32 بت و / أو مكتبة OS / وقت التشغيل ليست جميلة، عليك ربما تحتاج إلى تخصيص هذه الكائنات على الكومة.

وكلما قمت بإدخال وظيفة، المكدس ينمو لتتناسب مع المتغيرات المحلية في تلك الوظيفة. نظرا فئة largeObject يستخدم نقول 400 بايت:

void MyFunc(int p1, largeObject p2, largeObject *p3)
{
   int s1;
   largeObject s2;
   largeObject *s3;
}

عند استدعاء هذه الوظيفة، والكدسة ننظر بشيء من مثل هذا (سوف تختلف التفاصيل بناء على استدعاء اصطلاح والهندسة المعمارية):

   [... rest of stack ...]
   [4 bytes for p1] 
   [400 bytes for p2]
   [4 bytes for p3]
   [return address]
   [old frame pointer]
   [4 bytes for s1]
   [400 bytes for s2]
   [4 bytes for s3]

إلى x86 اتفاقيات دعوة للحصول على بعض المعلومات عن كيفية عمل المكدس. لديها MSDN أيضا بعض الرسوم البيانية لطيفة لبضع convections استدعاء مختلفة، مع رمز نموذج الناتجة المخططات كومة .

وكما قال آخرون، ليس من الواضح تماما ما تعنيه ب "الأجسام الكبيرة" ... ومع ذلك، وبما انك ثم يطلب

<اقتباس فقرة>   

هل لمجرد تناول كومة متعددة   "فتحات"؟

وأنا ذاهب لنفترض أنك ببساطة يعني أي شيء أكبر من عدد صحيح. كما لاحظ شخص آخر، على الرغم من لا يملك كومة "فتحات"، صحيح الحجم - انها مجرد جزء من الذاكرة، وكل بايت في أن لديها عنوان خاص به. المترجم يتتبع كل متغير من عنوان <م> أولا بايت من هذا المتغير - هذه هي القيمة التي تحصل عليها إذا كنت تستخدم عنوان المشغل (&var)، وقيمة مؤشر ل فقط هذا العنوان لبعض المتغيرات الأخرى. يعرف المترجم أيضا ما هو نوع هو كل متغير (قلت ذلك عند تعريف المتغير)، وأنه يعرف كيف كبيرة وينبغي أن يكون كل نوع - عند ترجمة البرنامج، فإنه مهما الرياضيات هو ضروري لمعرفة مقدار المساحة التي سوف تحتاج المتغيرات عندما يتم استدعاء وظيفة، ويتضمن نتيجة لذلك في رمز الدالة دخول نقطة (إطار مكدس PDaddy المذكورة).

في C و C ++ يجب أن لا تخزين كائنات كبيرة على المكدس، لأن كومة محدودة (كما كنت تفكر). كومة لكل موضوع عادة سوى بضعة ميغا بايت أو أقل (يمكن أن تكون محددة عند إنشاء موضوع). عند استدعاء "جديد" لإنشاء كائن، لا يتم وضعه على كومة - وضعه على كومة بدلا

وحجم المكدس محدودة. ومن المقرر عادة حجم مكدس عند إنشاء هذه العملية. كل موضوع في هذه العملية تلقائيا يحصل على حجم مكدس الافتراضية إذا لم ينص على خلاف ذلك في دعوة CreateThread (). لذا، نعم: يمكن أن يكون هناك كومة متعددة "فتحات"، ولكن كل موضوع واحد فقط. وأنها لا يمكن أن تكون مشتركة بين المواضيع.

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

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

هل يمكن أن يكون الأشياء التي هي ضخمة بما فيه الكفاية (أو متعددة بما فيه الكفاية) أنه لن يكون له معنى لوضعها على المكدس. في هذه الحالة، يمكنك وضع الكائن في كومة ووضع مؤشر على المكدس. هذا هو الفرق بين النجاح من حيث القيمة وتمرير حسب المرجع.

وكيف تعرف كائن كبير؟ نتحدث أكبر أو أقل من حجم مساحة مكدس المخصصة؟

وعلى سبيل المثال إذا كان لديك شيء من هذا القبيل:

void main() {
    int reallyreallybigobjectonthestack[1000000000];
}

واعتمادا على النظام الخاص بك سوف تحصل على الأرجح على segfault لأن هناك ببساطة ليست مساحة كافية لتخزين الكائن. وإلا فإنه يتم تخزينها مثل أي كائن آخر. إذا الحديث الخاص بك في الذاكرة الفعلية الفعلية ثم لم يكن لديك ما يدعو للقلق حول هذا لأن الذاكرة الظاهرية على مستوى نظام التشغيل رعاية التعامل مع ذلك.

وأيضا حجم المكدس هو ليس من المرجح حجم عدد صحيح أنه يعتمد كليا على نظام التشغيل الخاص بك وتخطيط التطبيقات <لأ href = "http://en.wikipedia.org/wiki/Virtual_address_space" يختلط = "noreferrer نوفولو"> مساحة العنوان الظاهري .

وبواسطة "المكدس هو حجم عدد صحيح"، يعني "مؤشر المكدس هو حجم عدد صحيح". ويشير إلى أعلى المكدس، وهي منطقة كبيرة من الذاكرة. حسنا، أكبر من عدد صحيح.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top