সরাসরি পর্বে যান
পর্ব ০৪ · ৫৬-এর মধ্যে · মডিউল ১

প্রথম C প্রোগ্রাম — hello.c-এর প্রতিটি অংশের মানে

Your First C Program — the Anatomy of main()

Taught in Bangla, this lesson walks line by line through a first C program: #include, main(), braces, semicolons, printf and return 0, who calls main, and how to recognise the most common first-day error messages.

এই পর্বে যা শিখবেন

  • hello.c-এর প্রতিটি শব্দ, ব্র্যাকেট ও চিহ্ন কী কাজ করে
  • প্রোগ্রাম চালালে main-এর আগে ও পরে কী কী ঘটে
  • return 0 কোথায় যায়, আর exit status কীভাবে পড়বেন
  • printf-এর format specifier ও escape sequence-এর মূল ব্যবহার
  • প্রথম দিনের সাধারণ error-গুলো দেখে সঙ্গে সঙ্গে কারণ ধরতে পারা

১ · ছয় লাইনের প্রোগ্রাম, কিন্তু প্রতিটি শব্দের কাজ আছে

আগের পর্বে আমরা দেখেছি, একটি .c ফাইল কোন কোন ধাপ পেরিয়ে চালানো যায় এমন প্রোগ্রামে পরিণত হয়। কিন্তু সেই ফাইলের ভেতরে কী লেখা ছিল, তা আমরা খুলে দেখিনি। আজ সেই কাজটিই করব। এটাই সেই ফাইল:

#include <stdio.h>

int main(void) {
    printf("Hello, Dhaka!\n");
    return 0;
}

মাত্র ছয় লাইন। দেখে মনে হয়, এ আর এমন কী! অনেকে এটি না বুঝেই মুখস্থ করে ফেলেন — “প্রতিটি প্রোগ্রামের শুরুতে এগুলো লিখতে হয়, ব্যস।” কিন্তু এই ছোট ফাইলেই লুকিয়ে আছে C ভাষার প্রায় সব মৌলিক ধারণা: কীভাবে বাইরের কোড আনা হয়, function কী, নির্দেশ কোথায় শুরু ও শেষ হয়, আর প্রোগ্রাম কীভাবে অপারেটিং সিস্টেমের সাথে কথা বলে।

এই ছয় লাইন ঠিকমতো বুঝলে একটি বড় লাভ আছে। হাজার লাইনের যেকোনো C ফাইল খুললেও আপনি একই কাঠামো চিনতে পারবেন। আর AI যখন আপনাকে কোড লিখে দেবে, তখন কোন লাইনটি অপ্রয়োজনীয়, পুরোনো বা ভুল — সেটিও ধরতে পারবেন।

একটি সরকারি আবেদনপত্রের কথা ভাবুন। ওপরে “বরাবর”, তারপর “বিষয়”, তারপর মূল কথা, শেষে “বিনীত” আর স্বাক্ষর। নতুন কেউ ভাবতে পারেন এগুলো শুধুই আনুষ্ঠানিকতা। কিন্তু অফিসের লোকজন ঠিক এই কাঠামো দেখেই বোঝেন চিঠিটি কার কাছে যাবে, কী নিয়ে, আর কে পাঠিয়েছেন। C প্রোগ্রামের কাঠামোও তেমনি — প্রতিটি অংশ কারও না কারও কাজে লাগে: preprocessor, compiler, linker, অপারেটিং সিস্টেম, এমনকি পরে কোড পড়বেন এমন মানুষেরও।

“hello, world” ছাপানোর এই রীতি এসেছে Bell Labs থেকে। ১৯৭৮ সালে প্রকাশিত Brian Kernighan ও Dennis Ritchie-র বই The C Programming Language-এ প্রথম উদাহরণই ছিল এই লেখাটি স্ক্রিনে ছাপানো। তার আগেও Kernighan-এর লেখা Bell Labs-এর ভেতরের টিউটোরিয়ালে এটি ছিল। সেই থেকে প্রায় সব প্রোগ্রামিং ভাষা শেখার প্রথম ধাপ “Hello, World” — একটি ছোট প্রোগ্রাম, যা প্রমাণ করে আপনার পুরো টুলচেইন ঠিকঠাক কাজ করছে।

মূল ধারণা

প্রথম প্রোগ্রামের আসল উদ্দেশ্য লেখা ছাপানো নয়। উদ্দেশ্য হলো যাচাই করা যে লেখা → compile → চালানো → ফল দেখা — এই পুরো পথটি আপনার কম্পিউটারে কাজ করছে। এই পথ একবার খুলে গেলে বাকি সব প্রোগ্রাম এই রাস্তা দিয়েই যাবে।

২ · hello.c-এর শারীরস্থান — লাইন ধরে ধরে

ডাক্তারি পড়ার শুরুতে শিক্ষার্থীরা শরীরের প্রতিটি অংশের নাম ও কাজ শেখেন — একে বলে শারীরস্থান বা anatomy. আমরাও এখন hello.c-এর anatomy দেখব। নিচের ডায়াগ্রামে “পরে” চাপুন। প্রতিটি ধাপে বাঁ দিকের কোডে একটি অংশ সবুজ হবে, আর ডান দিকে দেখাবে সেটি কী, কী ধরনের জিনিস, আর কার কাজে লাগে।

1#include <stdio.h>23int main(void) {4    printf("Hello, Dhaka!\n");5    return 0;6}
এই ধাপের অংশ
অংশ
ধরন
কার কাজে
  1. #include <stdio.h> — বাইরের তালিকা আনা। # দিয়ে শুরু মানে এটি preprocessor-এর জন্য নির্দেশ, C-এর সাধারণ কোড নয়। এটি বলে: “stdio.h নামের header ফাইলের পুরো লেখা এখানে বসাও।” ওই ফাইলে লেখা আছে printf কেমন ফাংশন — কী নেয়, কী ফেরত দেয়। < > মানে “সিস্টেমের নিজস্ব header-এর ফোল্ডারে খোঁজো”। লাইনের শেষে ; নেই, কারণ এটি C statement নয়।
  2. int — ফেরত দেওয়া মানের ধরন। এটি একটি keyword, অর্থ “পূর্ণসংখ্যা” (integer). এখানে এটি বলছে: main কাজ শেষে একটি পূর্ণসংখ্যা ফেরত দেবে। এই সংখ্যাটিই পরে অপারেটিং সিস্টেম পাবে — অংশ ৫-এ বিস্তারিত দেখব।
  3. main — বিশেষ একটি নাম। এটি keyword নয়, সাধারণ একটি identifier, অর্থাৎ একটি ফাংশনের নাম। কিন্তু নামটি বিশেষ: প্রোগ্রাম চালু হলে ঠিক এই নামের ফাংশনটিই খোঁজা হয়। বানান ভুল হলে (mian) কোড compile হবে, কিন্তু linker বলবে undefined reference to `main' — আগের পর্বের সেই error!
  4. (void) — কোনো ইনপুট নেই। ফাংশনের নামের পরের গোল বন্ধনীতে লেখা থাকে সে কী কী তথ্য নিয়ে কাজ শুরু করবে — এগুলোকে বলে parameter. void মানে “কিছুই না”। শুধু () লিখলেও চলে, তবে পুরোনো C-তে ()-এর অর্থ ছিল “parameter সম্পর্কে কিছু বলা হচ্ছে না” — তাই (void) বেশি স্পষ্ট।
  5. { } — ফাংশনের শরীর। দুটি বাঁকা বন্ধনীর মাঝের সবকিছু main-এর কাজ। এই দলকে বলে block. লাইন ৩-এ খোলা বন্ধনী আর লাইন ৬-এ বন্ধ — প্রতিটি খোলার জোড়া একটি বন্ধ থাকতেই হবে। শেষ } পর্যন্ত পৌঁছালে main-এর কাজ শেষ।
  6. printf — লেখা ছাপানোর ফাংশন। নামটি এসেছে “print formatted” থেকে। খেয়াল করুন, এই ফাংশন আপনি লেখেননি, C ভাষার ভেতরেও এটি নেই। এটি আছে standard library-তে (libc), আর linker সেটি আপনার প্রোগ্রামে জুড়ে দেয়। stdio.h শুধু জানিয়েছিল ফাংশনটি কেমন।
  7. "Hello, Dhaka!\n" — ছাপানোর লেখা। দুটি সোজা ডাবল কোটের মাঝের লেখাকে বলে string literal. শেষের \n দুটি অক্ষর দেখালেও আসলে একটি অক্ষর — “নতুন লাইন”। একে বলে escape sequence. এটি না দিলে লেখার পরেই terminal-এর prompt একই লাইনে চলে আসে।
  8. ; — বাক্যের দাঁড়ি। বাংলায় বাক্য শেষে যেমন দাঁড়ি, C-তে তেমনি প্রতিটি statement-এর শেষে সেমিকোলন। লাইন ৪ ও লাইন ৫ — দুটিই statement, তাই দুটিতেই ;. Compiler লাইন দেখে নয়, এই চিহ্ন দেখেই বোঝে একটি নির্দেশ কোথায় শেষ হলো।
  9. return 0 — শেষ বার্তা। main-এর কাজ শেষ, আর ফেরত যাচ্ছে সংখ্যা 0 — এটি main-এর return value. এর অর্থ: “সব ঠিকঠাক হয়েছে।” এই সংখ্যাটি শেষ পর্যন্ত পৌঁছায় অপারেটিং সিস্টেম আর যে প্রোগ্রাম আপনার প্রোগ্রামটি চালিয়েছিল (যেমন terminal) তার কাছে।

এবার পুরো ছবিটি এক টেবিলে দেখে নিন। কোন অংশ কোন ধাপে কাজে লাগে, সেটি খেয়াল করুন — আগের পর্বের টুলচেইনের সাথে এখানে সরাসরি যোগ আছে।

অংশকীনা থাকলে / ভুল হলে
#include <stdio.h>printf-এর পরিচয় আনাCompiler বলবে implicit declaration of function 'printf'
intmain-এর ফেরত মানের ধরনবাদ দিলে আধুনিক compiler-এ warning বা error
mainপ্রোগ্রামের প্রবেশদ্বারের নামLinker: undefined reference to `main'
{ }ফাংশনের শরীর (block)expected declaration or statement at end of input
;statement-এর শেষexpected ';' before ...
return 0;OS-কে সফলতার খবরC99 থেকে main-এ বাদ দিলেও 0 ধরা হয়, তবে লিখে রাখাই পরিষ্কার

