مصطفى 36a2

تحدي بسيط يظهر انخفاض مستوى لغة C (تمرير الوسطاء لـ printf)

18 ردود في هذا الموضوع

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

إذا علمت أن الكود التالي :

 #include<cstdio>main(){#include"mostafa36a2.txt" printf("%d %d %d %d\n"); #include"stuff.txt"return 0;}

يقوم بطباعة : 4 3 2 1

ولا شيء آخر .. ولا يظهر أي خطأ ... وتتم ترجمته بكل بساطة وسهولة ...

فما هو محتوى الملفين mostafa36a2.txt والملف stuff.txt

السؤال متاح لمدة أسبوع من الآن ...

بالتوفيق :)

ملاحظة : السؤال كامل ولا يوجد أي خدعة .. برمجة نظامية .. يرجى عدم طرح الأسئلة ..بل تقديم اجابات مقبولة 

3

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه

الملف cstdio من مكتبات لغة cpp و ليس c و على افتراض انك تقصد stdio.h فالكود كالتالي:
 

#include<stdio.h>main(){#include"mostafa36a2.txt" printf("%d %d %d %d\n"); #include"stuff.txt"return 0;}

الملف mostafa36a2.txt يحتوى على:

#define printf(X) printf("1 2 3 4");

و الملف stuff.txt فارغ.
 
 
سؤال جيد و يظهر مدي معرفة المبرمج بقواعد preproccessor.
 
 
و الله ولي التوفيق

تم تعديل بواسطه C++er
4

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه

السلام عليكم ..

 

لملف mostafa36a2.txt يحتوى على:

int a = 1 , b = 2 , c = 3 , d = 4;

و الملف stuff.txt فارغ.

و التفسير : 

Behaviour of printf when printing a %d without supplying variable name  رابط

2

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه
Behaviour of printf when printing a %d without supplying variable name

هذا undefined behavior.

 

 

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

1

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه

بالنسبة لجزء المعالجة الأولية، فكل مايقوم به المصرف عند استخدام #include أنه سيأخذ محتويات الملف الرأسي وسيضيفها للملف .c، مثلاً هنا:

// file.cint printf(const char *p, ...);void func(void) {  #include "mostafa36a2.txt"   printf("%d %d %d %d\n");}int main(void) {  func();  return 0;}
// mostafa36a2.txtint a = 1, b = 2, c = 3, d = 4;

لمشاهدة الملف بعد مرحلة المعالجة الأولية، يمكنك استخدام الخيار E في msvc أو E أيضاً في gcc. ناتج الملف في msvc:

>cl file.c /EMicrosoft (R) 32-bit C/C++ Optimizing Compiler Version 14.00.50727.762 for 80x86Copyright (C) Microsoft Corporation.  All rights reserved.file.c#line 1 "file.c"int printf(const char *p, ...);void func(void) {  #line 1 "c:\\users\\barakat\\desktop\\playground\\mostafa36a2.txt"int a = 1, b = 2, c = 3, d = 4;#line 8 "file.c"  printf("%d %d %d %d\n");}int main(void) {  func();  return 0;}>

محتويات الملف mostafa36a2.txt نسخت للملف الناتج قبل البدء في بناء الملف، لهذا لم استخدم #include <stdio.h> لأن الناتج سيكون كبير. التوجيهات #line، خاصة بالمصرف وتختلف بين مصرف وآخر لكن msvc يستخدمها لتحديد مكان الخطأ عند البدء في بناء الملف.

 

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

 

هنا هذه الدالة:

void func(void) {  int a = 1, b = 2, c = 3, d = 4;  printf("%d %d %d %d\n", a, b, c, d);}

ستولد:

push    ebpmov     ebp, espsub     esp, 10hmov     [ebp-4] , 1mov     [ebp-8] , 2mov     [ebp-16], 3mov     [ebp-12], 4mov     eax, [ebp-12]push    eaxmov     ecx, [ebp-16]push    ecxmov     edx, [ebp-8]push    edxmov     eax, [ebp-4]push    eaxpush    offset aDDDD    ; "%d %d %d %d\n"call    _printfadd     esp, 14hmov     esp, ebppop     ebpretn

وهذه الدالة:

void func(void) {  int a = 1, b = 2, c = 3, d = 4;  printf("%d %d %d %d\n");}

ستولد:

push    ebpmov     ebp, espsub     esp, 10hmov     [ebp-4] , 1mov     [ebp-8] , 2mov     [ebp-12], 3mov     [ebp-16], 4push    offset format   ; "%d %d %d %d\n"call    _printfadd     esp, 4mov     esp, ebppop     ebpretn

ستلاحظ أن المتغيرات متواجدة في المكدس في أماكنها الصحيحة، قريباً. عند تشغيل البرنامج ووضع نقاط توقف عند أماكن استدعاء printf لو نظرت لمحتويات المكدس في الدالة الأولى، السليمة:

