এই সি ফাংশনটি সর্বদা মিথ্যা ফিরিয়ে দেওয়া উচিত, তবে তা হয় না


316

আমি একটি মঞ্চে একটি আকর্ষণীয় প্রশ্নে হোঁচট খেয়েছি অনেক আগে and এবং আমি উত্তরটি জানতে চাই।

নিম্নলিখিত সি ফাংশন বিবেচনা করুন:

f1.c

#include <stdbool.h>

bool f1()
{
    int var1 = 1000;
    int var2 = 2000;
    int var3 = var1 + var2;
    return (var3 == 0) ? true : false;
}

এই সবসময় ফেরত পাঠাবেন falseযেহেতু var3 == 3000mainফাংশন ভালো দেখায়:

main.c

#include <stdio.h>
#include <stdbool.h>

int main()
{
    printf( f1() == true ? "true\n" : "false\n");
    if( f1() )
    {
        printf("executed\n");
    }
    return 0;
}

যেহেতু f1()সর্বদা ফিরে আসা উচিত false, তাই কেউ আশা করে যে প্রোগ্রামটি কেবলমাত্র একটি পর্দায় মিথ্যা মুদ্রণ করবে। তবে এটি সংকলন ও চালনার পরে মৃত্যুদন্ড কার্যকর করা হবে :

$ gcc main.c f1.c -o test
$ ./test
false
executed

তা কেন? এই কোডটি কি কোনও ধরণের অপরিজ্ঞাত আচরণ করে?

দ্রষ্টব্য: আমি এটি দিয়ে সংকলিত gcc (Ubuntu 4.9.2-10ubuntu13) 4.9.2


9
অন্যরা উল্লেখ করেছেন যে আপনার প্রোটোটাইপ দরকার কারণ আপনার ফাংশন পৃথক ফাইলে রয়েছে। তবে আপনি যদি f1()একই ফাইলটিতে অনুলিপি করে থাকেন তবে main()কিছুটা অদ্ভুততা পাবেন: যদিও ()খালি প্যারামিটার তালিকার জন্য সি ++ ব্যবহার করা সঠিক, সি -তে এটি এখনও-সংজ্ঞায়িত পরামিতি তালিকার কোনও ফাংশনের জন্য ব্যবহৃত হয় ( এটি মূলত এর পরে একটি কে & আর-স্টাইলের প্যারামিটার তালিকাটি আশা করে ))। সঠিক সি হতে আপনার কোডটি এতে পরিবর্তন করা উচিত bool f1(void)
uliwitness

1
main()সরলীকৃত যেতে পারে int main() { puts(f1() == true ? "true" : "false"); puts(f1() ? "true" : "false"); return 0; }এটা উত্তম অমিল দেখাতে হবে -।
পেরেক

@ ওলিউইটিস কে ওআরআর 1 ম এড সম্পর্কে কী? (1978) কখন ছিল না void?
Ho1

@uliwitness সেখানে ছিল না trueএবং falseকে & R এর 1 ম ইডি হবে।, তাই না সব সময়ে ধরনের সমস্যা ছিল। এটি কেবল 0 এবং সত্য এবং মিথ্যা জন্য শূন্য নয়। তাই না? আমি জানি না যে প্রোটোটাইপগুলি তখন পাওয়া যায়।
Ho1

1
কেএন্ডআর 1 ম এডন প্রোটোটাইপস (এবং সি স্ট্যান্ডার্ড) এর আগে এক দশকেরও বেশি সময় ধরে (1978 বনাম বইটির 1987 মানের জন্য) - প্রকৃতপক্ষে, কে & আর 1 প্রকাশিত হওয়ার পরে সি ++ (ক্লাস সহ সি) ভবিষ্যতে ছিল। এছাড়াও, C99 এর আগে, কোনও _Boolপ্রকার ছিল না এবং <stdbool.h>শিরোনাম ছিল না ।
জোনাথন লেফলার

উত্তর:


396

অন্যান্য উত্তরে উল্লিখিত হিসাবে, সমস্যাটি হ'ল আপনি gccকোনও সংকলক বিকল্প সেট না করে ব্যবহার করেন। আপনি যদি এটি করেন তবে এটি "gnu90" নামক ডিফল্ট, যা পুরাতন একটি অ-মানক বাস্তবায়ন, 1990 থেকে C90 মান প্রত্যাহার করে।