এই পর্বে “ফাংশন” শব্দটি অনেকবার আসছে। আপাতত এটুকু জানলেই চলবে: ফাংশন হলো নাম দেওয়া এক টুকরো কাজ। main একটি ফাংশন যা আপনি লেখেন, printf একটি ফাংশন যা অন্য কেউ লিখে রেখেছেন। নিজের ফাংশন বানানো শিখব পর্ব ১৩-তে।

৩ · C-এর ব্যাকরণের ছোট ছোট নিয়ম

প্রতিটি ভাষার ব্যাকরণ আছে। বাংলায় একটি ভুল বানান থাকলেও মানুষ বুঝে নেন। কিন্তু compiler অনেক বেশি কড়া — নিয়মের বাইরে এক চুলও গেলে সে থেমে যায়। তাই কয়েকটি মৌলিক নিয়ম প্রথম দিনেই পরিষ্কার করে নেওয়া ভালো।

Statement আর block

একটি statement হলো একটি সম্পূর্ণ নির্দেশ, যেমন printf("Hi\n");. আর একটি block হলো { } দিয়ে ঘেরা কয়েকটি statement-এর দল। ক্রিকেটের ভাষায় বললে, প্রতিটি বল একটি statement, আর একটি ওভার একটি block — ছয়টি বল মিলে একটি ইউনিট, যার শুরু ও শেষ পরিষ্কার। পরে if আর লুপ শিখলে দেখবেন, block-ই ঠিক করে দেয় কোন কোন নির্দেশ একসাথে চলবে।

C বড়-ছোট হাতের অক্ষর আলাদা ধরে

C একটি case-sensitive ভাষা। main, Main আর MAIN — compiler-এর কাছে এরা তিনটি আলাদা নাম। ঠিক তেমনি printf আর Printf আলাদা। মোবাইলে কোড লিখলে কিবোর্ড প্রায়ই নিজে থেকে প্রথম অক্ষর বড় করে দেয় — তখনই এই ভুল ঢোকে। C-এর সব keyword আর standard library-র প্রায় সব ফাংশনের নাম ছোট হাতের অক্ষরে।

ফাঁকা জায়গা মানুষের জন্য

স্পেস, ট্যাব আর নতুন লাইন — এদের একসাথে বলে whitespace. C-এর compiler এগুলোকে মূলত শব্দ আলাদা করার চিহ্ন হিসেবে দেখে। একটি স্পেস দিলেন না দশটি, তাতে অর্থ বদলায় না। তাই নিচের দুটি প্রোগ্রাম compiler-এর কাছে হুবহু এক:

মানুষের জন্য সাজানো

int main(void) {
    printf("Hi\n");
    return 0;
}
পড়তে সহজ

এক লাইনে ঠাসা

int main(void){printf("Hi\n");return 0;}
compiler খুশি, মানুষ বিরক্ত

তবে দুটি ব্যতিক্রম মনে রাখুন। এক, শব্দের ভেতরে স্পেস দেওয়া যাবে না — pri ntf লিখলে দুটি আলাদা শব্দ হয়ে যাবে। দুই, string literal-এর ভেতরের স্পেস আক্ষরিক অর্থেই ছাপা হয়। আর #include-এর মতো preprocessor লাইন নিজের আলাদা লাইনে থাকতে হয়।

মূল ধারণা

Indentation (লাইনের শুরুতে কয়েকটি স্পেস দিয়ে ভেতরে সরানো) compiler-এর কোনো কাজে লাগে না, কিন্তু মানুষের খুব কাজে লাগে। এক নজরে বোঝা যায় কোন লাইন কোন block-এর ভেতরে। পেশাদার দলে সবাই একই নিয়মে সাজায়, আর clang-format-এর মতো টুল নিজে থেকেই সেটি করে দেয়।

Comment — ভবিষ্যতের পাঠকের জন্য নোট

Comment হলো কোডের ভেতরে মানুষের জন্য লেখা নোট। Preprocessor এগুলো পুরোপুরি মুছে ফেলে, তাই প্রোগ্রামে এর কোনো প্রভাব নেই। C-তে দুই রকম comment আছে:

// এক লাইনের comment: লাইনের বাকি অংশ উপেক্ষিত (C99 থেকে)

/* একাধিক লাইনের comment:
   শুরু ও শেষের চিহ্নের মাঝের সবকিছু উপেক্ষিত */

প্রশ্ন হলো — comment-এ কী লিখবেন? নতুনরা প্রায়ই লেখেন কোড কী করছে, যা কোড দেখেই বোঝা যায়। অভিজ্ঞরা লেখেন কেন করছে, যা কোড দেখে বোঝা যায় না।

দুর্বল comment

total = total + 5;  // total-এর সাথে 5 যোগ

কোড নিজেই এটা বলছে। এই comment শুধু জায়গা নিচ্ছে।

কাজের comment

total = total + 5;  // প্রতি অর্ডারে ৫ টাকা ডেলিভারি চার্জ

এটি বলছে কেন 5 — ছয় মাস পরে কেউ কোড পড়লে এটাই জানতে চাইবেন।

AI-এর লেখা কোডে প্রায়ই প্রতিটি লাইনের পাশে “কী করছে” ধরনের comment থাকে। শেখার সময় এগুলো উপকারী, কিন্তু আসল প্রজেক্টে এগুলো শব্দদূষণ। তার চেয়েও বড় বিপদ: কোড বদলায়, comment বদলায় না। তখন comment এক কথা বলে আর কোড করে আরেক — এটা কোনো comment না থাকার চেয়েও খারাপ।

৪ · main() আসলে কে — আর তাকে কে ডাকে

বলা হয়, “C প্রোগ্রাম main থেকে শুরু হয়।” আপনার লেখা কোডের দিক থেকে কথাটি সত্য — main হলো প্রোগ্রামের entry point বা প্রবেশদ্বার। কিন্তু পুরো সত্যটা আরেকটু মজার। main নিজে থেকে চালু হয় না; কেউ একজন তাকে ডাকে। আর ফাংশন শেষে return করলে সেই মান যায় যে ডেকেছিল তার কাছে।

একটি বিয়ের অনুষ্ঠানের কথা ভাবুন। অতিথিদের কাছে অনুষ্ঠান শুরু হয় যখন বর-কনে মঞ্চে আসেন। কিন্তু তার আগে ডেকোরেটর মঞ্চ সাজিয়েছেন, বাবুর্চি রান্না শেষ করেছেন, কেউ চেয়ার পেতেছেন। আপনার main হলো সেই বর-কনে — সবার নজর তাদের দিকে। কিন্তু তার আগে অপারেটিং সিস্টেম আর একটি ছোট “প্রস্তুতি দল” অনেক কাজ সেরে রেখেছে, আর অনুষ্ঠান শেষে গোছগাছও তারাই করে।