نسخة من قيم المتغيرات المحلية الممررة للدالة0012FF0C  0040C000  أول متغير ; عنوان "%d %d %d %d\n"0012FF10  00000001  ثاني متغير0012FF14  00000002  ثالث متغير0012FF18  00000003  رابع متغير0012FF1C  00000004  خامس متغير----------------------المتغيرات المحلية0012FF20  000000030012FF24  000000040012FF28  000000020012FF2C  00000001

في النسخة الثانية:

0012FF1C  0040C000  أول متغير ; عنوان "%d %d %d %d\n"0012FF20  00000003  ثاني متغير0012FF24  00000004  ثالث متغير0012FF28  00000002  رابع متغير0012FF2C  00000001  خامس متغير

لاحظ التشابة في محتويات المكدس.

 

كما قلت هذه صدفة وطريقة خاطئة. المصرف قد لايستخدم المكدس للمتغيرات المحلية أو يغير ترتيبها أو تباعدها، كما يفعل gcc، فلايوجد قانون في معايير c/c++ يحدد الطريقة التي تمرر فيها المغيرات وترتيبها. وقد يستخدم المصرف مسجلات المعالج في تخزين المتغيرات المحلية وأيضاً قد يحذف المتغير المتغيرات المحلية لأنه يرى أنها لم تستخدم، مثلاً عند استخدام خيار التحسين O1، تصغير الكود، سيولد:

push    offset aDDDD    ; "%d %d %d %d\n"call    sub_401014pop     ecxretn

جسم الدالة اختفى والمتغيرات المحلية اختفت، ولم تعد تعمل هذه الحيلة، رأى المصرف أنها لم تستخدم لذا حذفها. الشيء الوحيد الذي سيتواجد في المكدس هو عنوان النص "%d %d %d %d\n"، وستقرأ printf قيم عشوائية من المكدس.

 

مصرف محترم مثل gcc سيكتشف أخطاء الـformat وسيحذرك من أن printf لم تمرر لها متغيرات كافية:

>gcc -Wall file.cfile.c: In function 'func':file.c:8:3: warning: format '%d' expects a matching 'int' argument [-Wformat]file.c:8:3: warning: format '%d' expects a matching 'int' argument [-Wformat]file.c:8:3: warning: format '%d' expects a matching 'int' argument [-Wformat]file.c:8:3: warning: format '%d' expects a matching 'int' argument [-Wformat]file.c:6:28: warning: unused variable 'd' [-Wunused-variable]file.c:6:21: warning: unused variable 'c' [-Wunused-variable]file.c:6:14: warning: unused variable 'b' [-Wunused-variable]file.c:6:7: warning: unused variable 'a' [-Wunused-variable]>
تم تعديل بواسطه Mr.B
4

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه
كما قلت هذه صدفة وطريقة خاطئة. المصرف قد لايستخدم المكدس للمتغيرات المحلية أو يغير ترتيبها أو تباعدها.

أغلب المترجمات فى حالة استخدام variable arguments يتم إستخدام الـ stack و ليس registers و ذلك حتى يمكنك استخدام va_start و اخواتها. ايضا احيانا يمكنك معرفة مكان وضع معاملات دالة عن طريق name mangling الخاص بها.

 

و حتى لو افترضنا بقاء المتغيرات داخل محتوى الدالة حتى مع تفعيل التحسينات - بإستخدام volatile - حينها ايضا ترتيب دفع المتغيرات داخل الـ stack يعتمد على المترجم، و بإفتراض ان كافة هذه العمليات ستكون صحيحة داخل بيئة 64بت حجم كل عنصر داخل الـ stack يساوى 8 بايت و انت تستخدم 4 بايت و حيث ان ترتيب دفع المعاملات قد يختلف عن ترتيب دفع المتغيرات حينها تصبح عائلة va_* بلا قيمة.

 

 

 

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

تم تعديل بواسطه C++er
0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه

جزاكم الله خيرا على النقاش المفيد .

0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه

جواب الأخ C++er خاطئ لأن المطلوب طباعة الخرج دون زيادة أو نقصان .. أما هذا الحل فسيطبع آخر أربع أرقام في الstack بعد طباعة المطلوب

وباقي الأجوبة ذات سلوك غير معرف (حسب المصرف ) وهذا مرفوض

الحل الصحيح .. صحيح في أي مصرف ويعطي المطلوب لا أكثر ولا أقل ... كما أنه يبين إلى أي مدى لغة C منخفضة المستوى :)

الحل برمجي بحت ليس له علاقة بالبيئة

بالتوفيق

تم تعديل بواسطه مصطفى 36a2
0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه

هذه محاولتي بال (gcc)

الملف mostafa36a2.txt يحتوي على

asm("pushl $4 \n\t"    "pushl $3 \n\t "     "pushl $2 \n\t "    "pushl $1 \n\t "    "pushl $0 " );

و الملف الثاني فارغ

تم تعديل بواسطه fouad2008
0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه

و هذا حل اخر

الملف الاول يحتوي

#define printf(n) printf("1 2 3 4");

الملف الثاني فارغ

0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه

أخي فؤاد حلّك هو الأقرب للصحة حتى الآن ... ولكن يبقى الحل الذي لا يعتمد على المصرّف فحلّك بعتمد على gcc  كما أنه يسبب "كركبة"  بعد استدعاء تابع printf وذلك بسبب esp
والملف الثاني غير فارغ بسبب ذلك :)

