الفريق العربي للبرمجةأرشيف المنتديات · 2000 – 2023
نسخة أرشيفية للقراءة فقط — التسجيل والمشاركة مغلقان، والمحتوى محفوظ كما كان.

خواص const

بدأه مصطفى 36a2 في 27 أكتوبر 2013 · 9 رد · 1,021 مشاهدة · في لغة C و ++C
مشاركة: واتساب X فيسبوك تيليجرام
#1 صاحب الموضوع

السلام عليكم ورحمة الله وبركاته ...

أرغب في هذا الموضوع أن نتحدث عن const  ..

وإليكم السؤال التالي :

أولا : الكود :

const int t=2;
int x=t*t*t*t*t*t+t+t+t+t+t+t+t+(2*t*t*t+3*t*t+t)/6;

والمطلوب .. حساب عدد تعليمات الأسمبلي التي سينتجها المصرّف للإسناد السابق :) ... (أستخدم MVC )

 

بالمناسبة  : يُمنع تجريب الكود :) والإجابة تُحسب بالورقة والقلم ..

بالتوفيق :)

تم تعديل هذه المشاركة بواسطة مصطفى 36a2 في 27 أكتوبر 2013 في 23:47

#2

حسنا سأجيب عن السؤال ..

لو قمنا بتعريف t بدون استخدام const ...

قسيقوم المصرف بتحويل هذا الكود :

int t=2;

إلى

mov         dword ptr [t],2

ثم سيحوّل هذا الكود :

int x=t*t*t*t*t*t+t+t+t+t+t+t+t+(2*t*t*t+3*t*t+t)/6;

إلى كل هذا الكود

mov         ecx,dword ptr [t]
imul        ecx,dword ptr [t]
imul        ecx,dword ptr [t]
imul        ecx,dword ptr [t]
imul        ecx,dword ptr [t]
imul        ecx,dword ptr [t]
add         ecx,dword ptr [t]
add         ecx,dword ptr [t]
add         ecx,dword ptr [t]
add         ecx,dword ptr [t]
add         ecx,dword ptr [t]
add         ecx,dword ptr [t]
add         ecx,dword ptr [t]
mov         edx,dword ptr [t]
shl         edx,1
imul        edx,dword ptr [t]
imul        edx,dword ptr [t]
mov         eax,dword ptr [t]
imul        eax,eax,3
imul        eax,dword ptr [t]
add         edx,dword ptr [t]
add         eax,edx
cdq              
mov         esi,6
idiv        eax,esi
add         ecx,eax
mov         dword ptr [x],ecx

 

ولكن لو قمنا بتعريف t كثابت const

فسيوفّر المصرّف Compiler  على البرنامج عناء حساب الناتج في كل مرة تشغيل أي أثناء Runtime..  وستتفاجأ إذا علمت أن الكود المولّد سيكون :

mov         dword ptr [x],53h

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

 

والله ولي التوفيق

#3

السلام عليكم ورحمة الله وبركاته


 


اخي مصطفى


 


اين استطيع ان اجد شرح لاوامر الاسمبلي التي يصدرها المترجم compiler


 


والله يعطيك العافية على هذا الموضوع


لا اله الا الله محمد رسول الله

#4

أهلا بك أخي :)

بالنسبة لي .. فأنا أستعمل Microsoft Visual C++ والذي يسمح برؤية تعليمات الأسمبلي التي يولّدها المترجم كما يلي :

من الشريط أعلاه اختر Debug ثم اختر Step Into  أو بالاختصار F11

والآن يمكنك تتبع البرنامج خطوة خطوة .. بلغة C

ولرؤية الكود بالأسمبلي نستخدم Disassembly  وفي نسخة 2008 نختارها كما يلي : debug > windows > disassembly

ثم يمكننا رؤية تعليمات الأسمبلي أسفل كل تعلمية C ..

 

بشكل عام عليك البحث عن Disassembler مثلا ollydbg  ويمكنك أن تتتبّع أي برنامج كيف يعمل تعليمة تعليمة ..

أما إذا كان لديك الكود .. فيمكنك استخدام الطريقة السابقة .. ولكن لا أعرف بالنسبة لبيئات أخرى ...

أرجو من الأخوة الكرام أن يتابعوا الإجابة لتتكامل ..

 

والله ولي التوفيق

#5

اى مترجم يقوم بخطوات ثابتة

 

1- preprocess

2- compile

3- assemble

4- link

 

اذا يمكنك فصل هذه الخطوات والتوقف عند الخطوة الثانية

 

مثلا GCC يدعم الخيار S- والذى يقوم بالترجمة الى ASSEMBLY فقط

 

 

