Learn C Programming

lesson ৬ / ৮ · C-র সঙ্গে পরিচয়: প্রথম program, চিহ্ন ধরে ধরে

Module ১ · C-র সঙ্গে পরিচয়: প্রথম program, চিহ্ন ধরে ধরে

নিজে compile করে চালাও: Playground আর নিজের মেশিনের toolchain

Freeপড়া

এই lesson-এ যা শিখবে

  • Playground-এ input দিয়ে একটা program চালাতে পারবে, আর output-র ঘরটা পড়তে পারবে।
  • চাইলে একই program নিজের মেশিনে compile করে চালাতে পারবে।
  • -Wall -Wextra চালু করে warning পড়তে পারবে, আর -E, -S, -c আর -lm কী করে বলতে পারবে।

Playground হলো Singapore-র একটা মেশিনে sandbox-র ভেতরে বসানো সত্যিকারের একটা GCC। নিজের মেশিনের toolchain হলো সেই একই GCC, তোমার নিজের disk-এ। কোনোটা বেশি আসল না; দুইটাই একই compiler, দুই জায়গায়।

তাই এই lesson কিছু বসানোর নির্দেশ না। এই track-র সবটাই তোমার browser-এ চলে, চিরকাল। নিজের একটা কপি চাইলে দ্বিতীয় অর্ধেকটা পড়ো, আর তার আগে নিশ্চিন্তে ওটা বাদ দিয়ে যাও।

Playground: সত্যিকারের একটা GCC, অন্য কারো মেশিনে

এই track-র প্রতিটা example-র একটা Run link আছে, যেটা program আগেই বসানো অবস্থায় Playground খুলে দেয়। ওই page-র চারটা অংশ কাজের।

Editor তোমার program ধরে রাখে। Run button ওটা পাঠিয়ে দেয়, আর এক সেকেন্ড পরে output-র ঘর দেখায় সে কী ছেপেছে। Input-র বাক্সে তুমি লেখো program যা পড়বে, আর এই module-র প্রতিটা problem ওভাবেই পরীক্ষা করা হয়।

তোমার file ওই মেশিনের disk-এ লেখা হয়, compile হয়, আর একটা sandbox-র ভেতরে চলে, সময়ের সীমা আর memory-র সীমা দিয়ে। Program যদি এমন input-র জন্য বসে থাকে যেটা কখনো আসে না, সময়ের সীমা ওটা থামিয়ে দেয়, আর সেটা একটা সত্যিকারের ফল, আটকে থাকা না।

একজন দর্শক মিনিটে কতবার চালাতে পারে, তারও একটা সীমা আছে। "Too many requests" message-র মানে অপেক্ষা করো, ভেঙে গেছে না।

তাই একটা Playground run মানে সত্যিকারের মেশিনে একটা compile আর একটা চালানো। এটা তোমার কাছ থেকে একটা জিনিসই লুকায়: command line।

Playground যে command-টা ব্যবহার করে

এটাই, আর এটা বাঁধা, মানে page থেকে তুমি এটা বদলাতে পারবে না:

gcc -O2 -std=c17 -lm

এটা থেকে তিনটা জিনিস বেরোয়, আর তিন নম্বরটাই মনে রাখার মতো।

-O2 চায় হিসাব করে সাজানো machine code, তাই program দ্রুত চলে। -std=c17 হলো এই track যে standard শেখায়। -lm ওখানে আগেই আছে, তাই অঙ্কের library সব সময় তোমার হয়ে জোড়া থাকে।

আর ওতে -Wall নেই। GCC নিজে থেকে যে message দেয়, Playground সেগুলোই ছাপে, তার বেশি কিছু না।

এর একটা ফল আছে, আর সেটা সোজা বলে দেওয়াই ভালো। নিজের মেশিনে তুমি যে message দেখো, Playground-এ সেটা চুপ থাকতে পারে। একটা অব্যবহৃত variable, %s-কে একটা সংখ্যা দেওয়া, একটা = দিয়ে লেখা if: তিনটাই ওখানে চুপ, আর তিনটাই -Wall-র নিচে warning।