নিচের ডায়াগ্রামে দেখুন, আপনি terminal-এ ./hello লিখে Enter চাপার পর থেকে আবার prompt ফিরে আসা পর্যন্ত কী কী ঘটে। প্রতিটি ধাপে নিচের প্যানেলে সেই মুহূর্তের অবস্থা দেখাবে।

  1. Shell./helloকমান্ড পড়ে
  2. OS loadermemoryফাইল মেমরিতে তোলে
  3. Startup code_startপ্রস্তুতি দল
  4. আপনার কোডmain()প্রবেশদ্বার
  5. libcprintfলেখা তৈরি করে
  6. OS → স্ক্রিনwriteলেখা দেখায়
Terminalআপনি কমান্ড দিলেন
$ ./hello
// shell বুঝল: বর্তমান ফোল্ডারের hello ফাইলটি চালাতে হবে
OSনতুন process তৈরি
hello ফাইল (ডিস্কে)  ──►  RAM-এ জায়গা
  কোড      : main, printf …
  ডেটা     : "Hello, Dhaka!\n"
  stack    : ফাংশনের কাজের জায়গা
// dynamic library (libc) থাকলে সেটিও জোড়া হয়
_start (C runtime)main-এর আগের কাজ
// আপনি এটি লেখেননি — linker জুড়ে দিয়েছে
১. stack ও library প্রস্তুত করা
২. stdin, stdout, stderr খুলে রাখা
৩. status = main();    // এবার আপনার পালা
main()আপনার প্রথম লাইন চলছে
int main(void) {
    printf("Hello, Dhaka!\n");   // ← এখানে
    return 0;
}
printf (libc)লেখা বাফারে জমা
buffer: [H e l l o ,   D h a k a ! \n]
// \n পেয়েছে, আর আউটপুট যাচ্ছে terminal-এ —
// তাই এখনই OS-কে পাঠানোর সময়
TerminalOS লেখাটি দেখাল
$ ./hello
Hello, Dhaka!
_start (C runtime)main ফিরে এসেছে
status = main();   // status এখন 0
exit(status);      // বাকি বাফার খালি করা, ফাইল বন্ধ করা
// তারপর OS-কে বলা: "শেষ, ফল 0"
TerminalShell ফল পেল
$ ./hello
Hello, Dhaka!
$ echo $?
0
// প্রোগ্রাম শেষ, মেমরি ফেরত, prompt আবার তৈরি
  1. আপনি কমান্ড দিলেন। Terminal-এর ভেতরে যে প্রোগ্রাম আপনার লেখা পড়ে, তার নাম shell (Linux-এ সাধারণত bash). ./ মানে “এই ফোল্ডারেই”। Shell নিজে প্রোগ্রাম চালাতে পারে না — সে অপারেটিং সিস্টেমকে অনুরোধ করে।
  2. OS ফাইলটি মেমরিতে তুলল। অপারেটিং সিস্টেমের loader অংশ hello ফাইলটি ডিস্ক থেকে পড়ে RAM-এ সাজায় — কোড এক জায়গায়, লেখা আরেক জায়গায়, কাজের জন্য খালি জায়গা আরেকখানে। এই চলমান প্রোগ্রামটিকে বলে process. Library আলাদা ফাইলে থাকলে (আগের পর্বের dynamic linking) সেটিও এখন জোড়া হয়।
  3. Startup code আগে চলে — main নয়! OS আসলে লাফ দেয় একটি ছোট প্রস্তুতি-কোডে, যাকে বলে C runtime startup code. Linux-এ এর শুরুর বিন্দুর নাম সাধারণত _start. এটি আপনি লেখেননি — linker নিজে থেকে জুড়ে দিয়েছে। এটি library প্রস্তুত করে, তারপর main-কে ডাকে। এজন্যই main নাম না পেলে linker অভিযোগ করে — startup code তো এই নামটিকেই ডাকতে চায়।
  4. আপনার কোড শুরু। এবার নিয়ন্ত্রণ এল main-এর প্রথম লাইনে। আপনার দৃষ্টিকোণ থেকে প্রোগ্রাম এখানেই শুরু, কিন্তু এর আগে অনেক কাজ হয়ে গেছে।
  5. printf লেখা তৈরি করল। printf লেখাটি সাজিয়ে প্রথমে একটি buffer-এ জমা রাখে — যেমন রিকশাওয়ালা একজন যাত্রী নিয়ে বারবার না গিয়ে ভরা রিকশা নিয়ে যান। আউটপুট terminal-এ গেলে সাধারণত \n পাওয়ামাত্র জমানো লেখা পাঠিয়ে দেওয়া হয়।
  6. OS স্ক্রিনে লেখাটি দেখাল। সাধারণ প্রোগ্রাম সরাসরি স্ক্রিন বা হার্ডওয়্যারে হাত দিতে পারে না। libc অপারেটিং সিস্টেমকে অনুরোধ পাঠায় — একে বলে system call (Linux-এ এর নাম write). OS লেখাটি terminal-এ পৌঁছে দেয়।
  7. return 0 — ফিরতি পথ। main 0 ফেরত দিল তাকে যে ডেকেছিল — অর্থাৎ startup code-কে। সে এই মান নিয়ে exit ডাকে: বাফারে কিছু বাকি থাকলে পাঠিয়ে দেয়, খোলা ফাইল বন্ধ করে, তারপর OS-কে জানায়: “প্রোগ্রাম শেষ, ফল শূন্য।”
  8. Shell ফলটি পেল। OS process-এর মেমরি ফেরত নেয়, আর ফলাফলের সংখ্যাটি পৌঁছে দেয় shell-কে। এই সংখ্যাকে বলে exit status. echo $? লিখলে শেষ প্রোগ্রামের exit status দেখা যায় — এখানে 0. তারপর shell নতুন prompt দেখায়, পরের কমান্ডের অপেক্ষায়।
Shell → OS loader → startup code → main() → libc → OS → স্ক্রিন
তারপর ফেরার পথে: return 0 → exit → OS → shell

Windows-এও একই ধরনের ঘটনা ঘটে, শুধু নামগুলো আলাদা — যেমন Microsoft-এর compiler-এ startup code-এর নাম mainCRTStartup. নাম যা-ই হোক, নকশা একই: OS একটি প্রস্তুতি-কোড চালায়, সে আপনার main ডাকে, আর main-এর ফেরত মান OS-এর কাছে ফিরে যায়।

৫ · return 0 — প্রোগ্রামের শেষ বার্তা

এতক্ষণে বুঝে গেছেন, return 0 শুধু “শেষ” বলার ভঙ্গি নয়। এটি একটি আসল সংখ্যা, যা প্রোগ্রাম থেকে বাইরে যায়। নিয়মটি খুব সহজ:

0 = সফল

“কাজ ঠিকঠাক শেষ হয়েছে।” প্রায় সব অপারেটিং সিস্টেম ও টুল এই অর্থই ধরে।

EXIT_SUCCESS

শূন্য ছাড়া যেকোনো সংখ্যা = সমস্যা

“কিছু একটা ঠিক হয়নি।” কোন সংখ্যা কোন সমস্যা বোঝাবে, তা প্রোগ্রামের লেখক ঠিক করেন।

EXIT_FAILURE (সাধারণত 1)

প্রশ্ন জাগতে পারে, সফলতার জন্য 0 কেন — 1 নয় কেন? কারণ সফল হওয়ার উপায় একটাই, কিন্তু ব্যর্থ হওয়ার উপায় অনেক। ফাইল পাওয়া যায়নি, অনুমতি নেই, ইনপুট ভুল — প্রতিটির জন্য আলাদা অশূন্য সংখ্যা রাখা যায়। একটি মাত্র 0 সফলতার জন্য, বাকি সব সংখ্যা নানা রকম ব্যর্থতার জন্য।

বোর্ড পরীক্ষার ফলাফলের কথা ভাবুন। পাস করলে খবর একটাই — “পাস”। কিন্তু ফেল করলে কারণ আলাদা হতে পারে: কোন বিষয়ে, অনুপস্থিত ছিলেন কি না, খাতা বাতিল হয়েছে কি না। exit status-ও তেমনি: 0 মানে “পাস”, আর অশূন্য সংখ্যা জানায় ঠিক কোন ধরনের সমস্যা।

exit status কীভাবে দেখবেন

প্রোগ্রাম চালানোর ঠিক পরেই echo $? লিখুন। $? সবসময় শেষ কমান্ডের exit status রাখে।

