Learn C Programming

lesson ৩ / ৭ · C কেন, আর program জিনিসটা আসলে কী

Module ০ · C কেন, আর program জিনিসটা আসলে কী

C-র গল্প: Bell Labs-র এক ঘর থেকে তোমার ফোন পর্যন্ত

Freeপড়া

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

  • ALGOL 60 থেকে Rust পর্যন্ত একটা সময়রেখায় C-কে বসাতে পারবে।
  • 1972 সালে Unix-র দলটার কেন নতুন একটা ভাষা দরকার হলো, বলতে পারবে।
  • C-র syntax ধার করা তিনটা ভাষার নাম আর কে কী ধার করেছে, বলতে পারবে।

সময়টা 1969, নিউ জার্সির Bell Labs-র পেছনের একটা ঘর। ছোট একটা দল একটা PDP-7 জোগাড় করেছে, যে minicomputer-টা আর কারো দরকার ছিল না। ওরা তাতে একটা operating system বানাচ্ছে। নাম দিয়েছে Unix। কাজ করছে।

কিন্তু সেটা ওই একটা মেশিনের সঙ্গে শেকলে বাঁধা। লেখা হয়েছে assembly language-এ, আর assembly প্রতিটা processor-এ আলাদা। নতুন computer-এ গেলে শূন্য থেকে আবার শুরু।

যে সমস্যায় স্রষ্টারা আটকে থাকেন, ভাষাগুলোর চেহারা সেভাবেই তৈরি হয়। ওটাই ছিল সমস্যা। C হলো তার উত্তর, আর C-র প্রায় প্রতিটা খটকা লাগা কোণ এটা জানলে মানে পেয়ে যায়।

Assembly-র সমস্যা

Assembly language মানে processor যে নির্দেশগুলো বোঝে, তার প্রায় শব্দে শব্দে লেখা একটা রূপ। এটা দ্রুত আর নিখুঁত। আবার এটা একটা নির্দিষ্ট chip পরিবারের সঙ্গে বাঁধা।

তাই assembly-তে লেখা operating system নড়তে পারে না। নতুন মেশিন মানেই হাজার হাজার লাইন হাতে, নির্ভুলভাবে আবার লেখা। Bell Labs চাইছিল এমন একটা ভাষা, যেটা hardware-র এত কাছে যে দিয়ে kernel লেখা যায়। আবার এতটা সাধারণ যে Unix-কে নতুন মেশিনে নেওয়া যায়।

তখনকার কোনো ভাষা দুইটাই পারত না। সেকালের high-level ভাষাগুলো মেশিনটাকে ঢেকে রাখত। Assembly ঢাকত না, আবার ভ্রমণও করত না।

BCPL থেকে B, B থেকে C

শেকলটার শুরু Cambridge-এ। Martin Richards 1967 সালে BCPL লেখেন, CPL নামের আগের একটা ভাষা ছেঁটে। CPL নিজেও এসেছিল ALGOL 60 থেকে। তারপর Ken Thompson BCPL-কে আরো ছেঁটে 1969 সালের দিকে বানান B।

B-র একটা বড় সীমা ছিল। ওখানে প্রতিটা data ছিল একটা সাদামাটা machine word। B বলতে পারত না "এটা একটা character, ওটা একটা পূর্ণসংখ্যা"। তিনটা অংশ নিয়ে একটা record বর্ণনা করার কোনো উপায় ওর ছিল না।

Dennis Ritchie 1972 সালের দিকে Bell Labs-এ এটা ঠিক করেন। তিনি যোগ করেন একটা type system, মানে ভাষাটা জানে প্রতিটা data কোন ধরনের জিনিস। বাকি সবকিছু ছোটই রাখেন। ফলাফল হলো C, নামটা এসেছে কারণ এটা এসেছে B-র পরে। Programmer-রা সব সময় কবি হন না।

1973: Unix আবার লেখা হলো C-তে

এই মুহূর্তটাই আসল। 1973 সালে Unix-র kernel নিজেই C-তে আবার লেখা হয়। Operating system হলো সেই software যেটা hardware-র সঙ্গে সবচেয়ে শক্ত করে বাঁধা। সেটাই এখন এমন একটা ভাষায় লেখা, যা মেশিন থেকে মেশিনে যেতে পারে।

এই মাপে আগে এটা কেউ করেনি, আর তাতেই তর্কটা মিটে যায়। এক দশকের মধ্যে system লেখার উপায় হয়ে দাঁড়ায় C। এই বছর তুমি যত operating system ব্যবহার করবে, সবগুলোই ওই সিদ্ধান্তটা বয়ে বেড়াচ্ছে।