কারণটা ভুলে যাওয়া না, একটা নিয়ম। Compiler-র option প্রতি ভাষায় বাঁধা, আর একটা client যে option যোগ করতে পারে তার তালিকা খালি। প্রতিটা পাঠক একই toolchain পায়।

তাই Playground হলো সৎ মেঝেটা: ওখানে build হলে তোমার শিক্ষকের কাছেও build হবে। মেঝের চাওয়ার চেয়ে বেশি warning খুঁজতে তুমি নিজের মেশিনে যাও।

GCC বসানো, তিনটা operating system-এ

কেবল যদি তুমি চাও। প্রতিটার জন্য পাঁচ লাইন।

Linux. একটা terminal খোলো। Debian বা Ubuntu-তে চালাও sudo apt update, তারপর sudo apt install build-essential। Fedora-তে চালাও sudo dnf install gcc। gcc --version দিয়ে মিলিয়ে নাও। কাজ শেষ।

macOS. Terminal খুলে চালাও xcode-select --install, তারপর যে dialog আসে সেটা মেনে নাও। এতে Command Line Tools বসে, যার ভেতরে একটা compiler আছে। gcc --version দিয়ে মিলিয়ে নাও। macOS-এ ওই নামটা আসলে Clang চালায়, আর এই track-র সবকিছুর জন্য সেটা ঠিকই আছে।

Windows, যে পথটা আমরা বলব। WSL বসাও, মানে Windows Subsystem for Linux, PowerShell-এ administrator হিসেবে wsl --install চালিয়ে আর মেশিন restart করে। ও যে Ubuntu app দেয় সেটা খুলে উপরের Linux-র নির্দেশ মানো। এতে Windows-র পাশেই তোমার একটা সত্যিকারের Linux toolchain হলো, আর পেশাদার C-র কাজ বেশিরভাগ ওটাই ব্যবহার করে।

Windows, WSL ছাড়া। MSYS2 তার নিজের site থেকে বসাও, ওটার UCRT64 shell খোলো, আর চালাও pacman -S mingw-w64-ucrt-x86_64-gcc। অন্য terminal-এও gcc চাইলে ওটার bin folder PATH-এ যোগ করো। এটা সত্যিকারের একটা GCC, আর একটা তফাত নিচে কাজে লাগবে।

তাই প্রতিটা পথ একই জায়গায় শেষ হয়: একটা gcc, যেটা তুমি prompt-এ লিখতে পারো।

যে চারটা command তুমি সত্যিই লিখবে

একটা program hello.c নামে save করো, আর এই module-র সবকিছু থেকে তুমি চারটা command দূরে।

  1. Compile. gcc -std=c17 -Wall -Wextra hello.c -o hello তোমার file পড়ে hello নামে একটা program লেখে।
  2. চালাও. Linux আর macOS-এ ./hello; Windows-এ hello.exe বা .\hello.exe।
  3. Status পড়ো. echo $? ছাপে main যে সংখ্যাটা ফেরত দিয়েছে, যেটা নিয়ে lesson 1 বলেছিল ওটা সাজসজ্জা না।
  4. অঙ্কের library জুড়ে দাও. gcc -std=c17 hello.c -o hello -lm, Linux-এ sqrt, pow বা sin call করলেই এটা লাগে।

শেষেরটা নিয়ে দুইটা কথা। Linux-এ অঙ্কের code আলাদা একটা library file-এ থাকে। -lm বাদ দিলে linker তোমাকে দেয় undefined reference to 'sqrt', আর সেটা Module 0-র lesson 4 কাজ করে দেখানো। Windows-এ MSYS2-তে, আর macOS-এ, ওটা flag ছাড়াই জোড়া লাগে। Playground সব সময় -lm পাঠায়, তাই ওখানে প্রশ্নটাই ওঠে না।

তাই -Wall -Wextra হলো অভ্যাসটা, আর -lm হলো সেই flag, যেটা তুমি ঠিক একবার ভুলবে।

ধাপের flag: pipeline আগেই থামিয়ে দেওয়া