./hello
echo $?

Command Prompt-এ এর নাম ERRORLEVEL:

hello.exe
echo %ERRORLEVEL%

PowerShell-এ বাইরের প্রোগ্রামের exit status থাকে একটি বিশেষ ভ্যারিয়েবলে:

.\hello.exe
echo $LASTEXITCODE

কে এই সংখ্যা পড়ে?

আপনি নিজে হাতে খুব কমই echo $? লিখবেন। কিন্তু অন্য প্রোগ্রামগুলো প্রতিনিয়ত এটি পড়ে। Build টুল make কোনো ধাপের exit status অশূন্য পেলেই থেমে যায়। GitHub Actions-এর মতো CI সার্ভার প্রতিটি ধাপের exit status দেখে ঠিক করে সবুজ টিক দেবে নাকি লাল ক্রস। Shell-এর && আর || চিহ্নও এর ওপর চলে। নিচের ডায়াগ্রামে একটি ছোট shell সেশন ধাপে ধাপে দেখুন:

1gcc -Wall hello.c -o hello2echo $?3./hello4./hello && echo "OK"5./helo || echo "FAILED"6echo $?
Shell-এর হিসাব
$? (শেষ status)
সিদ্ধান্ত
  1. Compile করা হলো। gcc নিজেও একটি প্রোগ্রাম, তারও exit status আছে। কোনো error নেই, তাই কিছু ছাপেনি, ফেরত দিয়েছে 0. আগের পর্বের ld returned 1 exit status মনে আছে? সেখানে linker 1 ফেরত দিয়েছিল — অর্থাৎ ব্যর্থ।
  2. echo $? — শেষ কমান্ডের (gcc) status দেখাল: 0. Build সফল।
  3. প্রোগ্রাম চালানো হলো। ./hello লেখা ছাপল, তারপর main থেকে return 0 হলো। $? এখন 0.
  4. && — “সফল হলে তবেই”। Shell আগে ./hello চালায়। exit status 0 পেলে তবেই ডানের কমান্ড চালায়। পেয়েছে 0, তাই OK ছাপা হলো। Build script-এ “compile সফল হলে তবেই চালাও” — ঠিক এভাবেই লেখা হয়।
  5. || — “ব্যর্থ হলে তবেই”। নামের বানান ভুল (helo), ফাইলটিই নেই। Bash বার্তা দেখাল, আর status দিল 127 — bash-এর নিয়মে এর অর্থ “কমান্ড খুঁজে পাইনি”। অশূন্য পেয়েছে, তাই ||-এর ডান দিকের FAILED চলল। সেই echo সফল হলো, তাই $? আবার 0 হয়ে গেল।
  6. একটি ফাঁদ! শেষ echo $? দেখাল 0, 127 নয়। কারণ $? সবসময় ঠিক আগের কমান্ডের ফল রাখে — আর ঠিক আগের কমান্ড ছিল echo "FAILED", যা সফল। তাই কোনো প্রোগ্রামের exit status দেখতে চাইলে তার ঠিক পরেই দেখতে হয়।

void main() কেন ভুল

বাংলাদেশের অনেক পুরোনো বই, কোচিং নোট আর ইউটিউব টিউটোরিয়ালে এখনো এরকম কোড দেখা যায়:

#include <conio.h>
void main()
{
    printf("Hello");
    getch();
}

এখানে তিনটি সমস্যা আছে। প্রথমত, C standard main-এর জন্য দুটি রূপের নিশ্চয়তা দেয় — int main(void) আর int main(int argc, char *argv[]). দুটিতেই ফেরত মানের ধরন int. void main মানে “কিছুই ফেরত দেব না” — তাহলে OS কোন exit status পাবে? কোনো কোনো compiler এটি মেনে নেয়, কিন্তু standard এর নিশ্চয়তা দেয় না; GCC-তে -Wall দিলে বলে return type of 'main' is not 'int'.

দ্বিতীয়ত, conio.h standard C-এর অংশ নয়। এটি এসেছে DOS-যুগের Turbo C থেকে, মূলত শুধু Windows/DOS-এ পাওয়া যায়। Linux বা Mac-এর GCC-তে এই ফাইলই নেই — preprocessor বলবে No such file or directory. তৃতীয়ত, getch() ব্যবহার করা হতো যাতে প্রোগ্রাম শেষ হয়ে কালো জানালাটি সঙ্গে সঙ্গে বন্ধ না হয়ে যায়। আধুনিক editor-এ এর দরকার নেই, আর এটিও conio.h-এর ফাংশন।

পরীক্ষার খাতায় বা চাকরির ইন্টারভিউতে void main() লিখলে অভিজ্ঞ পরীক্ষক সঙ্গে সঙ্গে বুঝবেন আপনি পুরোনো উৎস থেকে শিখেছেন। আর conio.h ব্যবহার করা কোড Linux-ভিত্তিক অনেক অনলাইন জাজ compile-ই করতে পারে না, কারণ সেখানে এই header থাকে না। সবসময় লিখুন int main(void) আর return 0;.

৬ · printf — লেখা ছাপানোর যন্ত্র

printf-কে আপাতত একটি ছাঁচ বা টেমপ্লেট হিসেবে ভাবুন। প্রথম অংশটি হলো format string — যে লেখা ছাপা হবে তার নকশা। এর ভেতরে কিছু ফাঁকা ঘর রাখা যায়, আর কমার পরে দেওয়া মানগুলো ক্রম অনুযায়ী সেই ঘরগুলো ভরে।

bKash বা Nagad থেকে টাকা পাঠালে যে SMS আসে, সেটি খেয়াল করুন: “আপনি ০১৭XXXXXXXX নম্বরে ৫০০ টাকা পাঠিয়েছেন। ফি ০ টাকা।” প্রতিটি SMS-এর বাক্য একই, শুধু নম্বর আর টাকার অঙ্ক বদলায়। সিস্টেমে আসলে একটি ছাঁচ রাখা আছে, যার ফাঁকা ঘরে প্রতিবার নতুন মান বসে। printf-এর format string ঠিক সেই ছাঁচ।

Format specifier — ফাঁকা ঘরের চিহ্ন

Format string-এর ভেতরে % দিয়ে শুরু হওয়া প্রতিটি চিহ্ন একটি ফাঁকা ঘর। একে বলে format specifier. অক্ষরটি বলে দেয় কোন ধরনের মান বসবে:

চিহ্নকী বসবেউদাহরণছাপা হবে
%dপূর্ণসংখ্যা (int)printf("%d", 17)17
%fদশমিক সংখ্যা (double)printf("%f", 4.75)4.750000
%.2fদশমিকের পরে ২ ঘরprintf("%.2f", 4.75)4.75
%cএকটি অক্ষরprintf("%c", 'A')A
%sলেখা (string)printf("%s", "Dhaka")Dhaka
%%আসল % চিহ্নprintf("50%%")50%

নিচের প্রোগ্রামটি চালিয়ে দেখুন। একজন ছাত্রের রোল, GPA, গ্রেড আর কোর্সের নাম ছাপা হচ্ছে — প্রতিটি আলাদা ধরনের মান:

#include <stdio.h>

int main(void) {
    int roll = 17;
    double gpa = 4.75;
    char grade = 'A';

    printf("Roll  : %d\n", roll);
    printf("GPA   : %f\n", gpa);
    printf("GPA   : %.2f\n", gpa);
    printf("Grade : %c\n", grade);
    printf("Course: %s\n", "C Programming");
    printf("Roll %d got grade %c\n", roll, grade);
    return 0;
}

আউটপুট হবে: Roll : 17, GPA : 4.750000, GPA : 4.75, Grade : A, Course: C Programming আর শেষে Roll 17 got grade A. খেয়াল করুন: শুধু %f দিলে দশমিকের পরে ছয় ঘর আসে; %.2f দিলে দুই ঘর। শেষ লাইনে দুটি ঘর — প্রথম %d পেল প্রথম মান roll, আর %c পেল দ্বিতীয় মান grade. ক্রম উল্টে গেলে ফলও উল্টাপাল্টা হবে। (ভ্যারিয়েবল কী, তা বিস্তারিত শিখব পর্ব ০৬-এ; আপাতত এগুলোকে নাম দেওয়া বাক্স ভাবুন।)

চিহ্ন আর মানের ধরন না মিললে — যেমন %d-এর জায়গায় দশমিক সংখ্যা দিলে — C-এর নিয়মে ফল নির্দিষ্ট নয় (undefined behavior). প্রায়ই আবর্জনা সংখ্যা ছাপা হয়। সুখবর হলো, -Wall দিলে GCC এই অমিল ধরে ফেলে warning দেয়। আরও বিস্তারিত printf আর ইনপুট নেওয়ার scanf শিখব পর্ব ১০-এ।