এভাবে ভাবো

Ritchie তিনটা সিদ্ধান্ত নিয়েছিলেন, আর C-র চরিত্রের বেশিরভাগটা ওখান থেকেই। Programmer-কে বিশ্বাস করো: C তোমাকে বিপজ্জনক কাজ করতে দেবে, ধরে নেবে তুমি জেনেই করছ। ভাষা ছোট রাখো, library আলাদা রাখো: C নিজে ছাপতেই জানে না। সেজন্যই এটা 2 KB memory-র একটা smart card-এ এঁটে যায়।

মেশিনটা ঢেকে দিও না: একটা char এক byte, আর একটা pointer একটা address। তুমি যা লেখো তার প্রায় প্রতিটা লাইন সত্যিকারের কিছুর সঙ্গে মেলে।

Standard-গুলো, আর কোনটা তোমাকে কী দিল

C 1972 সালে জমে যায়নি। এটাকে standard করা হয়, তারপর মোটামুটি প্রতি দশকে একবার নতুন করে লেখা হয়। নিচের প্রতিটা সারির ফল তুমি এই track-এ পাবে।

সালনামযা যোগ হলো আর তুমি সত্যিই পাবে
1978K&R CKernighan আর Ritchie-র বই। তখনো কোনো standard নেই, তাই বইটাই ছিল রেফারেন্স। Function লেখা হতো নামের নিচে আলাদা লাইনে parameter-র type দিয়ে।
1989ANSI C, তারপর 1990-এ ISO C90প্রথম official standard। Function prototype (int add(int a, int b);), void keyword, আর সংজ্ঞায়িত একটা standard library।
1999C99// comment। Block-র মাঝখানে আর for-র ভেতরে variable declare করা। long long। <stdbool.h> দিয়ে bool।
2011C11Thread, আরো ভালো Unicode সমর্থন, আর _Static_assert, যে যাচাইটা compiler build-র সময় চালায়।
2018C17Bug সারানো আর পরিষ্কার ভাষা, নতুন কোনো feature নেই। এই track এই standard দিয়েই compile করে।
2024C23nullptr, constexpr, আর bool, true, false সত্যিকারের keyword হিসেবে। পুরোনো ধাঁচের function definition বাদ দেওয়া হলো।

পরের চারটা program একই ধারণা, চার যুগে লেখা। চারটাই আজও compile হয়। এগুলোকে একটা সময়রেখা হিসেবে পড়ো, মুখস্থ করার code হিসেবে না।

Example 1: 1978 সালের K&R ধাঁচ

Parameter-র type কোথায় বসেছে খেয়াল করো। ওগুলো function-র নামের নিচে, নিজেদের আলাদা লাইনে।

#include <stdio.h>

/* K&R style: the parameter types come after the name. */
int add(a, b)
int a;
int b;
{
    return a + b;
}

int main()
{
    printf("%d\n", add(2, 3));
    return 0;
}
5

এটা এখনো C17-এ build হয়। সবচেয়ে নতুন standard চাইলে compiler নালিশ করে: warning: old-style function definition [-Wold-style-definition]। C23 এই ধাঁচটা বাদ দিয়েছে। পঞ্চাশ বছরের পুরোনো code এখনো ধীরে ধীরে অবসরে যাচ্ছে।

Compiler-এ চালাও
Example 2: 1989 সালের C89 ধাঁচ

দুইটা বদল এখানে আসে। একটা prototype function-টাকে ব্যবহারের আগেই ঘোষণা করে দেয়। আর প্রতিটা variable declare হয় block-র একদম উপরে, অন্য কোনো statement-র আগে।

#include <stdio.h>

/* C89: a prototype, and every variable declared at the top of the block. */
int add(int a, int b);

int main(void)
{
    int i;
    int total;

    total = 0;
    for (i = 1; i <= 5; i++) {
        total = total + i;
    }
    printf("%d\n", add(total, 0));
    return 0;
}

int add(int a, int b)
{
    return a + b;
}
15

আসল লাভটা prototype-এ। এটা compiler-কে যাচাই করতে দেয় যে তুমি add-কে দুইটা পূর্ণসংখ্যা দিয়ে call করেছ। Prototype আসার আগে ভুল call চুপচাপ আজেবাজে ফল দিত।

Compiler-এ চালাও
Example 3: 1999 সালে C99 যা বদলাল

