Module ০ · C কেন, আর program জিনিসটা আসলে কী
Compiler, interpreter, আর Run চাপলে ভেতরে যা ঘটে
এই lesson-এ যা শিখবে
hello.cথেকে চলন্ত program পর্যন্ত চারটা ধাপের নাম বলতে পারবে।- একটা error message পড়ে বলতে পারবে কোন ধাপ সেটা বলছে।
- Compiler আর interpreter-র মধ্যে দেওয়া নেওয়ার হিসাবটা বলতে পারবে।
David একটা বর্গক্ষেত্রের কর্ণ বের করতে আট লাইন লিখল। সে <math.h> include করেছে, sqrt call করেছে, Run চেপেছে, আর উত্তর পেয়েছে এটা: undefined reference to 'sqrt'। সে তাকিয়ে থাকল। সে তো header-টা দিয়েছেই।
sqrt নামে কোনো function সে নিজে লেখেনি, তাই নালিশটা করছে অন্য কেউ। যে অংশটা তার code পড়ে, এটা সেই অংশ না।
David আসলে এই সত্যটার মুখোমুখি হচ্ছে যে "compile করা" একটা program না। এটা পরপর চারটা program, প্রতিটার নিজের কাজ আর নিজের নালিশ আছে। কে কথা বলছে সেটা জানলে বেশিরভাগ error message আর ভয় দেখায় না।
অনুবাদক আর দোভাষী
ধরো জাপানি ভাষায় লেখা একটা technical manual তোমার বাংলায় দরকার।
উপায় ক, compiler। একজন অনুবাদক পুরো manual নিয়ে এক সপ্তাহ কাজ করেন। তুমি একটা পুরো বাংলা বই পাও। অনুবাদটা একবারই হয়, তাই পরে পড়াটা যেকোনো বই পড়ার মতোই দ্রুত। মূল লেখায় ব্যাকরণের ভুল থাকলে সেটা ওই সপ্তাহেই ধরা পড়ে, তোমার এক পাতা পড়ার আগেই।
উপায় খ, interpreter। একজন দোভাষী তোমার পাশে দাঁড়িয়ে তুমি যত পড়ছ তত লাইন ধরে অনুবাদ করেন। তুমি সঙ্গে সঙ্গে শুরু করতে পারো। কিন্তু প্রতিবার পড়াই ধীর হয়, আর 400 নম্বর পাতার ভুলটা ধরা পড়ে তুমি 400 পাতায় পৌঁছানোর পর।
C হলো উপায় ক। Python, JavaScript আর PHP উপায় খ-র কাছাকাছি। ওই একটা সিদ্ধান্তই তোমার সামনে আসা বেশিরভাগ পার্থক্যের ব্যাখ্যা।
একটা C program compile হওয়ার চারটা ধাপ
hello.c compile করলে পরপর চারটা আলাদা program চলে। প্রতিটা একটা file নেয়, আর নতুন একটা file বানায়।
চতুর্থ ধাপটাই David-কে ধরেছে। Compiler খুশি ছিল: <math.h> কথা দিয়েছিল যে sqrt নামে একটা function কোথাও আছে। Linker-র কাজ হলো ওটার আসল machine code খুঁজে বের করা। অনেক Linux system-এ library-র অঙ্কের অংশটা আলাদা একটা file-এ থাকে। Command-এ -lm যোগ করে তুমি ওটা চেয়ে নাও।
মনে রেখো
<math.h>-র মতো একটা header হলো একটা প্রতিশ্রুতি, code না। এটা বলে "sqrt নামে একটা function একটা double নেয় আর একটা double ফেরত দেয়"। Code-টা থাকে library-তে। এই ভাগাভাগির জন্যই ধাপ 1 আর ধাপ 4 একমত না হতে পারে।
Error দেখলে এটা কেন কাজে লাগে
নিচের প্রতিটা message GCC-র সত্যিকারের output। Table-টা একটা তালিকা হিসেবে পড়ো: তোমার message-র চেহারাটা খুঁজে নাও, আর ওটাই বলে দেবে কে কথা বলছে।
| Message যা বলে | ধাপ | সাধারণ কারণ |
|---|---|---|
fatal error: stdioo.h: No such file or directory | Preprocessor | Header-র নামের বানান ভুল, বা ওটা বসানো নেই |
error: expected ';' before 'return' | Compiler | যে লাইনের নাম বলছে, তার উপরের লাইনে semicolon নেই |
error: 'count' undeclared (first use in this function) | Compiler | Declare করার আগেই নাম ব্যবহার, প্রায়ই টাইপের ভুল |
warning: implicit declaration of function 'squareOf' | Compiler | উপরে prototype ছাড়াই একটা function call করা |
warning: initialization of 'int' from 'char *' | Compiler | ভুল type-র একটা মান variable-এ বসানো |
warning: format '%s' expects argument of type 'char *' | Compiler | printf-র placeholder মানটার সঙ্গে মিলছে না |
warning: unused variable 'total' | Compiler | Declare হয়েছে, ব্যবহার হয়নি, প্রায়ই আধখানা edit |
undefined reference to 'area' | Linker | Function-র কথা দেওয়া হয়েছিল, কিন্তু কেউ লেখেনি |
undefined reference to 'sqrt' | Linker | অঙ্কের library জোড়া হয়নি; -lm যোগ করো |
Segmentation fault | Runtime | ঠিকঠাক build হয়েছে, তারপর নিজের না এমন memory ছুঁয়েছে |
Version-র একটা কথা। মাঝের যে দুইটা সারি, prototype না থাকা আর ভুল type, ওই দুইটা GCC 12-তে warning আর GCC 14-তে error।
দুইটা জিনিস খেয়াল করার মতো। একটা warning build থামায় না, আর তবুও সাধারণত সেটা ঠিকই বলে। আর একদম শেষ সারিতে কোনো compiler নেই। সেটা আমাদের এই lesson-র সবচেয়ে জরুরি বাক্যটায় নিয়ে যাচ্ছে।
"Compile হয়েছে" মানে "ঠিক আছে" না
Compiler ব্যাকরণ দেখে, মানে দেখে না। প্রতিবার ভুল উত্তর দেয় এমন program সে হাসিমুখে বানিয়ে দেবে। নিচের Example 3-এ তুমি একটা দেখবে।
gcc-র command line
gcc -std=c17 -Wall -Wextra hello.c -o hello
gccহলো সেই program, যে তোমার হয়ে চারটা ধাপই চালায়।-std=c17ভাষার standard বেছে দেয়। এই track C17 ব্যবহার করে।-Wall -Wextraকাজের warning-গুলো চালু করে। প্রথম দিন থেকেই এটা অভ্যাস করে ফেলো।hello.cতোমার input file।-o hellooutput-র নাম ঠিক করে। এটা না দিলেa.outনামে একটা file পাবে।
সাত লাইন। চলার পথে এই file-টাই চারটা file হয়ে যাবে।
#include <stdio.h>
int main(void)
{
printf("Hello, C.\n");
return 0;
}
Hello, C.
কী নেই সেটা খেয়াল করো। printf-র কোনো code এখানে নেই। তোমার file শুধু বলছে "ওটাকে call করো"। ওই প্রতিশ্রুতিটা রাখা হয় ধাপ 4-এ।
প্রথম অনুচ্ছেদের program-টা এখানে পুরোটা লেখা আছে।
#include <math.h>
#include <stdio.h>
int main(void)
{
double side = 2.0;
printf("The diagonal is %.4f\n", side * sqrt(2.0));
return 0;
}
The diagonal is 2.8284
Playground-এ এটা যেমন আছে তেমনই চলে। Linux মেশিনে হয়তো gcc david.c -o david -lm লাগবে, কারণ ওখানে অঙ্কের code আলাদা একটা library file-এ থাকে। একই source, একই compiler, শুধু linker-র নির্দেশ আলাদা।
কোনো warning নেই। কোনো error নেই। আত্মবিশ্বাসী একটা ভুল উত্তর।
#include <stdio.h>
int main(void)
{
int marks[2] = {7, 8};
int average = (marks[0] + marks[1]) / 2;
printf("Average of 7 and 8 is %d\n", average);
return 0;
}
Average of 7 and 8 is 7
7 আর 8-এর গড় 7.5। C-তে একটা পূর্ণসংখ্যাকে আরেকটা পূর্ণসংখ্যা দিয়ে ভাগ করলে দশমিকের পরের অংশটা ফেলে দেওয়া হয়। Compiler-র এ নিয়ে কোনো মত নেই, কারণ ব্যাকরণে কিছু ভুল নেই। এটা ঠিকভাবে সারাবে Module 4-এ।
Compiler-এ চালাওCompiler আর interpreter, পাশাপাশি
| Compiler (C, C++, Rust, Go) | Interpreter (Python, JavaScript, PHP) | |
|---|---|---|
| অনুবাদ কখন হয় | একবার, চালানোর আগে | প্রতিবার, চলার সময় |
| তৈরি program-র গতি | দ্রুত; এটা native machine code | ধীর; বারবার অনুবাদ হয় |
| Error কখন ধরা পড়ে | বেশিরভাগ চালানোর আগেই | অনেকগুলো শুধু ওই লাইনে পৌঁছালে |
| Output কোথায় চলে | একধরনের CPU আর system-র সঙ্গে বাঁধা | যেখানে interpreter আছে, সেখানেই |
| সাধারণ startup | একটা compile ধাপ, তারপর সঙ্গে সঙ্গে শুরু | সঙ্গে সঙ্গে শুরু, চলাটা ধীর |
| কিসে ভালো | Operating system, game, embedded device, contest | Script, web backend, data analysis |
তিন নম্বর সারিটার একটা প্রমাণ দেখো। এই Python program-টায় এমন একটা function-এ বানান ভুল আছে, যেটা কখনো call-ই হয় না।
def never_called():
prnt("this line is wrong")
print("Python starts running anyway")
Python তার লাইনটা ছেপে দিব্যি বেরিয়ে যায়। ভুলটা এমন একটা function-এ আছে যাকে কেউ call করছে না, তাই ওটা কেউ পড়েই না। একই রকম ভুল একটা C file-এ রাখো, compiler build করতে রাজি হবে না। সে প্রতিটা লাইন পড়ে, ওই লাইন চলবে কি না তা নিয়ে তার মাথাব্যথা নেই।
বাস্তবটা একটু ঘোলাটে: JIT
Java আর C# দুইটাই করে। ওরা প্রথমে bytecode নামের একটা মাঝামাঝি রূপে compile করে। Program চলার সময় একটা JIT (just-in-time) compiler ওই bytecode-র সবচেয়ে ব্যস্ত অংশগুলোকে সত্যিকারের machine code-এ বদলে দেয়।
আধুনিক JavaScript engine তোমার browser-র code-এ ঠিক এটাই করে। উপরের সাদামাটা ভাগটা এখনো তোমার পছন্দের হিসাবটা বুঝিয়ে দেয়। তবে 2026 সালে প্রায় কিছুই পুরোপুরি এক দিকের না।
Playground আসলে কী করে
Progsity Playground-র page-এ Run চাপলে কোনো জাদু হয় না। তোমার program আলাদা একটা মেশিনে যায়, একটা file-এ লেখা হয়, আর GCC 12 ওটা compile করে। Command-টা হলো gcc -O2 -std=c17 -lm, এতে -Wall নেই, -Wextra-ও নেই। চারটা ধাপই ওখানে চলে, ছবি 1-এর ক্রমেই।
এই -Wall না থাকাটা গুরুত্বপূর্ণ। GCC নিজে থেকে যেগুলো বলে, Playground শুধু সেগুলোই দেখায়। তাই prototype না থাকলে সেটা এখনো দেখবে, কিন্তু %s-কে একটা সংখ্যা দিলে কিছুই দেখবে না। উপরের syntax card-টা তোমার নিজের মেশিনের command, যেখানে তুমি সব warning চাও।
Compile হওয়া program-টা তারপর একটা sandbox-র ভেতরে চলে, সময়ের সীমা আর memory-র সীমা দিয়ে। এর output তোমার browser-এ ফিরে আসে। পুরো কৌশলটা এইটুকুই। তুমি সেই একই toolchain ব্যবহার করছ যেটা নিজের মেশিনে বসাতে হতো, শুধু বসাতে হচ্ছে না।
এটা কোথায় কাজে লাগছে
- Linux kernel. GCC বা Clang প্রায় তিন কোটি লাইনের উপর ঠিক এই চারটা ধাপ চালিয়ে এটা build করে। পুরো একটা build-এ হাজার হাজার
.ofile তৈরি হয়, আর linker সেগুলো জুড়ে একটা image বানায়। - CPython. Python interpreter নিজেই compile হওয়া একটা C program। তাই ভবিষ্যতে তোমার Python চলবে এমন কিছুর ভেতরে, যেটা আগে এই pipeline-টা পেরিয়ে এসেছে।
- Java Virtual Machine. HotSpot, মানে standard JVM, শুরু করে bytecode interpret করে, আর যে code বারবার চলে সেটার জন্য JIT compile-এ চলে যায়। এটাই উপরের table-র ঘোলাটে মাঝখান।
- Progsity Playground. তোমার file পাঠায় Progsity runner-এ, GCC 12 দিয়ে
-O2 -std=c17 -lm-এ compile করে, আর সীমা বেঁধে একটা sandbox-এ চালায়। একই ধাপ, আলাদা মেশিন।
যে ভুলগুলো সবাই করে
১. Error যে লাইনের নাম বলছে সেটা সারানো, যখন দোষটা উপরের লাইনের।
printf("Hi\n")
return 0;
GCC বলে error: expected ';' before 'return'। return লাইনটাতে কোনো সমস্যাই নেই। Compiler বাদ পড়া semicolon-টা টের পেয়েছে পরের শব্দে পৌঁছে। যে লাইনের নাম বলছে, সব সময় তার উপরের লাইনটা দেখো।
২. Function ঘোষণা করে সেটা কখনো না লেখা।
int area(int w, int h); /* a promise */
int main(void) {
printf("%d\n", area(3, 4)); /* nobody kept it */
return 0;
}
প্রতিশ্রুতি পেয়েই compiler সন্তুষ্ট। Linker না: undefined reference to 'area'। সমাধান হলো function-র শরীরটা লেখা, অথবা যে file-এ ওটা আছে সেটা জুড়ে দেওয়া।
৩. Program চলেছে বলে warning-কে পাত্তা না দেওয়া।
int n = 5;
printf("%s\n", n);
GCC বলে warning: format '%s' expects argument of type 'char *', but argument 2 has type 'int'। এটা build হয়। তারপর 5 সংখ্যাটাকে একটা memory address ধরে নেয় আর সাধারণত crash করে। যারা -Wall -Wextra চালু করে, তারা বাকিদের চেয়ে কয়েক দিন আগে bug খুঁজে পায়।
৪. পরিষ্কার build মানেই সঠিক program, এটা ভেবে নেওয়া।
উপরের Example 3 শূন্য warning-এ compile হয়, আর ভুল গড় ছাপে। Pipeline-র কোনো ধাপই মানে যাচাই করে না। সেই কাজটা তোমার, আর সেজন্যই এই track-র অনুশীলনগুলো সব সময় কোন input পরীক্ষা করতে হবে সেটা বলে দেয়।
Terminal-এ একটা হাতে ধরা হাঁটা। এটা করো Linux মেশিনে, macOS-এ, বা Windows-এ WSL-এ। Example 1 hello.c নামে save করো, তারপর নিচের চারটা command একটা একটা করে চালাও।
gcc -E hello.c -o hello.i # stop after stage 1
gcc -S hello.c -o hello.s # stop after stage 2
gcc -c hello.c -o hello.o # stop after stage 3
gcc hello.o -o hello # stage 4 only
কী খুঁজবে। hello.i খোলো: তোমার সাত লাইন এখন হাজারেরও বেশি, কারণ পুরো stdio.h এখানে বসিয়ে দেওয়া হয়েছে। তোমার নিজের শেষ ছয় লাইন এখনো নিচেই আছে, অবিকল।
hello.s খোলো: এটা assembly, আর এর কোথাও তুমি .ascii "Hello, C. পাবে, ভেতরে তোমার লেখা বার্তা সহ। hello.o লেখা না, তাই ওটা editor-এ খুলো না।
নিজে যাচাই করো। শেষে ./hello চালাও। এটা Hello, C. ছাপলে বুঝবে, একটা gcc command যা করে তুমি সেটাই হাতে করে ফেললে।
Example 1-কে ইচ্ছা করে চারবার ভাঙো, আর সত্যিকারের message-গুলো জমাও। প্রতিবারের পর ওটা আবার ঠিক করো।
চারটা ভাঙা। stdio.h-র বানান করো studio.h। printf লাইনের পরের semicolon মুছে দাও। printf-কে করো printff। লেখার ভেতরে %s বসিয়ে তার বদলে একটা সংখ্যা পাঠাও।
Playground কোনটা দেখায়। প্রথম দুইটা build থামিয়ে দেয়। তিন নম্বরটা prototype না থাকার warning দেয়, তারপর linker-র error। চার নম্বরটা কিছুই বলে না, কারণ format-র ওই পরীক্ষাটা -Wall-র ভেতরে, আর Playground সেটা পাঠায় না।
নিয়ম। প্রতিটা ভাঙার জন্য হুবহু message আর সেটা কোন ধাপ থেকে এসেছে, লিখে রাখো। Message অনুমান করবে না; tool যা ছেপেছে সেটাই টুকে নাও।
নিজে যাচাই করো। শেষে অন্তত দুইটা আলাদা ধাপ থেকে তিনটা আলাদা message পাওয়ার কথা। চার নম্বর ভাঙাটাই আসল শিক্ষা: চুপ করে থাকা মানে ঠিক আছে না। নিজের মেশিনে gcc -std=c17 -Wall -Wextra চালালে ওটার message-ও পাবে।
সোজা বাংলায় লেখো, তোমার laptop-এ compile করা program কেন তোমার ফোনে চলবে না। C-র source দুইটা মেশিনেই compile হতো, তাই উত্তরটা ভাষা না।
নিয়ম। দুইটা অনুচ্ছেদ। প্রথমটায় "machine code" শব্দ দুইটা থাকতে হবে, আর কোন ধাপে source আর portable থাকে না সেটার নাম থাকতে হবে। দ্বিতীয়টায় উপরের table ব্যবহার করতে হবে। সেখানে বোঝাতে হবে, একটা Python script বন্ধুকে email করে দিলে সেটা কেন এমনিই চলে।
নিজে যাচাই করো। ভালো উত্তর বলে, source portable আর ধাপ 3-র output portable না। তোমার উত্তর যদি ধাপের বদলে ভাষাকে দোষ দেয়, ছবি 1 আরেকবার দেখো।
যে প্রশ্নগুলো সবার মনে আসে
এই track করতে হলে কি GCC বসাতে হবে?
না। এখানকার প্রতিটা program Playground-এ চলে, যেটা আলাদা মেশিনে বসানো একই compiler। GCC বসাও যখন কয়েকটা file নিয়ে project বানাতে শুরু করবে, মানে Module 15-এ।
GCC আর Clang-র মধ্যে পার্থক্য কী?
একই ভাষার দুইটা compiler। দুইটাই এই চারটা ধাপ চালায়, আর দুইটাই এই track-র code নেয়। ওদের error message-র ভাষা আলাদা, আর Clang-রটা প্রায়ই পড়তে সহজ।
একটা semicolon বাদ পড়লে দশটা error আসে কেন?
কারণ প্রথম গোলমালের পর compiler তোমার program ভুলভাবে পড়ছে, আর যা পাচ্ছে তা বলে যাচ্ছে। সব সময় প্রথম error-টা সারিয়ে আবার build করো। বাকিগুলোর বেশিরভাগ সাধারণত উধাও হয়ে যায়।
Warning কি নিশ্চিন্তে ছেড়ে দেওয়া যায়?
ওটাকে এমন একটা error ভাবো, যেটা এখনো তোমাকে কামড়ায়নি। উপরের table-র প্রতিটা warning সত্যিকারের ভাঙা code বর্ণনা করে। পেশাদার project-গুলো ইচ্ছা করেই warning-কে error বানিয়ে build করে।
C কি সব সময় Python-র চেয়ে দ্রুত?
একই algorithm-র জন্য সাধারণত অনেকটাই, কারণ চলার সময় কোনো অনুবাদ হয় না। কিন্তু Python-এ ভালো algorithm C-তে খারাপ algorithm-কে প্রতিবার হারিয়ে দেয়। ভাষা বাছার চেয়ে algorithm বাছা বেশি জরুরি।
মূল কথা
- Compile করা মানে পরপর চারটা program: preprocessor, compiler, assembler, linker।
- প্রতিটা ধাপের নিজের error আছে, আর message-টাই বলে দেয় কে কথা বলল।
- Header একটা প্রতিশ্রুতি; library-র code জোড়া লাগে linker-এ, একদম শেষ ধাপে।
- Compiler চালানোর আগে একবার অনুবাদ করে; interpreter চলার সময় অনুবাদ করে।
- "Compile হয়েছে" মানে ব্যাকরণ ঠিক, উত্তর নিয়ে এটা কিছুই বলে না।
- Playground একটা sandbox-এ GCC 12 দিয়ে C17-এ চালায়,
-Wallছাড়া, একই চারটা ধাপ পেরিয়ে।
পরের lesson-এ তুমি তোমার কাটানো দিনটার ভেতরেই C খুঁজবে। শুরুটা যে alarm তোমাকে জাগাল সেটা দিয়ে, শেষটা এই page দিয়ে।
lesson ৪ শেষ
শেষ হলে চিহ্ন দিন, অগ্রগতি আপনার সাথে থাকবে।
পরেরটা: আজকের দিনে C চলছে কোথায়: তুমি রোজ ব্যবহার করো এমন ছয়টা সিস্টেম