______________________

أما الحل الثاني فهو خاطئ لأنه سيتم طباعة أمور أخرى غير الخرج المطلوب كما ذكرت في ردي السابق

تم تعديل بواسطه مصطفى 36a2
0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه

الحل الثاني صحيح , سيطبع 4 3 2 1

مجرب ب gcc و VC

0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه

الحل الثاني يطبع 4 أرقام إضافية غير المطلوبة ...
وهذا مثال عن الخرج :

1 2 3 40 134513883 -1215778828 134513872

وهو نفس خطأ C++er

 

أما الحل الأول لك ... فهو صحيح  من أجل gcc وهذه محسوبة لك ..

ولكن الحل الذي كان في بالي عندما وضعت الحل هو التالي :

#include <stdio.h>int main(void) {_asm{push 4push 3push 2push 1}    printf("%d %d %d %d");_asm{add esp,16}    return 0;}

ولكن استخدام asm  يختلف بين المصرفات .. (عذرا لذلك ) ...

ولكن هذا هو الحل (الذي كان في بالي )
شكرا لمشاركتكم ... وعذرا لإزعاجكم :)

0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه

مصطفى 36a2: حلك غير صحيح حاول تجربته على نظام PowerPC و حتى مع Mac for Intel سينتج اخطاء إذا استخدمت إعدادات xcode الإفتراضية.

 

الهدف الأساسي من استخدام لغة السي هو كتابة الكود مرة واحدة ليعمل علي اكثر من نظام تشغيل بأقل تعديلات ممكنة.

 

ملحوظة: انت لم تحدد انك تهدف ويندوز :ph34r:.

 

 

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

1

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه

(رغم أني لا أفقه شيئا في ما ذكرته أخي C++er ) ولكن نعم ربما الحل غير شامل ولكن الفكرة وصلت .. وهي تمرير الوسطاء بشكل منفرد باستخدام الpush في الstack .. وهذا ما أكنت أرنو إليه

ولكن للأسف لا أظن أن برامج ++C يمكن تعميمها ولا حتى بسهولة متوسطة ..يحتاج الأمر دوماً إلى كثير من التعديلات من مصرف إلى آخر فما بالك من نظام تشغيل لآخر ..

عموماً اعذرني فلم أعلم بأن الموضوع كبير لهذا الحد :p
والسلام عليكم ورحمة الله :)

0

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه

مصطفى 36a2: حلك غير صحيح حاول تجربته على نظام PowerPC و حتى مع Mac for Intel سينتج اخطاء إذا استخدمت إعدادات xcode الإفتراضية.

 

الهدف الأساسي من استخدام لغة السي هو كتابة الكود مرة واحدة ليعمل علي اكثر من نظام تشغيل بأقل تعديلات ممكنة.

 

ملحوظة: انت لم تحدد انك تهدف ويندوز :ph34r:.

 

 

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

أظن الجواب التالي سيكون، شافي إن شاء الله

#include <cstdio>void GetESP (unsigned int **ptr){	*ptr = (unsigned int *) &ptr;	return;}int main (void){	unsigned int *iPtr = NULL;	GetESP (&iPtr);	*(iPtr + 1)	= 0x1;	*(iPtr + 2)	= 0x2;	*(iPtr + 3)	= 0x3;	*(iPtr + 4)	= 0x4;	printf ("%08x %08x %08x %08x");	getchar ();	return 0;}
1

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه

ما شاء الله عليك :) ... عصرت مخّي حتى فهمت الكود ..
هل ما فهمته صحيح : انت تدخل إلى خانات في الstack لا يحق الدخول إليها وهي أسفل المتحول المحجوز ثم تسند القيم إليها .. أي أن الدخول غير شرعي إلا أنه لا يؤثر على سير البرنامج طالما أن هذا هو الشيء الوحيد ..

هل هذا صحيح ؟

___________________تعديل ________________

فهمت الكود الآن .. ويبدو أنه إبداعي أكثر مما توقّعت

ففكرة الكود هي الحصول على عنوان الوسطاء التي تمرر للدوال ثم استخدام هذا العنوان (والذي هو esp ) في اسناد فيم جديدة للمكدّس وهذا كود مماثل ... تعبيراً عن فهمي للكود الأصل:

#include <cstdio>unsigned int *x ;void GetESP (unsigned int a){    //a has been pushed .. so it has the required adderss    x=&a;}int main (void){    GetESP(1);    x[1]    = 1;    x[2]    = 2;    x[3]    = 3;    x[4]    = 4;    printf ("%d %d %d %d");    getchar ();    return 0;}

جزاكم الله خيرا :)

تم تعديل بواسطه مصطفى 36a2
1

شارك هذا الرد


رابط المشاركة
شارك الرد من خلال المواقع ادناه
زوار
This topic is now closed to further replies.

  • يستعرض القسم حالياً   0 members

    لا يوجد أعضاء مسجلين يشاهدون هذه الصفحة .