Escape sequence — যে অক্ষর সরাসরি লেখা যায় না

String literal দুটি ডাবল কোট দিয়ে ঘেরা। তাহলে লেখার ভেতরে একটি ডাবল কোট ছাপাবেন কীভাবে? আর Enter চাপলে তো লাইনই ভেঙে যায় — নতুন লাইন লিখবেন কীভাবে? সমাধান হলো ব্যাকস্ল্যাশ (\). এর পরের অক্ষরটি বিশেষ অর্থ পায়:

লিখবেনঅর্থকখন লাগে
\nনতুন লাইনপ্রায় প্রতিটি printf-এর শেষে
\tট্যাব (কয়েক ঘর ফাঁকা)কলাম আকারে সাজাতে
\\একটি আসল ব্যাকস্ল্যাশWindows-এর ফোল্ডারের ঠিকানা লিখতে
\"একটি আসল ডাবল কোটকারও উক্তি ছাপাতে
\'একটি সিঙ্গল কোটঅক্ষর হিসেবে '\'' লিখতে
#include <stdio.h>

int main(void) {
    printf("Name\tRoll\tCity\n");
    printf("Rafi\t17\tDhaka\n");
    printf("Mitu\t23\tRajshahi\n\n");

    printf("Teacher said: \"C is the mother of languages.\"\n");
    printf("Saved in C:\\code\\hello.c\n");
    return 0;
}

প্রথম তিন লাইনে \t কলামগুলোকে সমান দূরত্বে সাজিয়েছে — একটি ছোট টেবিলের মতো দেখাবে। তৃতীয় লাইনের শেষে \n\n দুবার, তাই মাঝে একটি ফাঁকা লাইন আসবে। এরপর ছাপা হবে Teacher said: "C is the mother of languages." — \" লেখার ভেতরে আসল কোট বসিয়েছে, কিন্তু string শেষ করেনি। শেষ লাইনে দেখাবে Saved in C:\code\hello.c; দুটি \\ থেকে একটি করে \ এসেছে। একটি \ লিখলে compiler \c বা \h-কে escape sequence ভাবতে চাইত এবং warning দিত।

৭ · প্রথম দিনের ভুল — error মেসেজ দেখেই চিনুন

প্রথম সপ্তাহে প্রায় সবাই একই কয়েকটি ভুল করেন। ভালো খবর হলো, প্রতিটির error মেসেজ নির্দিষ্ট — একবার চিনে ফেললে পরের বার কয়েক সেকেন্ডে ঠিক করতে পারবেন। মনে রাখবেন, compiler যে লাইন নম্বর দেখায়, ভুল প্রায়ই তার ঠিক আগের লাইনে থাকে।

ভুলমেসেজে যা খুঁজবেনসমাধান
; বাদ পড়েছেerror: expected ';' before 'return'আগের লাইনের শেষে ; দিন
Printf (বড় হাতের P)implicit declaration of function 'Printf', পরে linker-এর undefined reference to `Printf'ছোট হাতের printf লিখুন
#include <stdio.h> ভুলে গেছেনimplicit declaration of function 'printf' (সাথে note: include '<stdio.h>')ফাইলের শুরুতে #include যোগ করুন
} বাদ বা বেশিexpected declaration or statement at end of inputপ্রতিটি {-এর জোড়া মিলিয়ে দেখুন
" বন্ধ করা হয়নিmissing terminating " characterলেখার শেষে কোট দিন
বাঁকা কোট (“ ”) কপি হয়ে এসেছেstray '\342' in programসব কোট মুছে কিবোর্ড থেকে নতুন করে " টাইপ করুন

“implicit declaration”-এর ব্যাপারটি একটু ব্যাখ্যা করি। পুরোনো C-তে না জানিয়ে কোনো ফাংশন ডাকলে compiler ধরে নিত সেটি একটি int ফেরত দেয় — একে বলে implicit declaration. এই অনুমান প্রায়ই ভুল হতো, আর তা থেকে লুকানো বাগ তৈরি হতো। তাই C99 থেকে এটি নিষিদ্ধ। Compiler-এর version ভেদে এটি warning বা error হিসেবে আসে — GCC 14 থেকে ডিফল্টভাবে error.

বাংলাদেশি শিক্ষার্থীদের সবচেয়ে রহস্যময় error

ধরুন বন্ধু WhatsApp-এ কোড পাঠাল, অথবা আপনি একটি PDF বা Word ফাইল থেকে কোড কপি করলেন। দেখতে একদম ঠিক, অথচ compile করলেই অদ্ভুত error. এর কারণ লুকিয়ে আছে কোটের আকৃতিতে। নিচের ডায়াগ্রামে ধাপে ধাপে রহস্যটি খুলুন:

hello.cWhatsApp থেকে কপি করা কোড
#include <stdio.h>

int main(void) {
    printf(“Hello, Dhaka!\n”);
    return 0;
}
Terminalgcc hello.c -o hello
hello.c:4:12: error: stray '\342' in program
hello.c:4:12: error: stray '\200' in program
hello.c:4:12: error: stray '\234' in program
// … তারপর আরও কয়েক লাইন error
Byte-এর হিসাবএকই রকম দেখতে, ভেতরে আলাদা
সোজা কোট   "   →  1 byte  : 0x22         (ASCII)
বাঁকা কোট  “   →  3 byte  : 0xE2 0x80 0x9C (UTF-8)
বাঁকা কোট  ”   →  3 byte  : 0xE2 0x80 0x9D (UTF-8)

0xE2 = 226 = অষ্টাল (octal) 342  ← error-এর সেই সংখ্যা!
hello.cসমাধান
#include <stdio.h>

int main(void) {
    printf("Hello, Dhaka!\n");   // কিবোর্ডের সোজা কোট
    return 0;
}
Terminalআবার compile
$ gcc -Wall hello.c -o hello
$ ./hello
Hello, Dhaka!
  1. কোডটি ঠিকই দেখাচ্ছে। কিন্তু খুব কাছ থেকে দেখুন — কোট দুটি একটু বাঁকানো: শুরুরটি “ আর শেষেরটি ”. WhatsApp, Word, Google Docs আর অনেক PDF সুন্দর দেখানোর জন্য সাধারণ কোটকে নিজে থেকে এমন বাঁকা কোটে (smart quote) বদলে দেয়।
  2. অদ্ভুত error. Compiler বলছে stray '\342' in program — অর্থাৎ “প্রোগ্রামে একটি বেখাপ্পা অক্ষর পেয়েছি, যার মান 342.” একই লাইনে পরপর তিনটি এমন error, আর তারপর আরও কিছু — কারণ compiler string literal-এর শুরুই খুঁজে পায়নি, তাই Hello-কেও কোনো নাম ভেবে বিভ্রান্ত হয়েছে।
  3. রহস্যের সমাধান। C-এর কোড লেখার চিহ্নগুলো সব ASCII তালিকার ভেতরে, প্রতিটি মাত্র ১ byte. সোজা কোটের মান 0x22. কিন্তু বাঁকা কোট ASCII-তে নেই; সেটি UTF-8-এ লেখা হয় ৩টি byte দিয়ে: E2 80 9C. GCC এই অচেনা byte-গুলো অষ্টাল সংখ্যায় দেখায় — E2 হলো অষ্টালে 342, 80 হলো 200, 9C হলো 234. তিনটি error, তিনটি byte!
  4. সমাধান — নিজে টাইপ করুন। বাঁকা কোট দুটি মুছে কিবোর্ড থেকে সাধারণ " টাইপ করুন। একই সমস্যা হয় বাঁকা সিঙ্গল কোট (‘ ’) আর লম্বা ড্যাশ (–) নিয়েও। ভালো editor (যেমন VS Code) এই অক্ষরগুলো আলাদা রঙে বা বাক্সে চিহ্নিত করে দেখায়।
  5. এবার চলল। Error নেই, warning নেই, লেখাও ঠিকঠাক ছাপা হলো। শিক্ষা: কোড শেয়ার করতে হলে সাধারণ টেক্সট হিসেবে পাঠান — GitHub Gist, Pastebin, বা অন্তত WhatsApp-এ তিনটি backtick (```) দিয়ে ঘিরে। তাহলে লেখা বদলায় না।