Module 0-র lesson 4 চারটা ধাপ বলেছিল: preprocessor, compiler, assembler, linker। তিনটা flag প্রথম তিনটার প্রতিটার পরেই থামিয়ে দেয়, আর ধাপগুলো সত্যি, এটা দেখার সবচেয়ে সস্তা উপায় ওগুলোই।

Flagকার পরে থামেতুমি যা পাওএটা লেখা?
-EPreprocessorhello.i: তোমার file, প্রতিটা header বসানো অবস্থায়হ্যাঁ, আর লম্বা
-SCompilerhello.s: তোমার CPU-র assemblyহ্যাঁ, আর তোমার string ওতে আছে
-cAssemblerhello.o: machine code, এখনো program নানা, ওটা খুলো না
কিছুই নাLinkerhello: চালানো যায় এমন একটা programনা

একবার gcc -E hello.c | wc -l চালিয়ে দেখো। তোমার সাত লাইন হাজারের বেশি হয়ে যায়, মানে stdio.h এসে বসেছে। এতেই বোঝা যায়, header বাদ পড়া কেন ধাপ 1-র ব্যর্থতা।

একটা terminal, পাঁচটা command, প্রতিটার output সহ terminal $ gcc -std=c17 -Wall -Wextra hello.c -o hello কোনো output নেই: পরিষ্কার build চুপ থাকে $ ./hello Order 15 boxes $ gcc -std=c17 -Wall -Wextra marks.c -o marks marks.c:6:9: warning: unused variable 'total' $ gcc -std=c17 diagonal.c -o diagonal -lm $ ./diagonal Diagonal: 7.071
ছবি 1। পাঁচটা command আর তাদের সত্যিকারের output। যে build কিছুই ছাপে না, সেটাই কাজ করা build; মাঝের warning-টা নিচের Example 2 বোঝায়।

তোমার প্রথম warning পড়া

Warning মানে compiler বলছে "এটা বৈধ, আর আমার মনে হয় না তুমি এটাই চেয়েছ"। এটা কখনো build থামায় না।

যে তিনটার সঙ্গে সবার আগে দেখা হবে, তিনটাই -Wall-র নিচে, আর তিনটাই ওটা ছাড়া চুপ:

  • warning: unused variable 'total'। তুমি ওটা declare করেছ আর কখনো ব্যবহার করোনি, যার মানে সাধারণত একটা edit আধখানা পড়ে আছে।
  • warning: format '%s' expects argument of type 'char *', but argument 2 has type 'int'। Placeholder আর মানটা মিলছে না, আর এটার শেষটা সাধারণত একটা crash।
  • warning: suggest parentheses around assignment used as truth value। Lesson 2-র = আর ==-র ফাঁদটা ধরা পড়ল।

প্রতিটাকে এমন একটা error ভাবো, যেটা এখনো তোমাকে কামড়ায়নি। যারা warning চালু রেখে build করে, তারা বাকিদের চেয়ে কয়েক দিন আগে bug খুঁজে পায়, আর পেশাদার project ইচ্ছা করেই warning-কে error বানিয়ে রাখে।

তাই -Wall -Wextra-তে পরিষ্কার একটা build, Playground-এ পরিষ্কার build-র চেয়ে জোরালো একটা দাবি, আর দুইটার তফাত ওইটুকুই।

gcc-র command line, টুকরো ধরে ধরে

gcc -std=c17 -Wall -Wextra hello.c -o hello
 |     |         |           |      |
 |     |         |           |      the name of the program to write
 |     |         |           your input file
 |     |         turn on the useful warnings
 |     pick the language standard
 the program that runs all four stages for you
  • -o hello বাদ দিলে a.out নামে একটা file পাবে, Windows-এ a.exe।
  • -std=c17 গুরুত্বপূর্ণ: একই file বৈধ C17 হতে পারে আর C89 হিসেবে বাতিল হতে পারে।
  • -Wall -Wextra দুইটা flag, একটা না। আর -Wall আসলে সবগুলোই না।
  • Linux-এ কোনো অঙ্কের function call করলে তোমার file-র পরে শেষে -lm যোগ করো।