এই version-টাকে আধুনিক লাগে, আর এর বয়স ছাব্বিশ বছর।

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

int main(void)
{
    // C99 added this comment style.
    long long big = 9000000000LL;
    bool ready = true;

    int total = 0;                  // declare where you need it
    for (int i = 1; i <= 5; i++) {  // and inside the for
        total += i;
    }

    printf("%d %lld %d\n", total, big, ready);
    return 0;
}
15 9000000000 1

C99-র চারটা সংযোজন এই লাইনগুলোতেই আছে। সেগুলো হলো // comment, int-এ না আঁটা সংখ্যার জন্য long long, header থেকে আসা bool, আর for-র ভেতরে i declare করা। Module 2 থেকে তুমি চারটাই লিখবে।

Compiler-এ চালাও
Example 4: C11-র একটা যাচাই, যেটা build-র সময় চলে

C11 এমন একটা উপায় দিল, যাতে তুমি ইচ্ছা করে compiler-কে build করতে বারণ করতে পারো।

#include <stdio.h>

/* C11 added a check the compiler runs, not the program. */
_Static_assert(sizeof(int) >= 4, "this code needs int to be at least 4 bytes");

int main(void)
{
    printf("int is %zu bytes here\n", sizeof(int));
    return 0;
}
int is 4 bytes here

এই program-টা এমন মেশিনে নাও যেখানে int দুই byte, তাহলে এটা build-ই হবে না। Message বলে দেবে কেন। Run-এর সময় গিয়ে জানার চেয়ে এটা অনেক দয়ালু।

Compiler-এ চালাও

C-র সন্তানেরা

C শুধু টিকে যায়নি। জনপ্রিয় বেশিরভাগ ভাষা আজও যে গড়নটা ব্যবহার করে, সেটা C-ই ঠিক করে দিয়েছে।

C-র বংশলতিকা: চারজন পূর্বসূরি আর এগারোজন উত্তরসূরি ALGOL 60 · 1960 CPL · 1963 BCPL · 1967 B · 1969 C · 1972 Objective-C · 1984 C++ · 1985 Perl · 1987 Java · 1995 JavaScript · 1995 PHP · 1995 C# · 2000 D · 2001 Go · 2009 Rust · 2010 Swift · 2014 সবগুলোই brace আর semicolon-র ভাষা
ছবি 1। বাঁয়ে চারজন পূর্বসূরি, ডানে এগারোজন উত্তরসূরি। মাঝখানে C, 1972 সালে।

এদের তিনজনের নাম ঠিকঠাক বলা দরকার, কারণ "C থেকে ধার করা" কথাটা বড্ড ধোঁয়াটে।

  • C++ (1985) ইচ্ছা করেই প্রায় সবকিছু ধার করেছে। শুরুটা ছিল "C with Classes" নামে, আর C-র type, operator, pointer আর standard library সবই রেখে দিয়েছে। বৈধ C-র বেশিরভাগ এখনো বৈধ C++, তাই এই track সোজা C++ track-এ গিয়ে ঢোকে।
  • Java (1995) syntax ধার করেছে, আর pointer ফেলে দিয়েছে। Brace, semicolon, if, while, for আর একই operator-গুলো সব চলে এসেছে। হাতে memory সামলানোর কাজটা আসেনি।
  • JavaScript (1995) শুধু চেহারাটা ধার করেছে। Brace, semicolon আর for loop C-র। নিচের সবকিছু, এর type-র নিয়ম সহ, অন্য জায়গা থেকে এসেছে। নামটা ছিল marketing-র সিদ্ধান্ত, Java-র সঙ্গে আত্মীয়তা না।

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

  • Linux, জন্ম 1991. Linus Torvalds ছাত্র অবস্থায় শুরু করেছিলেন, আর এখনো নেতৃত্ব দেন। এটা C-তে লেখা। Ritchie-র 1972 সালের portability-র লক্ষ্যের কারণেই একই kernel ফোনে, laptop-এ আর supercomputer-এ চলে।
  • SQLite, জন্ম 2000. D. Richard Hipp এটা C-তে লিখেছিলেন যুক্তরাষ্ট্রের নৌবাহিনীর একটা প্রকল্পের জন্য। উদ্দেশ্য ছিল জাহাজে একটা database server রাখার দরকার মিটিয়ে দেওয়া। এখন এটা তোমার browser আর ফোনে আছে, আর এটা public domain।
  • Git, জন্ম 2005. Torvalds প্রায় দুই সপ্তাহে C-তে লিখেছিলেন, যখন Linux প্রকল্প তার আগের tool-টা ব্যবহারের অধিকার হারায়। তুমি জীবনে যত commit করবে, সব ওই C-র code দিয়েই যাবে।

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

