أين يتم تخزين المتغيرات الثابتة في C و C++؟
-
01-07-2019 - |
سؤال
في أي جزء (.BSS، .DATA، وغيرها) من الملف القابل للتنفيذ يتم تخزين المتغيرات الثابتة بحيث لا يكون هناك تضارب في الأسماء؟على سبيل المثال:
foo.c: bar.c:
static int foo = 1; static int foo = 10;
void fooTest() { void barTest() {
static int bar = 2; static int bar = 20;
foo++; foo++;
bar++; bar++;
printf("%d,%d", foo, bar); printf("%d, %d", foo, bar);
} }
إذا قمت بتجميع كلا الملفين وربطهما بملف رئيسي يستدعي fooTest() وbarTest بشكل متكرر، فستزداد عبارات printf بشكل مستقل.يبدو الأمر منطقيًا نظرًا لأن متغيرات foo وbar محلية لوحدة الترجمة.
ولكن أين يتم تخصيص التخزين؟
للتوضيح، الافتراض هو أن لديك سلسلة أدوات يمكنها إخراج ملف بتنسيق ELF.وهكذا، أنا يعتقد أن هناك لديه ليكون هناك بعض المساحة المحجوزة في الملف القابل للتنفيذ لتلك المتغيرات الثابتة.
لأغراض المناقشة، لنفترض أننا نستخدم سلسلة أدوات مجلس التعاون الخليجي.
المحلول
يعتمد المكان الذي تذهب إليه الإحصائيات الخاصة بك على ما إذا كانت كذلك صفر التهيئة أم لا. صفر التهيئة تدخل البيانات الثابتة .BSS (الحظر الذي بدأ بواسطة الرمز), ، عدم صفر التهيئة تدخل البيانات .بيانات
نصائح أخرى
عندما يتم تحميل برنامج ما في الذاكرة، يتم تنظيمه إلى أجزاء مختلفة.أحد الأجزاء هو شريحة البيانات.ينقسم قطاع البيانات أيضًا إلى قسمين:
شريحة البيانات التي تمت تهيئتها: يتم تخزين جميع البيانات العالمية والثابتة والثابتة هنا.
شريحة البيانات غير المهيأة (BSS): يتم تخزين جميع البيانات غير المهيأة في هذا الجزء.
وفيما يلي رسم تخطيطي لشرح هذا المفهوم:
إليك رابط جيد جدًا يشرح هذه المفاهيم:
في الواقع، المتغير عبارة عن صف (التخزين، النطاق، النوع، العنوان، القيمة):
storage : where is it stored, for example data, stack, heap...
scope : who can see us, for example global, local...
type : what is our type, for example int, int*...
address : where are we located
value : what is our value
يمكن أن يعني النطاق المحلي محليًا إما لوحدة الترجمة (الملف المصدر) أو الوظيفة أو الكتلة اعتمادًا على مكان تعريفها.لجعل المتغير مرئيًا لأكثر من وظيفة واحدة، يجب بالتأكيد أن يكون إما في DATA أو في منطقة BSS (اعتمادًا على ما إذا تمت تهيئته بشكل صريح أم لا، على التوالي).ثم يتم تحديد نطاقه وفقًا لجميع الوظائف (الوظائف) أو الوظيفة (الوظائف) داخل الملف المصدر.
سيعتمد موقع تخزين البيانات على التنفيذ.
ومع ذلك فإن معنى ثابتة هو "الارتباط الداخلي".وهكذا يكون الرمز داخلي إلى وحدة الترجمة (foo.c, bar.c) ولا يمكن الرجوع إليها خارج وحدة الترجمة تلك.لذلك، لا يمكن أن يكون هناك تضارب في الأسماء.
لا أعتقد أنه سيكون هناك تصادم.يؤدي استخدام ثابت على مستوى الملف (الوظائف الخارجية) إلى وضع علامة على المتغير على أنه محلي لوحدة الترجمة الحالية (ملف).لا يكون مرئيًا أبدًا خارج الملف الحالي، لذا لا يجب أن يكون له اسم أبدًا.
يختلف استخدام الثبات داخل الدالة - فالمتغير يكون مرئيًا فقط للدالة، ولكن يتم الاحتفاظ بقيمته عبر استدعاءات تلك الدالة.
في الواقع، تقوم الأشياء الثابتة بشيئين مختلفين اعتمادًا على مكان وجودها.ومع ذلك، في حالات أخرى، فإنه يحد من رؤية المتغير لمنع تعارض مساحة الاسم،
بعد قولي هذا، أعتقد أنه سيتم تخزينه في DATA الذي يميل إلى تهيئة المتغير.كان BSS في الأصل يرمز إلى byte-set-<something> الذي يحتوي على متغيرات لم تتم تهيئتها.
كيف تجدها بنفسك مع objdump -Sr
لفهم ما يحدث فعليًا، يجب أن تفهم نقل الرابط.إذا لم تلمس ذلك من قبل، فكر في الأمر قراءة هذا المنصب أولا.
دعنا نحلل مثال Linux x86-64 ELF لرؤيته بأنفسنا:
#include <stdio.h>
int f() {
static int i = 1;
i++;
return i;
}
int main() {
printf("%d\n", f());
printf("%d\n", f());
return 0;
}
جمع مع:
gcc -ggdb -c main.c
قم بفك الكود باستخدام:
objdump -Sr main.o
-S
يفك شفرة الكود مع اختلاط المصدر الأصلي-r
يظهر معلومات النقل
داخل فك f
نحن نرى:
static int i = 1;
i++;
4: 8b 05 00 00 00 00 mov 0x0(%rip),%eax # a <f+0xa>
6: R_X86_64_PC32 .data-0x4
و ال .data-0x4
يقول أنه سوف يذهب إلى البايت الأول من .data
شريحة.
ال -0x4
موجود لأننا نستخدم RIP عنونة نسبية، وبالتالي فإن %rip
في التعليمات و R_X86_64_PC32
.
إنه مطلوب لأن RIP يشير إلى التالي التعليمات، والتي تبدأ بعد 4 بايت 00 00 00 00
وهو ما سيتم نقله.ولقد شرحت ذلك بمزيد من التفصيل في: https://stackoverflow.com/a/30515926/895245
ثم إذا قمنا بتعديل المصدر إلى i = 1
ونقوم بنفس التحليل نستنتج أن:
static int i = 0
يستمر.bss
static int i = 1
يستمر.data
في المنطقة "العالمية والثابتة" :)
هناك العديد من مناطق الذاكرة في C++
- كومة
- متجر مجاني
- كومة
- عالمية وثابتة
- مقدار ثابت
يرى هنا للحصول على إجابة مفصلة لسؤالك
يعتمد ذلك على النظام الأساسي والمترجم الذي تستخدمه.يتم تخزين بعض المترجمين مباشرة في مقطع التعليمات البرمجية.لا يمكن دائمًا الوصول إلى المتغيرات الثابتة إلا من خلال وحدة الترجمة الحالية ولا يتم تصدير الأسماء، وبالتالي لا يحدث تضارب الأسماء مطلقًا.
ستنتقل البيانات المعلنة في وحدة التحويل البرمجي إلى ملف .BSS أو بيانات إخراج تلك الملفات.البيانات التي تمت تهيئتها في BSS، لم تتم تهيئتها في DATA.
يأتي الفرق بين البيانات الثابتة والعالمية في تضمين معلومات الرمز في الملف.يميل المترجمون إلى تضمين معلومات الرمز ولكنهم يقومون فقط بوضع علامة على المعلومات العامة على هذا النحو.
الرابط يحترم هذه المعلومات.يتم تجاهل معلومات الرمز الخاصة بالمتغيرات الثابتة أو تشويهها بحيث لا يزال من الممكن الرجوع إلى المتغيرات الثابتة بطريقة ما (مع خيارات التصحيح أو الرمز).وفي كلتا الحالتين لا يمكن أن تتأثر وحدات الترجمة حيث يقوم الرابط بحل المراجع المحلية أولاً.
متغير ثابت مخزن في مقطع البيانات أو مقطع الكود كما ذكرنا من قبل.
يمكنك التأكد من أنه لن يتم تخصيصه على المكدس أو الكومة.
لا يوجد خطر الاصطدام منذ ذلك الحين static
تحدد الكلمة الرئيسية نطاق المتغير ليكون ملفًا أو وظيفة، وفي حالة الاصطدام يوجد مترجم/رابط لتحذيرك منه.
لطيف مثال
حسنًا، هذا السؤال قديم بعض الشيء، ولكن بما أنه لا أحد يشير إلى أي معلومات مفيدة:تحقق من المنشور بواسطة 'mohit12379' الذي يشرح مخزن المتغيرات الثابتة بنفس الاسم في جدول الرموز:http://www.geekinterview.com/question_details/24745
لقد قمت بتجربتها باستخدام objdump و gdb، وهذه هي النتيجة التي حصلت عليها:
(gdb) disas fooTest
Dump of assembler code for function fooTest:
0x000000000040052d <+0>: push %rbp
0x000000000040052e <+1>: mov %rsp,%rbp
0x0000000000400531 <+4>: mov 0x200b09(%rip),%eax # 0x601040 <foo>
0x0000000000400537 <+10>: add $0x1,%eax
0x000000000040053a <+13>: mov %eax,0x200b00(%rip) # 0x601040 <foo>
0x0000000000400540 <+19>: mov 0x200afe(%rip),%eax # 0x601044 <bar.2180>
0x0000000000400546 <+25>: add $0x1,%eax
0x0000000000400549 <+28>: mov %eax,0x200af5(%rip) # 0x601044 <bar.2180>
0x000000000040054f <+34>: mov 0x200aef(%rip),%edx # 0x601044 <bar.2180>
0x0000000000400555 <+40>: mov 0x200ae5(%rip),%eax # 0x601040 <foo>
0x000000000040055b <+46>: mov %eax,%esi
0x000000000040055d <+48>: mov $0x400654,%edi
0x0000000000400562 <+53>: mov $0x0,%eax
0x0000000000400567 <+58>: callq 0x400410 <printf@plt>
0x000000000040056c <+63>: pop %rbp
0x000000000040056d <+64>: retq
End of assembler dump.
(gdb) disas barTest
Dump of assembler code for function barTest:
0x000000000040056e <+0>: push %rbp
0x000000000040056f <+1>: mov %rsp,%rbp
0x0000000000400572 <+4>: mov 0x200ad0(%rip),%eax # 0x601048 <foo>
0x0000000000400578 <+10>: add $0x1,%eax
0x000000000040057b <+13>: mov %eax,0x200ac7(%rip) # 0x601048 <foo>
0x0000000000400581 <+19>: mov 0x200ac5(%rip),%eax # 0x60104c <bar.2180>
0x0000000000400587 <+25>: add $0x1,%eax
0x000000000040058a <+28>: mov %eax,0x200abc(%rip) # 0x60104c <bar.2180>
0x0000000000400590 <+34>: mov 0x200ab6(%rip),%edx # 0x60104c <bar.2180>
0x0000000000400596 <+40>: mov 0x200aac(%rip),%eax # 0x601048 <foo>
0x000000000040059c <+46>: mov %eax,%esi
0x000000000040059e <+48>: mov $0x40065c,%edi
0x00000000004005a3 <+53>: mov $0x0,%eax
0x00000000004005a8 <+58>: callq 0x400410 <printf@plt>
0x00000000004005ad <+63>: pop %rbp
0x00000000004005ae <+64>: retq
End of assembler dump.
هنا نتيجة objdump
Disassembly of section .data:
0000000000601030 <__data_start>:
...
0000000000601038 <__dso_handle>:
...
0000000000601040 <foo>:
601040: 01 00 add %eax,(%rax)
...
0000000000601044 <bar.2180>:
601044: 02 00 add (%rax),%al
...
0000000000601048 <foo>:
601048: 0a 00 or (%rax),%al
...
000000000060104c <bar.2180>:
60104c: 14 00 adc $0x0,%al
وهذا يعني أن المتغيرات الأربعة الخاصة بك موجودة في حدث قسم البيانات بنفس الاسم، ولكن مع إزاحة مختلفة.
قد تعتمد الإجابة بشكل جيد على المترجم، لذلك ربما تريد تعديل سؤالك (أعني، حتى فكرة المقاطع ليست مفوضة من قبل ISO C أو ISO C++).على سبيل المثال، في نظام التشغيل Windows، لا يحمل الملف القابل للتنفيذ أسماء الرموز.سيتم إزاحة أحد "foo" بـ 0x100، وربما الآخر 0x2B0، ويتم تجميع التعليمات البرمجية من كلتا وحدتي الترجمة مع معرفة إزاحات foo "الخاصة بهم".
سيتم تخزينهما بشكل مستقل، ولكن إذا كنت تريد توضيح الأمر للمطورين الآخرين، فقد ترغب في تغليفهما بمساحات الأسماء.
أنت تعرف بالفعل أنه يتم تخزينه في bss (بدء الكتلة بالرمز) ويُشار إليه أيضًا باسم مقطع البيانات غير المهيأ أو في مقطع البيانات الذي تمت تهيئته.
لنأخذ مثالا بسيطا
void main(void)
{
static int i;
}
لم تتم تهيئة المتغير الثابت أعلاه، لذلك ينتقل إلى شريحة البيانات غير المهيأة (bss).
void main(void)
{
static int i=10;
}
وبالطبع تمت تهيئته بحلول 10 بحيث ينتقل إلى شريحة البيانات التي تمت تهيئتها.