পুরানো সি 90 স্ট্যান্ডার্ডে সি ভাষায় একটি বড় ত্রুটি ছিল: যদি আপনি কোনও ফাংশন ব্যবহারের আগে প্রোটোটাইপ ঘোষণা না করেন তবে এটি ডিফল্ট হবে int func ()( ( )যার অর্থ "কোনও পরামিতি গ্রহণ করুন")। এটি ফাংশনের কলিং কনভেনশন পরিবর্তন করে func, তবে এটি আসল ফাংশন সংজ্ঞা পরিবর্তন করে না। আকার boolএবং intপৃথক হওয়ার কারণে, ফাংশনটি যখন ডাকা হয় তখন আপনার কোডটি অপরিজ্ঞাত আচরণের ডাক দেয়।

এই বিপজ্জনক বাজে আচরণটি 1999 সালে C99 স্ট্যান্ডার্ড প্রকাশের সাথে সংশোধন করা হয়েছিল। অন্তর্ভুক্ত কর্মের ঘোষণা নিষিদ্ধ ছিল।

দুর্ভাগ্যক্রমে, সংস্করণ 5.xx পর্যন্ত জিসিসি এখনও ডিফল্টরূপে পুরানো সি স্ট্যান্ডার্ড ব্যবহার করে। আপনার কোডটি স্ট্যান্ডার্ড সি ব্যতীত অন্য কোনও হিসাবে সংকলন করতে চান এমন কোনও কারণ নেই তাই আপনার জিসিসিকে স্পষ্টভাবে বলতে হবে যে এটি আপনার কোডটি প্রায় 25+ বছরের পুরনো, অ-স্ট্যান্ডার্ড জিএনইউ ক্রেপের পরিবর্তে আধুনিক সি কোড হিসাবে সংকলন করবে should ।

আপনার প্রোগ্রামটি সর্বদা এইভাবে সংকলন করে সমস্যার সমাধান করুন:

gcc -std=c11 -pedantic-errors -Wall -Wextra
  • -std=c11 এটি (বর্তমান) সি স্ট্যান্ডার্ড (অনানুষ্ঠানিকভাবে সি 11 হিসাবে পরিচিত) অনুযায়ী সংকলনের অর্ধ-হৃদয় চেষ্টা করতে বলে।
  • -pedantic-errors এটিকে উপরোক্ত মনোযোগ সহকারে বলার জন্য এবং যখন আপনি সি কোডটি লঙ্ঘন করে এমন কোনও ভুল কোড লিখেন তখন সংকলক ত্রুটিগুলি দিন।
  • -Wall মানে আমাকে কিছু অতিরিক্ত সতর্কতা দিন যা ভাল হতে পারে।
  • -Wextra মানে আমাকে আরও কিছু অতিরিক্ত সতর্কতা দিন যা ভাল হতে পারে।

19
এই উত্তরটি সামগ্রিকভাবে সঠিক, তবে জটিল প্রোগ্রামগুলির -std=gnu11জন্য প্রত্যাশার চেয়ে অনেক বেশি কাজ করার সম্ভাবনা রয়েছে -std=c11, যে কোনও বা সকলের কারণে: "Gnu" এ উপলব্ধ সি 11 (POSIX, এক্স / ওপেন, ইত্যাদি) এর বাইরে গ্রন্থাগারের কার্যকারিতা প্রয়োজন that প্রসারিত মোডগুলি কিন্তু কঠোর কনফারেন্স মোডে দমন করা; সিস্টেমের শিরোনামগুলিতে বাগগুলি যেগুলি প্রসারিত মোডগুলিতে লুকানো থাকে, যেমন নন-স্ট্যান্ডার্ড টাইপডেফগুলির উপলব্ধতা ধরে নেওয়া; ট্রাইগ্রাফের অনিচ্ছাকৃত ব্যবহার (এই স্ট্যান্ডার্ডের অপব্যবহার "gnu" মোডে অক্ষম করা হয়েছে)।
zwol

5
অনুরূপ কারণে, যখন আমি সাধারণত উচ্চ সতর্কতা স্তরের ব্যবহারকে উত্সাহিত করি আমি সতর্কতা-হ'ল ত্রুটি মোডগুলির ব্যবহারকে সমর্থন করতে পারি না। -pedantic-errorsএর চেয়ে কম ঝামেলা -Werrorহলেও উভয়ই প্রোগ্রামগুলি অপারেটিং সিস্টেমগুলিতে সংকলন করতে ব্যর্থ হতে পারে যা আসল লেখকের পরীক্ষায় অন্তর্ভুক্ত নয়, এমনকি কোনও আসল সমস্যা না থাকলেও।
zwol