১. "পুরোনো" মানেই "অচল" ভেবে নেওয়া।

যারা C ব্যবহার করে, C তাদের বেশিরভাগের চেয়ে বয়সে বড়। 2001 সালে শুরু হওয়া TIOBE index-এ সেই শুরু থেকেই এটা প্রথম পাঁচে আছে। পঞ্চাশ বছরের পুরোনো code-কে সবচেয়ে নতুন compiler উত্তর দেয় একটা warning দিয়ে, প্রত্যাখ্যান দিয়ে না। পঞ্চাশ বছর পরেও যে code build হয়, সেটা যত্নের প্রমাণ, ক্ষয়ের না।

২. পুরোনো বই বা পুরোনো উত্তর থেকে K&R ধাঁচের function copy করা।

int add(a, b)
int a;
int b;
{ return a + b; }

gcc -std=c23 দিয়ে এটা compile করলে পাবে warning: old-style function definition [-Wold-style-definition]। সমাধান হলো C89-র রূপ: int add(int a, int b)। Type-গুলো বন্ধনীর ভেতরে লিখলে এই জিনিসের সঙ্গে আর কোনোদিন দেখা হবে না।

৩. C আর C++-কে একই ভাষা ভেবে নেওয়া।

ওদের মধ্যে বংশের মিল আছে, নিয়মের না। C যেসব জিনিস মেনে নেয়, C++ compiler দিয়ে compile করলে সেগুলোতেই একটা C file আটকে যেতে পারে, যেমন void * থেকে নিজে নিজে হওয়া রূপান্তর। তোমার file-র নাম .c-তে শেষ হয়, অথচ error message-এ g++-র কথা আছে, তাহলে প্রথমে এটাই দেখবে।

৪. JavaScript-কে Java-র একটা version ভাবা।

এটা না, আর এটা interviewer-দের চেয়ে শিক্ষার্থীদেরই বেশি গুলিয়ে দেয়। Brace আর semicolon ওদের মিলে যায়, কারণ দুইজনই C থেকে নকল করেছে। বাকি সব আলাদা, এমনকি সংখ্যা আর memory সামলানোর ধরনও।

মাথা খাটাও

নিচে পাঁচটা ভাষার পাঁচটা টুকরো আছে: C, Java, JavaScript, Go আর Python, এলোমেলো ক্রমে। চারটা C-র চেহারাটা পেয়েছে। একটা পায়নি। কোনটা বলো, আর C-র চেহারার কোন দুইটা জিনিস ওটার নেই, সেটাও বলো।

def add(a, b):
    return a + b
func add(a int, b int) int {
    return a + b
}
int add(int a, int b) {
    return a + b;
}
function add(a, b) {
    return a + b;
}
int add(int a, int b)
{
    return a + b;
}

শব্দগুলো ঢেকে দাও, শুধু যতিচিহ্নের দিকে তাকাও। পাঁচটার মধ্যে চারটায় দুইটা চিহ্ন আছে, আর পঞ্চমটায় কখনোই নেই।

অনুশীলন ১সহজ

পূর্বসূরিদের শেকলটা স্মৃতি থেকে খাতায় আঁকো: নাম আর সাল সহ পাঁচটা বাক্স, সবচেয়ে পুরোনোটা উপরে।

নিয়ম। ছবি 1-এর দিকে তাকাবে না। প্রতিটা তীরের পাশে এক লাইনে লেখো, নতুন ভাষাটা কী বদলেছিল।

নিজে যাচাই করো। ছবি 1-র সঙ্গে মিলিয়ে দেখো। এক দুই বছর সাল এদিক ওদিক হলে সমস্যা নেই। ক্রম ভুল হলে এই lesson-টা আরেকবার পড়া দরকার।

অনুশীলন ২মাঝারি

Example 1, মানে K&R version-টা নাও, আর হাতে খাতায় সেটাকে C89 ধাঁচে লেখো। তারপর Playground-এ টাইপ করে চালাও।

নিয়ম। Parameter-র type-গুলো বন্ধনীর ভেতরে নাও। main-র উপরে একটা prototype যোগ করো। int main()-কে int main(void) করো। Output 5-ই থাকতে হবে।