একটি সূত্র মনে রাখুন: error মেসেজে \342, \200, \302 বা এ ধরনের ব্যাকস্ল্যাশসহ সংখ্যা দেখলেই বুঝবেন, কোডে কোনো অ-ASCII অক্ষর ঢুকে গেছে — বাঁকা কোট, অদৃশ্য বিশেষ স্পেস, বা ভুল করে টাইপ হওয়া বাংলা অক্ষর। String literal আর comment-এর ভেতরে বাংলা লেখা যায়, কিন্তু কোডের বাকি অংশে নয়।

৮ · AI-এর যুগে — পুরোনো অভ্যাস চিনে নিন

AI মডেলগুলো শিখেছে ইন্টারনেটে থাকা কোটি কোটি লাইনের কোড থেকে। তার মধ্যে আছে কয়েক দশক পুরোনো টিউটোরিয়াল আর ফোরামের উত্তরও। তাই AI মাঝেমধ্যে এমন কোড লেখে যা আপনার কম্পিউটারে চলে ঠিকই, কিন্তু অন্য কোথাও ভেঙে পড়ে বা লুকানো বিপদ বয়ে আনে। এই পর্বের বিষয়ের সাথে যুক্ত সাধারণ ভুলগুলো চিনে রাখুন:

যা দেখবেনসমস্যাযা লিখবেন
void main()Standard নয়; OS সঠিক exit status পায় নাint main(void) + return 0;
#include <conio.h>, getch(), clrscr()পুরোনো DOS/Windows-এর জিনিস; Linux, Mac ও অনেক অনলাইন জাজে নেইবাদ দিন; জানালা খোলা রাখতে editor-এর terminal ব্যবহার করুন
fflush(stdin)Standard-এ ইনপুটের জন্য এর ফল নির্দিষ্ট নয় (undefined behavior)পর্ব ১০-এ ইনপুট পরিষ্কারের সঠিক উপায় দেখব
gets(name)এত বিপজ্জনক যে C11 থেকে ভাষা থেকেই বাদ দেওয়া হয়েছেfgets (পর্ব ১৯)
printf-এ বাংলা লেখাWindows-এর পুরোনো console-এ ভাঙা অক্ষর দেখায়শেখার সময় ইংরেজি (ASCII) লেখা

AI-কে কোড লিখতে বলার সময় শুরুতেই নিয়ম বেঁধে দিন। তাহলে পুরোনো অভ্যাসগুলো আসার সুযোগই পাবে না। নিচের prompt কপি করে নিজের প্রয়োজনমতো বদলে নিন:

আমি C শিখছি। নিচের কাজের জন্য একটি C প্রোগ্রাম লিখে দাও: [এখানে কাজটি লিখুন] শর্ত: ১. শুধু standard C17 ব্যবহার করবে — conio.h, windows.h বা অন্য কোনো platform-specific header নয়। ২. int main(void) লিখবে, আর শেষে return 0; দেবে। void main একদম নয়। ৩. gets, fflush(stdin), getch ব্যবহার করবে না। ৪. printf-এর লেখা ইংরেজিতে রাখবে। ৫. কোডটি যেন gcc -std=c17 -Wall -Wextra দিয়ে কোনো warning ছাড়াই compile হয়। ৬. প্রতিটি অংশ কেন আছে, কোডের নিচে সহজ বাংলায় ৩-৪ লাইনে ব্যাখ্যা করো।

AI-এর লেখা প্রথম প্রোগ্রাম যাচাইয়ের চেকলিস্ট

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলোই আপনাকে কোড-লেখক থেকে সিস্টেম-চিন্তক বানাবে।

প্র ০১main কেন int ফেরত দেয়? এই সংখ্যাটি বাস্তবে কে, কীভাবে ব্যবহার করে?

কারণ একটি প্রোগ্রাম কখনো একা থাকে না — সে প্রায় সবসময় বড় কোনো ব্যবস্থার একটি অংশ। Unix-এর নকশার মূল দর্শনই ছিল: ছোট ছোট প্রোগ্রাম, প্রতিটি একটি কাজ ভালোভাবে করবে, আর একে অপরের সাথে মিলে বড় কাজ করবে। এই মিলনের জন্য প্রতিটি প্রোগ্রামকে শেষে জানাতে হয় — কাজ হয়েছে কি না। int ফেরত মান ঠিক সেই জানানোর মাধ্যম।

  • Build system: make প্রতিটি compile কমান্ডের exit status দেখে। একটি ফাইলে error হলে gcc অশূন্য ফেরত দেয়, আর make সেখানেই থেমে যায় — ভাঙা প্রোগ্রাম তৈরি হয় না।
  • CI/CD: GitHub Actions বা GitLab CI-তে প্রতিটি ধাপ একটি কমান্ড। Test প্রোগ্রাম কোনো test-এ ব্যর্থ হলে অশূন্য ফেরত দেয়, আর পুরো pipeline লাল হয়ে যায়। কোড merge বা deploy আটকে যায়। কোটি টাকার সফটওয়্যারের নিরাপত্তা দাঁড়িয়ে আছে এই একটি সংখ্যার ওপর।
  • Script: ./backup && ./upload — backup সফল হলে তবেই আপলোড। ব্যাংকের রাতের হিসাব-মেলানোর কাজের মতো স্বয়ংক্রিয় কাজগুলো এভাবেই শিকল বেঁধে চলে।
  • বিশেষ সংখ্যা: কিছু প্রোগ্রাম আলাদা অর্থের জন্য আলাদা সংখ্যা ব্যবহার করে। যেমন grep: 0 মানে মিল পেয়েছি, 1 মানে মিল পাইনি, 2 মানে error. Bash নিজেও 127 দেয় কমান্ড না পেলে।

একটি সূক্ষ্ম কথা: Linux ও Mac-এ shell exit status-এর কেবল নিচের ৮ বিট দেখতে পায়, অর্থাৎ 0 থেকে 255. তাই return 256; লিখলে shell দেখবে 0 — সফল! আর ঋণাত্মক সংখ্যাও বদলে যায়: return -1; দেখাবে 255. এজন্যই বহনযোগ্য কোডে stdlib.h-এর EXIT_SUCCESS ও EXIT_FAILURE ব্যবহার করা ভালো।

মূল উপলব্ধি: exit status হলো প্রোগ্রামের “API” — অন্য প্রোগ্রাম আপনার প্রোগ্রামকে এই সংখ্যা দিয়েই বোঝে। ভুল সংখ্যা ফেরত দেওয়া মানে পুরো স্বয়ংক্রিয় ব্যবস্থাকে মিথ্যা বলা।

প্র ০২C-তে #include লাগে কেন? Compiler নিজেই কেন জানে না printf কেমন?

এর উত্তর লুকিয়ে আছে আগের পর্বের separate compilation-এ। C-এর compiler একবারে একটি মাত্র .c ফাইল দেখে। অন্য ফাইলে কী আছে, library-তে কী আছে — সে কিছুই জানে না। তবু printf("%d", x) ডাকার সঠিক machine code বানাতে তার কয়েকটি তথ্য লাগে: ফাংশনটি কী ফেরত দেয়, কোন ধরনের মান নেয়, কয়টি নেয়।

এখানে দুটি শব্দের পার্থক্য বোঝা জরুরি:

  • Declaration (ঘোষণা): “এমন একটি ফাংশন আছে, তার চেহারা এমন।” যেমন int printf(const char *format, ...); — শুধু পরিচয়, কাজের কোড নেই। stdio.h-তে এগুলোই থাকে।
  • Definition (সংজ্ঞা): ফাংশনের আসল কাজের কোড। printf-এর definition আগেই compile হয়ে libc-তে রাখা আছে।

Compiler-এর লাগে declaration, আর linker-এর লাগে definition. #include কোনো জাদু নয় — preprocessor আক্ষরিক অর্থেই header ফাইলের লেখা কপি করে আপনার ফাইলে বসিয়ে দেয়, যেন আপনি নিজে টাইপ করেছেন। আগের পর্বে gcc -E দিয়ে এটাই দেখেছিলেন — কয়েকশো লাইন।

কেন এমন নকশা? ১৯৭০-এর দশকে কম্পিউটারের মেমরি ছিল খুব কম। পুরো প্রোগ্রাম একসাথে মেমরিতে রেখে compile করা সম্ভব ছিল না। তাই প্রতিটি ফাইল আলাদা compile, আর তথ্য ভাগাভাগির জন্য সহজতম কৌশল — লেখা কপি-পেস্ট। এর দামও আছে: একটি বড় C++ প্রজেক্টে একই header হাজারবার পড়া হয়, যা build ধীর করে। এজন্যই নতুন ভাষাগুলো (Rust, Go, এবং C++20-এর modules) “module” ব্যবস্থা এনেছে, যেখানে compiler একবার পড়ে তথ্য মনে রাখে।