Example 1: সবার আগে যে program-টা compile করবে

C-তে নতুন কিছু নেই। আসল কথাটা command: এই file -Wall -Wextra-তে পরিষ্কার build হয়, তাই পরে compiler তোমাকে যা বলবে সেটা তোমার edit নিয়েই।

#include <stdio.h>

int main(void)
{
    int physics = 37;
    int chemistry = 41;
    int total = physics + chemistry;

    printf("Total: %d\n", total);
    printf("Subjects: 2\n");
    return 0;
}
Total: 78
Subjects: 2

এটা marks.c নামে save করো, gcc -std=c17 -Wall -Wextra marks.c -o marks দিয়ে build করো, আর ./marks চালাও। পরিষ্কার build কিছুই ছাপে না, আর এটা সবাইকে একবার অবাক করে।

Compiler-এ চালাও
Example 2: একটা warning, আর তার দাম

একটা লাইন যোগ হলো। Output একটুও বদলায় না, আর compiler-র এখন কিছু বলার আছে, তবে কেবল তুমি চাইলে।

#include <stdio.h>

int main(void)
{
    int marks = 90;
    int total = 0;          /* declared, then never used again */

    printf("Marks: %d\n", marks);
    return 0;
}
Marks: 90

Playground-এ: কোনো message নেই। gcc -std=c17 -Wall -Wextra দিয়ে: যে লাইনে ওটা declare হয়েছে সেখানে warning: unused variable 'total' [-Wunused-variable]।

ছোট একটা warning, আর এর পেছনে বড় একটা অভ্যাস। দশবারের নয়বার একটা অব্যবহৃত variable মানে এমন একটা লাইন যেটা তুমি শেষ করতে চেয়েছিলে, আর দশ নম্বরটা এমন variable যেটা মুছে দেওয়া উচিত।

Compiler-এ চালাও
Example 3: যে program-র -lm লাগে

একটা অঙ্কের function, একটা সত্যিকারের উত্তর, আর linking বোঝার আগেই beginner-রা যে flag-র সামনে পড়ে।

#include <math.h>
#include <stdio.h>

int main(void)
{
    double side = 5.0;

    /* sqrt is declared in math.h and lives in the maths library. */
    double diagonal = side * sqrt(2.0);

    printf("Diagonal: %.3f\n", diagonal);
    return 0;
}
Diagonal: 7.071

Playground-এ এটা যেমন আছে তেমনই চলে, কারণ বাঁধা command line-এ -lm আছে। Linux-এ gcc diagonal.c -o diagonal তোমাকে দেয় undefined reference to 'sqrt', আর gcc diagonal.c -o diagonal -lm দেয় না। macOS আর MSYS2-তে দুইভাবেই জোড়া লাগে।

একই source, একই compiler, একটা flag নিয়ে তিনটা আলাদা উত্তর। ওটাই linker আলাদা একটা program হওয়ার ফল, ঠিক যেমন Module 0 বলেছিল।

Compiler-এ চালাও

এটা কোথায় কাজে লাগছে

  • দুনিয়ার প্রতিটা CI pipeline. একটা build server মানুষ ছাড়াই এই একই command চালায়, আর warning ছাড়া build প্রায়ই merge-র শর্ত। এখানে তুমি যে অভ্যাসটা গড়ছ, চাকরিতে ওটাই লাগে।
  • Linux kernel. এর build লম্বা একটা warning flag-র তালিকা চালু করে আর অনেকগুলোকে error ধরে। তিন কোটি লাইনে, যে warning কেউ পড়ে না, সেটা এমন warning যেটা কেউ সারায় না।
  • Progsity runner. Run button-র পেছনের মেশিনটা তোমার file ওই বাঁধা command line দিয়ে compile করে আর একটা sandbox-এ চালায়। তারপর তোমার program যা ছেপেছে সেটা ফেরত পাঠায়। একই GCC, অন্য কারো disk।
  • তোমার পড়া প্রতিটা Makefile. একটা Makefile মূলত এই flag-গুলোই, একবার লিখে রাখা, যাতে কারো মনে রাখতে না হয়, আর সেটা Module 15।