7
@ লন্ডিন বিপরীতে, আমি যে দ্বিতীয় সমস্যাটি উল্লেখ করেছি (সিস্টেমের শিরোনামগুলিতে বাগগুলি যা কঠোর কনফারেন্স মোড দ্বারা উদ্ঘাটিত হয়) সর্বব্যাপী ; আমি বিস্তৃত, নিয়মিত পদ্ধতিতে পরীক্ষা করেছি এবং এমন কোনও বহুল ব্যবহৃত অপারেটিং সিস্টেম নেই যা অন্তত একটির মতো বাগ (দু'বছর আগে, যাইহোক) নেই। সি প্রোগ্রামগুলির জন্য কেবলমাত্র সিড 11 এর কার্যকারিতা প্রয়োজন, এতে আরও কোনও সংযোজন না করাও আমার অভিজ্ঞতার নিয়মের চেয়ে ব্যতিক্রম।
zwol

6
@joop আপনি স্ট্যান্ডার্ড সি ব্যবহার করেন তাহলে bool/ _Boolএকটি আপনার সি কোড তারপর আপনি লিখতে পারেন "সি ++ - এসকিউ" আপনি যেখানে অনুমান যে সব তুলনা এবং লজিক্যাল অপারেটর একটি ফিরতি পথ, boolসি মত ++ যদিও তারা একটি ফিরে intসি, ঐতিহাসিক কারণে । এর দুর্দান্ত সুবিধা রয়েছে যে আপনি এই জাতীয় সমস্ত এক্সপ্রেশনগুলির প্রকার সুরক্ষা পরীক্ষা করতে স্ট্যাটিক বিশ্লেষণ সরঞ্জামগুলি ব্যবহার করতে পারেন এবং সংকলনকালে সমস্ত ধরণের বাগগুলি প্রকাশ করতে পারেন। এটি স্ব-ডকুমেন্টিং কোড আকারে অভিপ্রায় প্রকাশ করারও একটি উপায়। এবং কম গুরুত্বপূর্ণ, এটি র‌্যামের কয়েকটি বাইটও সঞ্চয় করে।
লন্ডিন

7
মনে রাখবেন যে C99 এ বেশিরভাগ নতুন স্টাফ সেই 25+ বছরের পুরানো জিএনইউ ক্রেপ থেকে এসেছে।
শাহবাজ

141

f1()মেইন.সি.-এর জন্য আপনার কাছে প্রোটোটাইপ ঘোষণা করা নেই , সুতরাং এটি স্পষ্টভাবে সংজ্ঞায়িত করা হয়েছে int f1(), অর্থাত এটি এমন একটি ফাংশন যা অজানা সংখ্যক আর্গুমেন্ট গ্রহণ করে এবং একটি প্রদান করে int

যদি intএবং boolবিভিন্ন আকারের হয়, এর ফলে অপরিবর্তিত আচরণ হবে । উদাহরণস্বরূপ, আমার মেশিনে, int4 বাইট এবং boolএক বাইট। যেহেতু ফাংশনটি ফিরে আসার জন্য সংজ্ঞায়িত করা হয়েছে bool, এটি ফিরে আসার পরে এটি স্ট্যাকের উপর একটি বাইট রাখে। তবে, যেহেতু এটি স্পষ্টভাবে মেইন.সি. থেকে ফিরে আসার ঘোষণা করেছেint , কলিং ফাংশন স্ট্যাক থেকে 4 বাইট পড়ার চেষ্টা করবে।

জিসিসিতে ডিফল্ট সংকলক বিকল্পগুলি আপনাকে বলবে না যে এটি এটি করছে। তবে আপনি যদি সংকলন করেন তবে আপনি এটি -Wall -Wextraপাবেন:

main.c: In function ‘main’:
main.c:6: warning: implicit declaration of function ‘f1’

এটি ঠিক করতে f1, পূর্বে main.c- এর জন্য একটি ঘোষণা যুক্ত করুন main:

bool f1(void);

দ্রষ্টব্য তালিকাটি সুস্পষ্টভাবে সেট করা আছে void, যা সংকলকটি বলে যে ফাংশনটি কোনও আর্গুমেন্ট নেয় না, খালি প্যারামিটার তালিকার বিপরীতে যার অর্থ অজানা সংখ্যক আর্গুমেন্ট। f1এটিকে প্রতিফলিত করতে f1.c এর সংজ্ঞাটিও পরিবর্তন করা উচিত।


2
আমার প্রকল্পগুলিতে আমি যা কিছু করতাম (যখন আমি এখনও জিসিসি ব্যবহার করি) তা -Werror-implicit-function-declarationজিসিসির বিকল্পগুলিতে যুক্ত হয়েছিল, এইভাবে এটি আর অতীত হয় না। আরও -Werrorসতর্কতা হ'ল সমস্ত সতর্কতাগুলিকে ত্রুটিতে পরিণত করা। সমস্ত সতর্কতা যখন তারা প্রদর্শিত হবে তখন আপনাকে ঠিক করতে বাধ্য করে।
uliwitness

2
আপনার খালি বন্ধনী ব্যবহার করা উচিত নয় কারণ এটি করা একটি অপ্রচলিত বৈশিষ্ট্য। এর অর্থ তারা সি স্ট্যান্ডার্ডের পরবর্তী সংস্করণে এই জাতীয় কোডটি নিষিদ্ধ করতে পারে।
লন্ডিন

1
@ উত্তর সাক্ষী আহ। সি ++ থেকে
আগতদের

প্রত্যাবর্তনের মানটি সাধারণত স্ট্যাকের মধ্যে রাখা হয় না, তবে একটি রেজিস্টারে থাকে। ওউনের উত্তর দেখুন। এছাড়াও, আপনি সাধারণত কখনও স্ট্যাকের মধ্যে একটি বাইট রাখেন না, তবে শব্দের আকারের একাধিক।
rsanchez

জিসিসির নতুন সংস্করণ (5.xx) অতিরিক্ত পতাকা ছাড়াই সতর্কতা দেয়।
ওভারভি

36

আমি লন্ডিনের উত্তরের উত্তরে উল্লিখিত আকার-মিলগুলি দেখতে আসলে এটি আকর্ষণীয় বলে মনে হয়।

আপনি যদি সংকলন করেন তবে আপনি --save-tempsসমাবেশ ফাইলগুলি দেখতে পাবেন যা আপনি দেখতে পারেন। এখানে অংশ কোথায় f1()আছে == 0তুলনা এবং তার মান:

cmpl    $0, -4(%rbp)
sete    %al

রিটার্নিং অংশটি হ'ল sete %al। সি এর এক্স 86 কলিং নিয়মাবলী সালে আগমন 4 বাইট বা ছোট (যার মধ্যে মান intএবং bool) রেজিস্টার মাধ্যমে ফিরিয়ে আনা হয় %eax%alসব চেয়ে কম বাইট %eax। সুতরাং, উপরের 3 বাইটগুলি %eaxএকটি অনিয়ন্ত্রিত অবস্থায় রেখে দেওয়া হয়েছে।

এখন এখানে main():

call    f1
testl   %eax, %eax
je  .L2

কিনা এই চেক পুরো এর %eaxকারণ এটি মনে এটা কোন int পরীক্ষা এর শূন্য।

এতে একটি সুস্পষ্ট ফাংশন ঘোষণার পরিবর্তন যুক্ত main()হয়:

call    f1
testb   %al, %al
je  .L2

যা আমরা চাই


27

এই যেমন একটি আদেশ দিয়ে সংকলন করুন:

gcc -Wall -Wextra -Werror -std=gnu99 -o main.exe main.c

আউটপুট:

main.c: In function 'main':
main.c:14:5: error: implicit declaration of function 'f1' [-Werror=impl
icit-function-declaration]
     printf( f1() == true ? "true\n" : "false\n");
     ^
cc1.exe: all warnings being treated as errors

এই জাতীয় বার্তার সাথে, আপনার এটি জানা উচিত যে এটি সংশোধন করতে হবে।

সম্পাদনা: একটি (এখন মুছে ফেলা) মন্তব্য পড়ার পরে, আমি পতাকাগুলি ছাড়াই আপনার কোডটি সংকলনের চেষ্টা করেছি। ঠিক আছে, এটি আমাকে সংকলক ত্রুটির পরিবর্তে কোনও সংকলক সতর্কতা সহ লিঙ্কার ত্রুটিতে পরিচালিত করেছিল। এবং এই লিঙ্কারের ত্রুটিগুলি বোঝা আরও বেশি কঠিন, তাই যদি -std-gnu99প্রয়োজনীয় না হয় তবে দয়া করে সর্বদা ব্যবহার করার চেষ্টা করুন এটি পাছায় -Wall -Werrorব্যথা অনেকটা বাঁচাতে পারে।

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.