g++ -Wall -S -o main.asm main.cpp

 

 

هنا ينتج الملف main.asm وبه تعليمات لغة التجميع

 

 

main.cpp

#include <iostream>

using namespace std;

int main()
{
    cout << "Hello world!" << endl;
    return 0;
}

main.asm

	.file	"main.cpp"
	.section .rdata,"dr"
__ZStL19piecewise_construct:
	.space 1
.lcomm __ZStL8__ioinit,1,1
	.def	___main;	.scl	2;	.type	32;	.endef
LC0:
	.ascii "Hello world!\0"
	.text
	.globl	_main
	.def	_main;	.scl	2;	.type	32;	.endef
_main:
LFB1137:
	.cfi_startproc
	leal	4(%esp), %ecx
	.cfi_def_cfa 1, 0
	andl	$-16, %esp
	pushl	-4(%ecx)
	pushl	%ebp
	.cfi_escape 0x10,0x5,0x2,0x75,0
	movl	%esp, %ebp
	pushl	%ecx
	.cfi_escape 0xf,0x3,0x75,0x7c,0x6
	subl	$20, %esp
	call	___main
	movl	$LC0, 4(%esp)
	movl	$__ZSt4cout, (%esp)
	call	__ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
	movl	$__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, (%esp)
	movl	%eax, %ecx
	call	__ZNSolsEPFRSoS_E
	subl	$4, %esp
	movl	$0, %eax
	movl	-4(%ebp), %ecx
	.cfi_def_cfa 1, 0
	leave
	.cfi_restore 5
	leal	-4(%ecx), %esp
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
LFE1137:
	.def	___tcf_0;	.scl	3;	.type	32;	.endef
___tcf_0:
LFB1165:
	.cfi_startproc
	pushl	%ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	movl	%esp, %ebp
	.cfi_def_cfa_register 5
	subl	$8, %esp
	movl	$__ZStL8__ioinit, %ecx
	call	__ZNSt8ios_base4InitD1Ev
	leave
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
LFE1165:
	.def	__Z41__static_initialization_and_destruction_0ii;	.scl	3;	.type	32;	.endef
__Z41__static_initialization_and_destruction_0ii:
LFB1164:
	.cfi_startproc
	pushl	%ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	movl	%esp, %ebp
	.cfi_def_cfa_register 5
	subl	$24, %esp
	cmpl	$1, 8(%ebp)
	jne	L4
	cmpl	$65535, 12(%ebp)
	jne	L4
	movl	$__ZStL8__ioinit, %ecx
	call	__ZNSt8ios_base4InitC1Ev
	movl	$___tcf_0, (%esp)
	call	_atexit
L4:
	leave
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
LFE1164:
	.def	__GLOBAL__sub_I_main;	.scl	3;	.type	32;	.endef
__GLOBAL__sub_I_main:
LFB1166:
	.cfi_startproc
	pushl	%ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	movl	%esp, %ebp
	.cfi_def_cfa_register 5
	subl	$24, %esp
	movl	$65535, 4(%esp)
	movl	$1, (%esp)
	call	__Z41__static_initialization_and_destruction_0ii
	leave
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
LFE1166:
	.section	.ctors,"w"
	.align 4
	.long	__GLOBAL__sub_I_main
	.ident	"GCC: (GNU) 4.8.0"
	.def	__ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc;	.scl	2;	.type	32;	.endef
	.def	__ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_;	.scl	2;	.type	32;	.endef
	.def	__ZNSolsEPFRSoS_E;	.scl	2;	.type	32;	.endef
	.def	__ZNSt8ios_base4InitD1Ev;	.scl	2;	.type	32;	.endef
	.def	__ZNSt8ios_base4InitC1Ev;	.scl	2;	.type	32;	.endef
	.def	_atexit;	.scl	2;	.type	32;	.endef

تحياتى

2


﴿وَمَا كَانَ اللَّهُ لِيُعَذِّبَهُمْ وَأَنْتَ فِيهِمْ ۚ وَمَا كَانَ اللَّهُ مُعَذِّبَهُمْ وَهُمْ يَسْتَغْفِرُونَ﴾

الأنفال ۳۳                                  
 

#6

شكرا جزيلا لك أخ محمد فتحي .. لا أدري متى سيكون يوم السعد ..يوم أحمّل gcc  ..

_________________________________________
والآن سأذكر الفكرة التي كانت السبب في كتابة هذا الموضوع :

ما رأيك بتغيير القيمة التي يحتفظ بها المتحول الثابت ..!! رغماً عنه :) ... إنها C التي تسمح لك بكل شيء .. ولكن : هناك ولكن :):)