যে ভুলগুলো সবাই করে

১. hello নামে একটা program খোঁজা, যার নামই তুমি দাওনি।

gcc hello.c
./hello

Shell উত্তর দেয় bash: ./hello: No such file or directory। GCC-র কোনো message নেই, কারণ সে তোমার কথাই শুনেছে: সে লিখেছে a.out। -o hello যোগ করো, বা ./a.out চালাও।

২. Linux-এ -lm ভুলে যাওয়া।

gcc -std=c17 diagonal.c -o diagonal

Linker বলে undefined reference to 'sqrt', আর compiler কিছুই বলেনি, কারণ math.h কথা দিয়েছিল function-টা আছে। পুরো track-এ ধাপ 1 আর ধাপ 4-র একমত না হওয়ার এটাই সবচেয়ে পরিষ্কার উদাহরণ।

৩. File বদলে পুরনো program-টাই চালানো।

./hello

কোনো message নেই, আর output তোমার আগের রূপটার, যেটা তোমার এক ঘণ্টা গোলমালে কাটাতে পারে। একটা source file program না। প্রতিটা edit-র পর আবার compile করো, বা একটা Makefile-কে মনে রাখতে দাও।

৪. Warning নিয়ে Playground আর নিজের মেশিন একমত, এটা ধরে নেওয়া।

int n = 5;
printf("%s\n", n);

Playground-এ: কিছুই না, কারণ format-র ওই পরীক্ষাটা -Wall-র ভেতরে। নিজের মেশিনে -Wall দিলে: warning: format '%s' expects argument of type 'char *', but argument 2 has type 'int'। দুইটাই একই GCC 12-র আচরণ, আলাদা flag-এ, আর program দুই জায়গাতেই ভাঙা।

মাথা খাটাও

Bob একটা file তিনবার compile করল, কেবল flag বদলে, আর একই source থেকে তিনটা আলাদা ফল পেল।

gcc -std=c17 quiz.c -o quiz
gcc -std=c17 -Wall -Wextra quiz.c -o quiz
gcc -std=c89 -Wall -Wextra -pedantic-errors quiz.c -o quiz

File-টা এই lesson-র Example 2, একটা বদল নিয়ে: total-র declaration printf-র নিচে সরানো। বলো তিনটা command-র প্রতিটা কী ছাপে, আর কোনটা build করতেই রাজি হয় না। তারপর বলো Bob কোন ফলটা বিশ্বাস করবে।

তিনটার দুইটা তফাত warning নিয়ে, আর একটা এমন একটা নিয়ম নিয়ে যেটা দুইটা standard-র মাঝে বদলে গেছে। জিজ্ঞেস করো, C89 একটা declaration কোথায় বসতে দিত, আর শেষের ওই flag একটা নালিশকে কী বানিয়ে দেয়।

অনুশীলন ১সহজ

এই program-র warning সরাও, আর output-র একটা character-ও বদলাবে না।

#include <stdio.h>

int main(void)
{
    int marks = 90;
    int bonus = 5;

    printf("Marks: %d\n", marks);
    return 0;
}

Compiler কী বলে। -Wall -Wextra দিয়ে: warning: unused variable 'bonus'। Playground-এ: কিছুই না, তাই এই অনুশীলনটা নিজের মেশিনে করতে হবে, বা মন দিয়ে পড়ে।

দুইটা সারাই, একটা ঠিক উত্তর। লাইনটা মুছে দিলে warning যায়। Variable-টাকে printf-এ ব্যবহার করলে warning যায় আর output বদলে যায়, যেটা কাজের শর্তে নিষেধ। ঠিক করো কাজটা কোনটা মানে, আর এক বাক্যে বলো আরেকটা কেন খারাপ।

নিজে যাচাই করো। gcc -std=c17 -Wall -Wextra দিয়ে build করে একটাও কথা শুনবে না। Output-কে তখনো হুবহু Marks: 90 থাকতে হবে।

Compiler-এ চালাও
অনুশীলন ২মাঝারি