নিজে যাচাই করো। তোমার version-টা Example 2-র add function-র মতো দেখাবে। Program 5 ছাড়া অন্য কিছু ছাপলে তুমি চেহারার চেয়ে বেশি কিছু বদলে ফেলেছ।

অনুশীলন ৩কঠিন

Ritchie "programmer-কে থামাও"-র বদলে "programmer-কে বিশ্বাস করো" বেছেছিলেন। মতামত না, সত্যিকারের প্রমাণ দিয়ে দুই দিকেই যুক্তি লেখো।

নিয়ম। দুইটা অনুচ্ছেদ, প্রতিটা মোটামুটি পাঁচ বাক্য। একটাতে এমন এক ধরনের bug-র নাম থাকতে হবে, যেটা এই সিদ্ধান্তের জন্যই সম্ভব। অন্যটাতে এমন একটা সত্যিকারের system-র নাম থাকতে হবে, যেটা এই সিদ্ধান্ত ছাড়া থাকতেই পারত না।

নিজে যাচাই করো। ভালো উত্তরে এক দিকে memory safety-র কথা আসে, অন্য দিকে kernel বা embedded device-র কথা আসে। লেখাটা রেখে দাও। Module 16-এ C23 আর Rust হাতে নিয়ে এটাতে আবার ফিরবে।

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

  • এই সালগুলো কি মুখস্থ করতে হবে?

    না। দুইটা তথ্য রাখার মতো: C এসেছে 1972 সালে, আর তুমি যেটা দিয়ে compile করো সেটা 2018 সালের C17। বাকিটা প্রেক্ষাপট, যেটা error message আর পুরোনো code পড়া সহজ করে দেয়।

  • কোন standard-এ লিখব?

    C17, এই track আর Playground যেটা ব্যবহার করে। C99 ধাঁচে লিখলে সেটা সব জায়গায় build হবে, বিশ্ববিদ্যালয়ের পুরোনো মেশিনেও। যে মেশিনে খাতা দেখা হবে সেটা C23 নেয় কি না না জেনে C23-র feature এড়িয়ে চলো।

  • C23 যখন আছে, track C17-এ কেন?

    কারণ compiler, বিশ্ববিদ্যালয় আর judge আজ C17-এই একমত। বিভিন্ন toolchain-এ C23-র সমর্থন এখনো সমান না। কী বদলেছে সেটা Module 16-এ আছে, তাই কোনো C23 codebase দেখলে তুমি চমকাবে না।

  • C++ কি শুধু নতুন একটা C?

    না। ওটা আলাদা একটা ভাষা, যেটা C থেকে শুরু হয়ে চল্লিশ বছর ধরে বেড়েছে। সাধারণ বেশিরভাগ C program C++ হিসেবেও compile হয়, তাই যাওয়াটা সহজ। Idiom, standard library আর style, সবই আলাদা।

  • Rust কি C-কে সরিয়ে দেবে?

    শিগগিরই না, আর সব জায়গায় না। নতুন system-র code-এ Rust ব্যবহার হচ্ছে, 2022 সাল থেকে Linux kernel-র কিছু অংশেও। ভালো একটা ভাষা এসেছে বলে তিন কোটি লাইন চলতে থাকা C আবার লেখা হয় না।

মূল কথা

  • C এসেছে কারণ assembly-তে লেখা Unix এক মেশিন থেকে আরেক মেশিনে যেতে পারত না।
  • শেকলটা হলো ALGOL 60, CPL, BCPL, B, তারপর 1972-র C, আর বড় সংযোজন ছিল type।
  • 1973 সালে Unix-কে C-তে আবার লেখা প্রমাণ করল, portable ভাষা দিয়েও system-র কাজ হয়।
  • Standard-গুলো হলো K&R, C89, C99, C11, C17 আর C23; এই track C17 দিয়ে compile করে।
  • C++ ভাষাটা ধার করেছে, Java syntax ধার করেছে, JavaScript শুধু চেহারা ধার করেছে।
  • Ritchie-র তিনটা সিদ্ধান্ত: programmer-কে বিশ্বাস করো, ছোট রাখো, মেশিন ঢেকো না।

পরের lesson-এ একটা ছোট file-র পিছু নেবে, লেখা থেকে চলন্ত program পর্যন্ত। মাঝে চারটা আলাদা ধাপ আছে, আর সাধারণত কেউ তোমাকে একটাও দেখায় না।

lesson ৩ শেষ

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

পরেরটা: Compiler, interpreter, আর Run চাপলে ভেতরে যা ঘটে