سؤال

أنا أستخدم Linux. أحاول كتابة برنامج في C من شأنه طباعة سلسلة للخلف. ها هو رمزتي:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main (){
    char string[100];
    printf ("Enter string:\n");
    gets (string);
    int length = strlen (string)-1;
    for (length = length; length>=0; length--){
        puts (string[length]);
    }
}

وهنا الخطأ:

a.c:10: warning: passing argument 1 of ‘puts’ makes pointer from integer without a cast
/usr/include/stdio.h:668: note: expected ‘const char *’ but argument is of type ‘char’
/tmp/cc5rpeG7.o: In function `main':
a.c:(.text+0x29): warning: the `gets' function is dangerous and should not be used.

ماذا علي أن أفعل؟

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

المحلول

أولاً:

ابدا، ابدا، ابدا، ابدا، ابدا استعمال gets(); ؛ هو - هي إرادة تقديم نقطة فشل في الكود الخاص بك. لا توجد طريقة لتقول gets() كم هو كبير المخزن المؤقت المستهدف ، لذلك إذا قمت بتمرير حجم المخزن المؤقت ليحمل 10 أحرف وهناك 100 حرف في دفق الإدخال ، gets() سوف يقوم بسعادة بتخزين تلك السعة 90 حرفًا إضافية في الذاكرة بعد نهاية المخزن المؤقت ، مما قد يتجول في شيء مهم. التجاوزات العازلة هي استغلال برامج ضارة سهلة. استغلت دودة موريس على وجه التحديد أ gets() اتصل في SendMail.

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

char string[100]; 
char *newline;
printf("Enter a string: ");
fflush(stdout);
fgets(string, sizeof string, stdin);
newline = strchr(buffer, '\n');      // search for the newline character
if (newline)                         // if it's present
  *newline = 0;                      // set it to zero

الآن هذا بعيد عن الطريق ...

خطأك يأتي من حقيقة أن puts() يتوقع حجة من النوع char *, ، لكنك تمر بحجة من النوع char, ، ومن هنا جاءت رسالة "مؤشر من عدد صحيح بدون ملاءمة" (char هو نوع متكامل). لكتابة حرف واحد إلى stdout ، استخدم putchar() أو fputc().

نصائح أخرى

ننسى أن الوظيفة gets() موجود - إنه قاتل. يستخدم fgets() بدلاً من ذلك (ولكن لاحظ أنه لا يزيل الخط الجديد في نهاية الخط).

تريد وضع حرف واحد في وقت واحد: استخدم putchar() لكتابته إلى stdout. لا تنس إضافة سطر جديد إلى الإخراج بعد الحلقة.

ايضا، for (length = length; length >= 0; length--) ليس الاصطلاحي C. استخدم واحدة من:

  • for ( ; length >= 0; length--)
  • for (length = strlen(string) - 1; length >= 0; length--)
  • for (int length = strlen(string) - 1; length >= 0; length--)

يستخدم البديل الأخير ميزة تمت إضافتها إلى C99 (والتي كانت متوفرة في C ++ قبل فترة طويلة).

أيضا ، يمكننا مناقشة ما إذا كان length هو الاسم المناسب للمتغير. سيكون من الأفضل إعادة تسمية i أو pos أو شيء مشابه لأنه ، على الرغم من تهيئته إلى طول المدخلات ، إلا أنه يستخدم فعليًا كمؤشر صفيف ، وليس طول أي شيء.

شخصي: لا تضع مساحة بين اسم الوظيفة وقائمة المعلمات الخاصة بها. لا يفعل الآباء المؤسسون لـ C ذلك - ولا يجب عليك.


لماذا تحصل () قاتلة؟

دودة الإنترنت الأولى - موريس دودة من عام 1988 - استغل fingerd البرنامج المستخدم gets() بدلاً من fgets(). منذ ذلك الحين ، تم تحطيم العديد من البرامج لأنها استخدمت gets() و لا fgets() أو بديل آخر.

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

إذا قام شخص ما بتكوين 150 حرفًا من إدخال برنامج المثال ، فعندئذٍ gets() سيخزن 150 حرفًا في المصفوفة التي يبلغ طولها 100. هذا لا يؤدي أبدًا إلى السعادة - عادة ما يؤدي إلى تفريغ أساسي ، ولكن مع المدخلات المختارة بعناية - غالبًا ما يتم إنشاؤها بواسطة نص Perl أو Python - ربما يمكنك الحصول على البرنامج لتنفيذ التعسفي رمز آخر. هذا يهم حقًا إذا كان المستخدم يديره المستخدم مع "امتيازات مرتفعة".

صدفة، gets() من المحتمل أن تتم إزالتها من مكتبة C القياسية في الإصدار التالي (C1X - انظر N1494 من WG14). لن تتلاشى من مكتبات C الفعلية لفترة طويلة حتى الآن (20 عامًا؟) ، ولكن يجب استبدالها بهذا التنفيذ (أو شيء مشابه):

#undef NDEBUG
#include <assert.h>
char *gets(char *buffer)
{
    assert("Probability of using gets() safely" == 0);
}

التفاصيل البسيطة الأخرى ، التي تمت مناقشتها جزئيًا بموجب التعليقات على السؤال الرئيسي.

الكود الموضح بوضوح لـ C99 ؛ إعلان length جزء من خلال الوظيفة غير صالح في C89. بالنظر إلى ذلك ، لا بأس في main() وظيفة لا لإرجاع قيمة بشكل صريح ، لأن معيار C99 يتبع الرصاص المعيار C ++ ويسمح لك بحذف العائد من main() والتأثير هو نفسه return(0); أو return 0; في نهايةالمطاف.

على هذا النحو ، لا يمكن أن يخطئ بشكل صارم البرنامج في هذا السؤال لعدم وجود return في نهايةالمطاف. ومع ذلك ، فإنني أعتبر أن أحد القرارات الموحدة الأكثر غرابة ، وسأفضل ذلك كثيرًا إذا تركت المعايير هذا الحكم - أو فعلت شيئًا أكثر راديكالية مثل السماح بالوجود في كل مكان ولكنه خاطئ void main() ملاحظة أنه عند عودة التحكم من ذلك ، فإن النتيجة هي أن حالة النجاح تُعاد إلى البيئة. لا يستحق القتال للحصول على هذا الجانب من المعيار الذي تغير - للأسف - ولكن كقرار أسلوب شخصي ، لا أستفيد من الترخيص الممنوح لحذف النهائي return من main(). إذا كان على الرمز العمل مع مجمعين C89 ، فيجب أن يكون لديه صريح return 0; في النهاية (ولكن بعد ذلك إعلان length يجب إصلاحه أيضًا).

يمكنك أيضًا استخدام العودية للقيام بذلك. أعتقد أنه يبدو أجمل ثم عند استخدام حلقة.

ما عليك سوى استدعاء الطريقة باستخدام السلسلة الخاصة بك ، وقبل طباعة Char في الطريقة ، اتصل بالطريقة مرة أخرى بنفس السلسلة ، مطروحًا منها The First Char.

سيؤدي هذا إلى طباعة سلسلة بترتيب عكسي.

يجب أن تستخدم putchar بدلاً من puts

لذلك هذه الحلقة:

for (length = length; length>=0; length--){
    puts (string[length]);
}

سوف يكون:

for (length = length; length>=0; length--){
    putchar (string[length]);
}

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

يستخدم putc أو putchar, ، كما puts تم تحديده لأخذ ملف char* وأنت تطعمها char.

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