এমন একটা program লেখো যেটা math.h-র pow দিয়ে 2-র ঘাত 10 ছাপে, আর একটা comment যোগ করো, যেটা বোঝায় Linux-র build-এ -lm কেন লাগে।

Output. ঠিক এক লাইন: 2 to the 10 is 1024।

নিয়ম। pow(2.0, 10.0) ব্যবহার করো আর %.0f দিয়ে ছাপো, কারণ pow একটা double ফেরত দেয়। Comment-এ linker-র কথা থাকতে হবে, আর ওটা শুধু flag-র নাম আবার বলবে না।

#include <math.h>
#include <stdio.h>

int main(void)
{
    /* Your one comment goes here, about the linker. */
    printf("2 to the 10 is %.0f\n", pow(2.0, 10.0));
    return 0;
}

নিজে যাচাই করো। Playground-এ এটা অবিকল চলে। Linux-এ একবার -lm ছাড়া build করে linker-র error-টা দেখো, তারপর একবার ওটা দিয়ে। ইচ্ছা করে ওই error পড়াটাই এই অনুশীলনের আসল কাজ।

Compiler-এ চালাও

যে প্রশ্নগুলো সবার মনে আসে

  • এই track শেষ করতে কি একটা compiler বসাতেই হবে?

    না। প্রতিটা lesson, প্রতিটা example আর প্রতিটা graded problem Playground-এ চলে। কয়েকটা file নিয়ে project বানাতে চাইলে তখন বসাও, মানে Module 15-এ।

  • Windows-এ WSL না MSYS2?

    পারলে WSL। ওটা তোমাকে সেই একই Linux toolchain দেয় যেটা server আর CI ব্যবহার করে, তাই error message আর flag তোমার পড়া জিনিসের সঙ্গে মেলে। MSYS2 হালকা, আর এই track-র জন্য পুরোপুরি ভালো।

  • Playground-এ আমি নিজে -Wall যোগ করতে পারি না কেন?

    কারণ command line প্রতি ভাষায় বাঁধা, আর একটা page যে option যোগ করতে পারে তার তালিকা ইচ্ছা করেই খালি। সবার program একই ভাবে compile হয়, আর সেটাই একটা graded run-কে ন্যায্য রাখে।

  • GCC-র বদলে Clang চলবে?

    চলবে। এই track-র সবকিছুই সে নেয়, আর তার message প্রায়ই বেশি পরিষ্কার। মনে রাখো, warning-র হুবহু ভাষা আলাদা, তাই এখানকার একটা message তোমারটার সঙ্গে শব্দে শব্দে না মিলতে পারে।

  • Terminal-র বদলে একটা IDE ব্যবহার করা যায়?

    নিশ্চয়ই, আর বেশিরভাগ পেশাদার দুইটাই করে। তবু চারটা command শিখে রাখো: IDE-র build button ওগুলোই চালাচ্ছে, আর ওটা ফেল করলে কারণটা terminal-এই পাবে।

মূল কথা

  • Playground হলো sandbox-এ সত্যিকারের GCC 12, আর তার বাঁধা command line gcc -O2 -std=c17 -lm।
  • ওখানে -Wall নেই, তাই নিজের মেশিনে দেখা একটা warning Playground-এ চুপ থাকতে পারে।
  • নিজের মেশিনে gcc -std=c17 -Wall -Wextra file.c -o name হলো গড়ে তোলার অভ্যাসটা।
  • পরিষ্কার একটা build কিছুই ছাপে না।
  • -E, -S আর -c থামে preprocessor, compiler আর assembler-র পরে।
  • -lm অঙ্কের library জোড়ে, যেটা Linux-র লাগে আর Playground আগেই পাঠায়।

এতে Part Zero শেষ। পরে আসছে এই module-র graded problem-র সেট, hidden test সহ দশটা problem, আর তারপর module test।

lesson ৬ শেষ

শেষ হলে চিহ্ন দিন, অগ্রগতি আপনার সাথে থাকবে।

পরেরটা: Problem: প্রথম program-গুলো