মূল উপলব্ধি: header হলো একটি চুক্তিপত্র — “এই ফাংশনগুলো আছে, এভাবে ডাকতে হয়।” চুক্তি (declaration) আর কাজ (definition) আলাদা রাখাই বড় সফটওয়্যারকে টুকরো করে বানানো সম্ভব করেছে।

প্র ০৩printf কেন C ভাষার অংশ নয়, বরং একটি library ফাংশন? এতে লাভ কী?

অনেক ভাষায় লেখা ছাপানো ভাষারই অংশ — যেমন পুরোনো BASIC-এ PRINT একটি keyword. C ইচ্ছা করেই এই পথে হাঁটেনি। C-এর মূল ভাষা খুব ছোট: মোটামুটি কয়েক ডজন keyword, কিছু অপারেটর আর গঠনের নিয়ম। ইনপুট-আউটপুট, ফাইল, মেমরি নেওয়া, গাণিতিক ফাংশন — সবই রাখা হয়েছে library-তে।

  • বহনযোগ্যতা: স্ক্রিনে লেখা দেখানোর পদ্ধতি Linux, Windows আর একটি ছোট মাইক্রোকন্ট্রোলারে সম্পূর্ণ আলাদা। ভাষা একই রেখে শুধু library বদলালেই C নতুন যন্ত্রে চলে যায়। Unix-কে নতুন কম্পিউটারে নেওয়া এভাবেই সহজ হয়েছিল।
  • যেখানে স্ক্রিনই নেই: গাড়ির ব্রেক কন্ট্রোলার বা ওয়াশিং মেশিনের চিপে কোনো terminal নেই। C standard এ জন্য দুই রকম পরিবেশ ঠিক করেছে — hosted (পূর্ণ OS আছে) আর freestanding (OS নেই). Freestanding-এ stdio.h থাকা বাধ্যতামূলক নয়, এমনকি main-এর নিয়মও আলাদা হতে পারে। অপারেটিং সিস্টেমের kernel নিজেও এভাবে লেখা হয় — Linux kernel-এ printf নেই, আছে নিজস্ব printk.
  • বদলানো যায়: একই standard-এর বিভিন্ন library আছে — Linux-এ glibc, ছোট ডিভাইসে musl বা newlib, Windows-এ Microsoft-এর CRT. প্রয়োজনমতো বেছে নেওয়া যায়।

দাম হলো — printf সাধারণ একটি ফাংশন, তাই compiler নিজে থেকে জানে না %d-এর জায়গায় কী দিতে হবে। এজন্যই ভুল ধরন দিলে ভাষার নিয়মে তা ধরা পড়ে না। GCC এখানে বাড়তি চালাকি করে — printf-এর format string নিজে পড়ে warning দেয়, যদিও ভাষা এটা বাধ্য করে না।

মূল উপলব্ধি: ছোট ও স্থির একটি “কেন্দ্র”, আর বদলানো যায় এমন “চারপাশ” — এই নকশাই C-কে ৫০ বছর ধরে ছোট চিপ থেকে সুপারকম্পিউটার পর্যন্ত সবখানে টিকিয়ে রেখেছে।

প্র ০৪void main() আমার কম্পিউটারে তো চলছে — তাহলে সমস্যা কোথায়? “undefined behavior” আসলে কী বোঝায়?

“আমার কম্পিউটারে চলছে” কথাটি প্রোগ্রামিংয়ের সবচেয়ে বিপজ্জনক বাক্যগুলোর একটি। C standard তিন রকম আচরণ আলাদা করে:

  • নির্দিষ্ট আচরণ: standard বলে দেয় ঠিক কী হবে। যেমন int main(void) থেকে return 0 মানে সফল।
  • Implementation-defined: প্রতিটি compiler নিজে ঠিক করে, তবে লিখে জানাতে হয়। Standard বলে, main ওই দুটি রূপ ছাড়াও “implementation-এর নির্ধারিত অন্য কোনো রূপে” হতে পারে। কোনো compiler void main মেনে নিলে সেটি তার নিজস্ব সুবিধা — অন্য compiler-এ সেই নিশ্চয়তা নেই।
  • Undefined behavior: standard কোনো কথাই দেয় না। Compiler যা খুশি করতে পারে — ঠিক চলতে পারে, ভুল ফল দিতে পারে, crash করতে পারে।

void main-এর বাস্তব ফল কী? main কোনো মান ফেরত না দিলে startup code যে জায়গা থেকে ফল পড়ে, সেখানে আগে থেকে যা ছিল তা-ই exit status হিসেবে চলে যেতে পারে — একটি আবর্জনা সংখ্যা। ধরুন আপনার প্রোগ্রাম একটি CI pipeline-এ চলছে। কাজ ঠিকঠাক হলো, কিন্তু exit status এলো 13. Pipeline ব্যর্থ দেখাবে, আর কেউ বুঝবে না কেন। উল্টোটাও হতে পারে — প্রোগ্রাম ব্যর্থ হলো, কিন্তু 0 ফেরত গেল, আর ভুল ডেটা নিয়ে পরের ধাপ চলতে থাকল।

Undefined behavior-এর বড় বিপদ হলো এটি আজ চুপচাপ থাকতে পারে। Compiler-এর নতুন version, optimization চালু করা (-O2) বা অন্য CPU-তে নেওয়া — যেকোনো পরিবর্তনে হঠাৎ আচরণ বদলে যেতে পারে। বহু নিরাপত্তা-ত্রুটির পেছনে এই ধরনের “এতদিন তো চলছিল” কোড।

মূল উপলব্ধি: “চলছে” আর “সঠিক” এক জিনিস নয়। পেশাদার প্রোগ্রামার standard মেনে লেখেন, যাতে কোড শুধু আজকের নয়, যেকোনো compiler আর যেকোনো যন্ত্রে নিশ্চিতভাবে কাজ করে।

প্র ০৫Compiler তো স্পেস আর indentation পাত্তাই দেয় না — তাহলে কোড সাজানো নিয়ে দলগুলো এত কড়া কেন? AI-এর যুগে এটা কি আরও জরুরি?

কারণ কোড একবার লেখা হয়, কিন্তু পড়া হয় শতবার। একটি বাস্তব সফটওয়্যারের জীবনে বেশিরভাগ সময় যায় পুরোনো কোড পড়া, বোঝা আর বদলানোয় — নতুন কোড লেখায় নয়। Compiler-এর কাছে যা অর্থহীন, মানুষের মস্তিষ্কের কাছে সেটাই সবচেয়ে বড় সূত্র।

এর একটি বিখ্যাত উদাহরণ আছে। ২০১৪ সালে Apple-এর SSL নিরাপত্তা কোডে একটি বাগ ধরা পড়ে, যার ডাকনাম “goto fail”. একটি if-এর নিচে একই লাইন ভুল করে দুবার লেখা হয়েছিল। Indentation দেখে মনে হচ্ছিল দ্বিতীয় লাইনটিও if-এর ভেতরে, কিন্তু { } না থাকায় সেটি সবসময় চলত — ফলে একটি গুরুত্বপূর্ণ নিরাপত্তা-যাচাই পুরোপুরি বাদ পড়ে যেত। মানুষ চোখে দেখেছে indentation, compiler মেনেছে ব্যাকরণ। এজন্যই আজকের অনেক কোডিং নিয়ম বলে: if-এর ভেতরে একটি লাইন হলেও সবসময় { } দিন। আধুনিক GCC এমন বিভ্রান্তিকর indentation দেখলে -Wall-এ warning দেয় (-Wmisleading-indentation).

  • দলে কাজ: সবাই নিজের মতো সাজালে প্রতিটি পরিবর্তনে অপ্রয়োজনীয় স্পেস-বদল দেখা যায়, আসল পরিবর্তন হারিয়ে যায়। তাই দলগুলো clang-format-এর মতো টুল দিয়ে একটি নিয়ম স্বয়ংক্রিয়ভাবে চাপিয়ে দেয় — তর্কের প্রয়োজনই থাকে না।
  • AI-এর যুগে: AI মিনিটে শত লাইন লিখতে পারে, কিন্তু সেই কোড review করবেন মানুষ। পরিচ্ছন্ন, নিয়মিত কাঠামোর কোডে ভুল চোখে পড়ে দ্রুত। আবার এলোমেলো কোডে AI নিজেও বেশি ভুল করে, কারণ ভুল indentation তাকেও বিভ্রান্ত করে।