لدينا الكود البسيط التالي :

    int t=5;    int *x=&t;    *x=4;    printf("%i\n",t);

سيتم تغيير قيمة t وسيطبع 4

ولكن لو حاولنا كتابة :

const int t=5;
    int *x=&t;
    *x=4;
    printf("%i\n",t);

فسيصدر الخطأ :

cannot convert from 'const int *' to 'int *'

ولكن :: يمكننا الاختيال وحفظ العنوان في int بدلا من int*  كما يلي :

أولا : المتحول عادي وليس بثابت

int t=5;
    int x=(int)&t;
    *((int*)x)=4;
    printf("%i\n",t);

نعم .. ستتم تغيير قيمة t  وسيطبع 4 !

ولكن :

ماذا لو كان نوع t هو  const ؟؟

const int t=5;
    int x=(int)&t;
    *((int*)x)=4;
    printf("%i\n",t);

طبعا لن يجرؤ على الاعتراض ..فلم نخرق أي قاعدة .. وسيعمل الكود بشكل طبيعي ..... ولكن ::: سيطبع 5 ,وليس 4 كما أردنا !! رغم تأكدنا من عمل الكود !

لماذا ؟

إنها const التي لا علاقة لها بالــRuntime  .. حيث يتم توليد الكود مباشرة وهو يحتوي القيمة 5 قبل أن يتم تشغيل البرنامج حتى ..

أي أن الدالة printf في وقت التشغيل ستأخذ الوسيط 5 وليس t ..

push        5    
push        offset string "%i\n" (110B9CCh)
call        dword ptr [__imp__printf (111057Ch)]

ولكن رغم ذلك فإن المترجم لم يخذلنا وقام بتغيير قيمة t  ولكن متى ؟ أثناء وقت التشغيل .. أي أنها بلا فائدة :)

إلا إذا قمنا باحتيال آخر :D ..:)

 

كيف سنقوم بالاحتيال ... علينا تلخيص ما توصلنا إليه :

1- لا يمكن استخدام أي متحول ثابت أثناء وقت التشغيل لأنه يكون قد استُبدل وانتهى مآله إلى قيمته الثابتة ..

2- يمكننا الوصول إلى خانة الذاكرة التي تحتفظ بقيمة المتحول الثابت وتعديلها وكأنها خانة متحول عادي ( أي لا يهم نوع المتحول طالما وصلنا إلى خانة الذاكرة )

 

أين الثغرة ؟ يمكننا الوصول اخانة الذاكرة والتعديل عليها ولكن لن نستفيد من استخدام المتحول بعد التعديل !

إذاً :

يمكننا استخدام خانة الذاكرة عن طريق متحول آخر :)

اقرأ الكود التالي بتمعّن .. وسعادة :D :)

const int t=5;
    int x=(int)&t;
    *((int*)x)=4;
    printf("%i\n",t);
    int &y=*((int*)x);
    printf("%i\n",y);
    y+=10;
    printf("%i\n",y);

أين تتوقع أن المتحول y موجود ؟ .... إنه في نفس عنوان المتحول الثابت t !!  ... والآن يمكننا استخدام الخانة التي حجزها المتحول الثابت .. في أي متحول وتعديل القيمة ..

والدليل على أن المتحول y  له نفس خانة الذاكرة للمتحول t هو الكود التالي :

const int t=5;
    int x=(int)&t;
    *((int*)x)=4;
    int &y=*((int*)x);

    printf("%i\n%i\n",&y,&t);

لهما نفس العنوان :)

 

النتيجة : إن ثبات المتحول هو ثبات أثناء عملية التصريف Compilation وليس أثناء وقت التشغيل Runtime ...

 

والله ولي التوفيق .

تم تعديل هذه المشاركة بواسطة مصطفى 36a2 في 30 أكتوبر 2013 في 11:13

#7

لهذا كتب Stroustrup هذه النصيحة فى كتابه الاخير

 

اقتباس

 

[4] Macro substitution is almost never necessary in C++. Use const (§7.5), constexpr (§2.2.3,
§10.4), enum or enum class (§8.4) to define manifest constants,

 

يمكنك ان تنتج كود Assembly in intel syntax مستخدما GCC

g++ -Wall -c -o main.o main.cpp
objdump -d --disassembler-options=intel main.o > main.asm

main.asm

main.o:     file format pe-i386


Disassembly of section .text:

00000000 <_main>:
   0:	8d 4c 24 04          	lea    ecx,[esp+0x4]
   4:	83 e4 f0             	and    esp,0xfffffff0
   7:	ff 71 fc             	push   DWORD PTR [ecx-0x4]
   a:	55                   	push   ebp
   b:	89 e5                	mov    ebp,esp
   d:	51                   	push   ecx
   e:	83 ec 14             	sub    esp,0x14
  11:	e8 00 00 00 00       	call   16 <_main+0x16>
  16:	c7 44 24 04 01 00 00 	mov    DWORD PTR [esp+0x4],0x1
  1d:	00 
  1e:	c7 04 24 00 00 00 00 	mov    DWORD PTR [esp],0x0
  25:	e8 00 00 00 00       	call   2a <_main+0x2a>
  2a:	c7 04 24 00 00 00 00 	mov    DWORD PTR [esp],0x0
  31:	89 c1                	mov    ecx,eax
  33:	e8 00 00 00 00       	call   38 <_main+0x38>
  38:	83 ec 04             	sub    esp,0x4
  3b:	b8 00 00 00 00       	mov    eax,0x0
  40:	8b 4d fc             	mov    ecx,DWORD PTR [ebp-0x4]
  43:	c9                   	leave  
  44:	8d 61 fc             	lea    esp,[ecx-0x4]
  47:	c3                   	ret    

00000048 <___tcf_0>:
  48:	55                   	push   ebp
  49:	89 e5                	mov    ebp,esp
  4b:	83 ec 08             	sub    esp,0x8
  4e:	b9 00 00 00 00       	mov    ecx,0x0
  53:	e8 00 00 00 00       	call   58 <___tcf_0+0x10>
  58:	c9                   	leave  
  59:	c3                   	ret    

0000005a <__Z41__static_initialization_and_destruction_0ii>:
  5a:	55                   	push   ebp
  5b:	89 e5                	mov    ebp,esp
  5d:	83 ec 18             	sub    esp,0x18
  60:	83 7d 08 01          	cmp    DWORD PTR [ebp+0x8],0x1
  64:	75 1f                	jne    85 <__Z41__static_initialization_and_destruction_0ii+0x2b>
  66:	81 7d 0c ff ff 00 00 	cmp    DWORD PTR [ebp+0xc],0xffff
  6d:	75 16                	jne    85 <__Z41__static_initialization_and_destruction_0ii+0x2b>
  6f:	b9 00 00 00 00       	mov    ecx,0x0
  74:	e8 00 00 00 00       	call   79 <__Z41__static_initialization_and_destruction_0ii+0x1f>
  79:	c7 04 24 48 00 00 00 	mov    DWORD PTR [esp],0x48
  80:	e8 00 00 00 00       	call   85 <__Z41__static_initialization_and_destruction_0ii+0x2b>
  85:	c9                   	leave  
  86:	c3                   	ret    

00000087 <__GLOBAL__sub_I_main>:
  87:	55                   	push   ebp
  88:	89 e5                	mov    ebp,esp
  8a:	83 ec 18             	sub    esp,0x18
  8d:	c7 44 24 04 ff ff 00 	mov    DWORD PTR [esp+0x4],0xffff
  94:	00 
  95:	c7 04 24 01 00 00 00 	mov    DWORD PTR [esp],0x1
  9c:	e8 b9 ff ff ff       	call   5a <__Z41__static_initialization_and_destruction_0ii>
  a1:	c9                   	leave  
  a2:	c3                   	ret    
  a3:	90                   	nop
1


﴿وَمَا كَانَ اللَّهُ لِيُعَذِّبَهُمْ وَأَنْتَ فِيهِمْ ۚ وَمَا كَانَ اللَّهُ مُعَذِّبَهُمْ وَهُمْ يَسْتَغْفِرُونَ﴾

الأنفال ۳۳                                  
 

#8

مصطفي 362a: السطر التالي لن يعمل فى بيئة 64بت:

int x=(int)&t;

السبب ان int سيكون بمساحة 32بت ام int* سيكون 64بت.

 

و الله ولي التوفيق

مدونتي: C++ Tips and Tricks

#9

فهمت أن  int* مساحتها 64بت ولكن لماذا int أيضاً , كنت أظن أنها معتمدة على النظام أيضاً ويجب أن تكون 64بت ..!

 

إذا كان كلامي خاطئاً فأعتقد أنك تقترح استخدام long أليس كذلك ؟

#10

النوع int لابد ان يستطيع ترميز الأرقام فى مدى معين، هذا المدى يطابق مساحة 32بت.

 

اقتباس
إذا كان كلامي خاطئاً فأعتقد أنك تقترح استخدام long أليس كذلك ؟

لا المفترض ان تستخدم النوع int* لحفظ قيمة المؤشر.

 

 

و الله ولي التوفيق

مدونتي: C++ Tips and Tricks

مواضيع مشابهة