মূল উপলব্ধি: কোড দুই শ্রোতার জন্য লেখা — যন্ত্রের জন্য ব্যাকরণ, মানুষের জন্য কাঠামো। যন্ত্র ক্ষমাহীন, কিন্তু মানুষ বিভ্রান্তিপ্রবণ। ভালো formatting মানুষের ভুলের সুযোগ কমায়।

অনুশীলন

এগুলোর বেশিরভাগে কোড লিখতে হবে না — হাতে-কলমে দেখা আর ভাবাই আসল কাজ।

  1. ইচ্ছা করে ভাঙুন: hello.c-তে একবারে একটি করে পরিবর্তন করুন, compile করুন, error-টি লিখে রাখুন, তারপর আগের অবস্থায় ফেরত আনুন — (ক) printf-এর পরের ; মুছুন (খ) main-কে Main করুন (গ) শেষের } মুছুন।

    (ক) Compiler: expected ';' before 'return'. লক্ষ করুন, error দেখাবে লাইন ৫-এ (return-এর লাইনে), যদিও ভুল লাইন ৪-এ — কারণ compiler পরের শব্দ দেখেই বুঝেছে আগেরটি শেষ হয়নি।

    (খ) Compile হবে, কিন্তু linker বলবে undefined reference to `main'. Compiler-এর কাছে Main একটি সাধারণ বৈধ ফাংশন; শুধু startup code যে main খোঁজে, সেটি পাওয়া যায়নি।

    (গ) expected declaration or statement at end of input — compiler ফাইলের শেষে পৌঁছে গেছে, অথচ block বন্ধ হয়নি। এই অভ্যাসটিই সবচেয়ে কাজের: ইচ্ছা করে ভাঙলে প্রতিটি error-এর চেহারা মনে গেঁথে যায়।

  2. exit status নিয়ে পরীক্ষা: student.c-এর return 0; বদলে return 3; করুন। নিজের terminal-এ compile ও চালান, তারপর echo $? (বা Windows-এ echo %ERRORLEVEL%) লিখুন। এরপর চালান ./student && echo done — done ছাপা হলো কি? কেন?

    প্রোগ্রামের আউটপুট আগের মতোই আসবে, কারণ printf-এর কাজে কোনো পরিবর্তন হয়নি। কিন্তু echo $? দেখাবে 3. আর && echo done-এ done ছাপা হবে না — shell 3-কে ব্যর্থতা ধরে নিয়েছে। অর্থাৎ স্ক্রিনে সব ঠিক দেখালেও, অন্য প্রোগ্রামের চোখে আপনার প্রোগ্রাম “ব্যর্থ”। এজন্যই সঠিক exit status এত গুরুত্বপূর্ণ।

  3. পুরোনো কোড আধুনিক করুন: অংশ ৫-এর conio.h-ওয়ালা কোডটির প্রতিটি সমস্যা চিহ্নিত করুন এবং কাগজে standard C রূপে লিখুন। (কোনো লাইন কি পুরোপুরি বাদ দিতে হবে?)

    তিনটি পরিবর্তন: #include <conio.h>-এর বদলে #include <stdio.h> (আসল কোডে printf-এর header-ই ছিল না!); void main()-এর বদলে int main(void); আর getch(); পুরোপুরি বাদ দিয়ে তার জায়গায় return 0;. এছাড়া "Hello"-এর শেষে \n যোগ করা ভালো। ফলাফল হুবহু এই পর্বের hello.c-এর মতো দেখাবে।

  4. Smart quote শিকার: ফোনের WhatsApp-এ নিজেকে printf("Hi"); লিখে পাঠান (iPhone হলে সম্ভাবনা বেশি), তারপর কপি করে OnlineGDB-তে একটি প্রোগ্রামের ভেতরে পেস্ট করে চালান। কী দেখলেন? এবার একই লাইন তিনটি backtick দিয়ে ঘিরে পাঠিয়ে আবার চেষ্টা করুন।

    অনেক ফোনের কিবোর্ডে “smart punctuation” চালু থাকে, তাই কোট বাঁকা হয়ে যায় এবং stray '\342' ধরনের error আসে। (আপনার ফোনে এই সুবিধা বন্ধ থাকলে কোট সোজাই থাকবে, error-ও আসবে না — সেটিও একটি শিক্ষা: সমস্যাটি কিবোর্ডের সেটিং-নির্ভর।) Backtick দিয়ে ঘিরলে WhatsApp লেখাটিকে কোড হিসেবে monospace অক্ষরে দেখায়; তবে কোট বাঁকা হবে কি না, তা নির্ভর করে লেখার মুহূর্তে কিবোর্ড কী টাইপ করেছিল তার ওপর। সবচেয়ে নিরাপদ পথ: কোড পাঠান ফাইল হিসেবে বা GitHub Gist-এর লিংক দিয়ে।

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রম./hello চালানোর পর ঘটনাগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. ⌨️ Shell কমান্ডটি পড়ে OS-কে প্রোগ্রাম চালাতে বলে
  2. 🧠 OS loader ফাইলটি মেমরিতে তোলে
  3. 🚀 Startup code প্রস্তুতি সেরে main-কে ডাকে
  4. 🏠 main-এর ভেতরে printf লেখা ছাপায়
  5. ↩️ return 0 — startup code exit ডাকে
  6. ✅ Shell exit status 0 পায়

#include <stdio.h> লাইনটি আসলে কী করে?

  • printf ফাংশনের পুরো কাজের কোড প্রোগ্রামে যোগ করে
  • printf-এর পরিচয় (declaration) সহ একটি header ফাইলের লেখা এখানে বসিয়ে দেয়
  • প্রোগ্রাম চালু হওয়ার সময় প্রথমে এই লাইনটি চলে
  • স্ক্রিন ব্যবহারের অনুমতি চায়

প্রোগ্রাম চালু হলে সবার আগে আপনার লেখা কোন কোডটি চলে না, বরং অন্য কিছু চলে। সেটি কী?

  • printf ফাংশন
  • Compiler
  • C runtime startup code, যা পরে main-কে ডাকে
  • return 0

একটি প্রোগ্রাম return 2; দিয়ে শেষ হলো। তারপর ./prog && echo yes চালালে কী হবে?

  • yes ছাপা হবে না, কারণ 2 মানে ব্যর্থতা
  • yes ছাপা হবে, কারণ প্রোগ্রাম চলেছে
  • yes-এর বদলে 2 ছাপা হবে
  • Compile error হবে, কারণ main শুধু 0 ফেরত দিতে পারে

printf("%d and %c\n", 25, 'B'); কী ছাপবে?

  • %d and %c
  • B and 25
  • 25 and B\n
  • 25 and B (তারপর নতুন লাইন)

WhatsApp থেকে কপি করা কোডে error এলো: stray '\342' in program. সবচেয়ে সম্ভাব্য কারণ কী?

  • stdio.h ফাইলটি কম্পিউটারে নেই
  • সোজা কোটের বদলে বাঁকা (smart) কোট চলে এসেছে
  • একটি সেমিকোলন বাদ পড়েছে
  • GCC নষ্ট হয়ে গেছে, আবার ইনস্টল করতে হবে

AI একটি প্রোগ্রাম দিল যা শুরু হয় #include <conio.h> আর void main() দিয়ে। আপনার করণীয় কী?

  • নিজের কম্পিউটারে চললে আর কিছু করার দরকার নেই
  • শুধু conio.h মুছে দেওয়া
  • int main(void) ও standard header দিয়ে ঠিক করা, আর পরের বার prompt-এ standard C চাওয়া
  • অন্য একটি AI ব্যবহার করা

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. #include আনে declaration (পরিচয়); printf-এর আসল কোড আসে library থেকে, linker জোড়ে।
  2. main হলো entry point — OS আর startup code প্রস্তুতি সেরে তাকে ডাকে।
  3. return 0 = সফল, অশূন্য = সমস্যা; echo $? দিয়ে দেখা যায়, আর script, make, CI এটি পড়েই সিদ্ধান্ত নেয়।
  4. প্রতিটি statement-এর শেষে ;; C case-sensitive; স্পেস আর indentation মানুষের জন্য; comment-এ লিখুন “কেন”।
  5. সবসময় int main(void) — void main, conio.h, getch নয়; আর stray '\342' দেখলে বাঁকা কোট খুঁজুন।

পরের পর্বে: আজ printf দিয়ে 17 আর 4.75 ছাপালেন। কিন্তু কম্পিউটারের ভেতরে তো শুধু 0 আর 1 — তাহলে 17, 4.75 বা অক্ষর 'A' সেখানে আসলে কীভাবে থাকে? পরের পর্বে দেখব বিট থেকে দশমিক পর্যন্ত ডেটার আসল চেহারা।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান