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

ভ্যারিয়েবল ও টাইপ — মেমরির ঘরে ডেটা রাখার নিয়ম

Variables & Types — Declaration, Sizes, Conversions & const

Taught in Bangla, this lesson covers C variables and types: declaration versus initialization, garbage values, the size of each basic type, fixed-width types, implicit conversion and casting, signed/unsigned pitfalls, and const.

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

  • ভ্যারিয়েবল মেমরিতে আসলে কী — নাম, ঠিকানা, মাপ আর টাইপের সম্পর্ক
  • ঘোষণা, initialization আর assignment-এর পার্থক্য, আর garbage value কেন বিপজ্জনক
  • কোন ডেটার জন্য কোন টাইপ — বয়স, জনসংখ্যা, টাকা, ফোন নম্বর
  • টাইপ বদলের নিয়ম: পূর্ণসংখ্যার ভাগ, promotion, cast আর signed-unsigned ফাঁদ
  • const, ভালো নাম রাখার রীতি, আর AI-এর লেখা কোডের টাইপ যাচাই

১ · ভ্যারিয়েবল আসলে কী — নাম দেওয়া একটি ঘর

আগের পর্বে দেখেছেন, কম্পিউটারের মেমরিতে সবকিছুই বিট — ০ আর ১. একই ৮টি বিট কখনো সংখ্যা ৬৫, কখনো অক্ষর A. কিন্তু একটি প্রশ্ন বাকি রয়ে গেছে: প্রোগ্রাম লেখার সময় আপনি এই বিটগুলোকে খুঁজে পাবেন কীভাবে? মেমরিতে তো কোটি কোটি বাইট!

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

ব্যাংক বা শপিং মলের লকার রুমের কথা ভাবুন। সারি সারি লকার, প্রতিটির গায়ে একটি নম্বর লেখা — এটি ঠিকানা। কোনো লকার ছোট, শুধু মোবাইল রাখা যায়; কোনোটি বড়, ব্যাগ রাখা যায় — এটি মাপ। আপনি চাবির সাথে একটি কাগজে লিখে রাখলেন “আমার ব্যাগ” — এটি নাম। আর লকারের গায়ে লেখা আছে “শুধু কাগজপত্র” বা “শুধু ইলেকট্রনিক্স” — এটি টাইপ, অর্থাৎ ভেতরে কী ধরনের জিনিস থাকবে আর কীভাবে ব্যবহার হবে।

একটি ভ্যারিয়েবলের চারটি পরিচয়

Name, address, size and type of int age = 21;

  1. নামageআপনার দেওয়া
  2. ঠিকানা0x7ffe1000মেমরিতে কোথায়
  3. মাপ4 bytesকত জায়গা
  4. টাইপintবিট পড়ার নিয়ম
ছবি ৬.১ — নাম আপনার জন্য, ঠিকানা CPU-র জন্য, আর টাইপ দুজনের মধ্যে চুক্তি।

নাম শুধু মানুষের সুবিধার জন্য। Compile হয়ে যাওয়ার পর machine code-এ age নামটি আর থাকে না — থাকে শুধু ঠিকানা. CPU জানেও না যে আপনি ঘরটির নাম “age” রেখেছিলেন।

মূল ধারণা

টাইপ হলো compiler-এর সাথে আপনার একটি চুক্তি। int age; লিখে আপনি বলছেন: “আমার জন্য পূর্ণসংখ্যা রাখার মতো একটি ঘর বানাও, আর এই ঘরের বিটগুলোকে সবসময় two's complement নিয়মে পূর্ণসংখ্যা হিসেবে পড়ো।” double cgpa; লিখলে চুক্তি ভিন্ন — ৮ বাইট, আর পড়ার নিয়ম IEEE 754. একই বিট, ভিন্ন টাইপ — ভিন্ন অর্থ। পর্ব ০৫-এর এই কথাটিই এখানে কাজে লাগছে।

এজন্যই C-কে বলা হয় statically typed ভাষা: প্রতিটি ভ্যারিয়েবলের টাইপ লেখার সময়েই ঠিক হয়ে যায়, আর প্রোগ্রাম চলার সময় সেটি বদলায় না। Python-এ একই নামে কখনো সংখ্যা, কখনো লেখা রাখা যায়; C-তে যায় না। এতে compiler আগেভাগেই জানে কোন ঘরের জন্য কত জায়গা লাগবে আর কোন CPU নির্দেশ ব্যবহার করতে হবে — C এত দ্রুত হওয়ার একটি বড় কারণ এটাই।

২ · ঘোষণা, initialization আর assignment — তিনটি আলাদা কাজ

নতুনরা প্রায়ই তিনটি কাজকে এক মনে করেন। আসলে এরা আলাদা, আর পার্থক্যটা বুঝলেই অনেক বাগ এড়ানো যায়:

কাজউদাহরণকী ঘটে
ঘোষণা (declaration)int age;নাম ও টাইপ জানানো হলো, ঘর তৈরি হলো — কিন্তু ভেতরে কোনো মান রাখা হলো না
Initializationint age = 20;ঘর তৈরির মুহূর্তেই প্রথম মান বসানো হলো
Assignmentage = 21;আগে থেকে থাকা ঘরের পুরোনো মান মুছে নতুন মান বসানো হলো

এবার একটি ছোট প্রোগ্রাম লাইন ধরে ধরে চালিয়ে দেখি। ডান পাশের টেবিলটি হলো মেমরির একটি ছবি — কোন ঠিকানায় কোন ভ্যারিয়েবল, আর তার ভেতরে এখন কী আছে। “পরে” চাপুন এবং খেয়াল করুন, মান না দেওয়া পর্যন্ত ঘরে কী থাকে:

1int main(void) {2    int age;3    double cgpa = 3.75;4    age = 20;5    char grade = 'A';6    age = age + 1;7    printf("%d %.2f %c\n", age, cgpa, grade);8    return 0;9}
মেমরি (ঠিকানা · নাম · মান)
…1000age (int, 4B)
…1008cgpa (double, 8B)
…1010grade (char, 1B)
  1. ফাংশন শুরু, ঘোষণা। main শুরু হতেই compiler তিনটি ভ্যারিয়েবলের জন্য একসাথে জায়গা আলাদা করে রেখেছে। লাইন ২ শুধু ঘোষণা — এর জন্য কোনো মান লেখার নির্দেশ তৈরিই হয় না। তাই তিনটি ঘরেই আছে ? — আগে থেকে পড়ে থাকা অজানা বিট।
  2. Initialization. লাইন ৩-এ cgpa তৈরির সাথে সাথেই মান পেল 3.75. খেয়াল করুন, age এখনো ? — এই মুহূর্তে কেউ age পড়লে অজানা কিছু পাবে।
  3. Assignment. লাইন ৪-এ age-এর ঘরে বসল 20. এতক্ষণে এই ঘরটি পড়া নিরাপদ হলো।
  4. অক্ষরও আসলে সংখ্যা। grade-এ রাখা হলো 'A'. মেমরিতে আসলে বসেছে ASCII কোড 65 — মাত্র ১ বাইটে। পর্ব ০৫-এর কথা মনে আছে তো?
  5. পুরোনো মান পড়ে নতুন মান লেখা। age = age + 1 মানে আগে ডান পাশের হিসাব — পুরোনো মান 20 পড়ে 1 যোগ — তারপর ফল 21 একই ঘরে লেখা। এটি গণিতের “সমান” নয়; C-তে = মানে “ডান পাশের মান বাঁ পাশের ঘরে রাখো”।
  6. ফল ছাপা। তিনটি ঘর থেকে মান পড়ে ছাপা হলো: 21 3.75 A. প্রতিটি মান ঠিক তার টাইপ অনুযায়ী পড়া হয়েছে — %d, %.2f, %c. (ঠিকানাগুলো উদাহরণ মাত্র; আসল ঠিকানা প্রতিবার চালালে বদলাতে পারে।)

একটি লাইনে কয়েকটি ভ্যারিয়েবলও ঘোষণা করা যায়: int a = 1, b = 2, c; — এখানে a আর b মান পেল, কিন্তু c পেল না। এই ধরনের লাইনে একটি ভ্যারিয়েবল বাদ পড়ে যাওয়া খুব সাধারণ ভুল, তাই অনেক দল এক লাইনে একটি ভ্যারিয়েবল রাখার নিয়ম মানে।

৩ · Garbage value — “খালি ঘর” বলে কিছু নেই

উপরের ডায়াগ্রামে ? দেখে মনে হতে পারে, “মান না দিলে নিশ্চয়ই শূন্য থাকে।” না। মেমরির কোনো ঘরই কখনো সত্যিই খালি থাকে না — প্রতিটি বিট হয় ০, নয় ১. মান না দিলে ঘরে থাকে তার আগে যে প্রোগ্রাম বা ফাংশন ওই জায়গা ব্যবহার করেছিল, তার ফেলে যাওয়া বিট। একে বলে garbage value বা আবর্জনা মান।

বাসের একটি নম্বর দেওয়া সিটের কথা ভাবুন। আপনার টিকিটে সিট নম্বর ১২. বসতে গিয়ে দেখলেন, আগের যাত্রী সিটে একটি পুরোনো খবরের কাগজ ফেলে গেছেন। সিট আপনার নামে বরাদ্দ ঠিকই, কিন্তু কেউ সেটি পরিষ্কার করে দেয়নি। এখন কেউ যদি জিজ্ঞেস করে “১২ নম্বর সিটে কী আছে?” — উত্তর হবে “আগের যাত্রী যা রেখে গেছেন”, যা আগে থেকে বলা অসম্ভব।

ফাংশনের ভেতরের ভ্যারিয়েবলগুলো মেমরির যে অংশে জায়গা পায়, তাকে বলে stack. একটি ফাংশন শেষ হলে তার জায়গা ছেড়ে দেওয়া হয়, কিন্তু বিটগুলো মুছে ফেলা হয় না। পরের ফাংশন সেই একই জায়গা পায় — পুরোনো বিটসহ। এজন্যই garbage value কখনো শূন্য, কখনো বিশাল কোনো সংখ্যা, আর একবার চালালে এক রকম, অন্যবার আরেক রকম।

মান না দেওয়া ভ্যারিয়েবল পড়া C-এর নিয়মে undefined behavior. এর মানে শুধু “অজানা সংখ্যা” নয় — compiler ধরে নিতে পারে আপনি কখনো এমন কাজ করবেন না, আর সেই ধারণার ওপর ভিত্তি করে কোড বদলে দিতে পারে। ফলে -O0-এ এক ফল, -O2-এ আরেক ফল আসতে পারে। নিয়ম একটাই: প্রতিটি ভ্যারিয়েবল তৈরির সময়েই মান দিন।

তিনটি তথ্য মনে রাখুন:

  • ফাংশনের ভেতরের ভ্যারিয়েবল নিজে থেকে মান পায় না — garbage থাকে।
  • সব ফাংশনের বাইরে ঘোষণা করা global variable (আর static ভ্যারিয়েবল) নিজে থেকেই শূন্য হয় — এটি C-এর নিয়মে নিশ্চিত।
  • Visual Studio-র debug mode-এ মান না দেওয়া int প্রায়ই -858993460 দেখায়। এটি আসলে hex 0xCCCCCCCC — ভুল ধরা সহজ করতে debug build ইচ্ছা করে stack-এ এই বিট বসিয়ে রাখে। এই সংখ্যাটি দেখলেই বুঝবেন, কোথাও একটি ভ্যারিয়েবল মান পায়নি।

সুখবর হলো, compiler অনেক সময় এই ভুল ধরতে পারে। -Wall চালু থাকলে GCC সাধারণত বলে 'age' is used uninitialized বা may be used uninitialized. পর্ব ০৩-এর নিয়মটি আবার মনে করুন — warning মানেই একটি সম্ভাব্য বাগ।

৪ · C-এর মূল টাইপগুলো — কোনটির কত জায়গা

C-তে সংখ্যার টাইপ মূলত দুই পরিবারে ভাগ করা: পূর্ণসংখ্যা (char, short, int, long, long long) আর দশমিক সংখ্যা (float, double). প্রতিটি পূর্ণসংখ্যা টাইপের দুটি রূপ আছে — signed (ঋণাত্মকও রাখা যায়) আর unsigned (শুধু শূন্য ও ধনাত্মক)। একই বিট সংখ্যায় unsigned দ্বিগুণ বড় ধনাত্মক মান রাখতে পারে, কারণ চিহ্নের জন্য কোনো জায়গা খরচ হয় না।

টাইপসাধারণ মাপ (আজকের PC)মানদণ্ডে ন্যূনতমমোটামুটি পরিসর
char১ বাইট৮ বিট−128 থেকে 127, অথবা 0 থেকে 255
short২ বাইট১৬ বিট−32,768 থেকে 32,767
int৪ বাইট১৬ বিটপ্রায় ±২১৪ কোটি
longWindows-এ ৪, Linux/Mac-এ ৮ বাইট৩২ বিটplatform অনুযায়ী বদলায়
long long৮ বাইট৬৪ বিটপ্রায় ±9.2 × 1018
float৪ বাইট—প্রায় ৬–৭ অঙ্ক পর্যন্ত নির্ভুল
double৮ বাইট—প্রায় ১৫–১৬ অঙ্ক পর্যন্ত নির্ভুল

টেবিলের তৃতীয় কলামটি খেয়াল করুন। C-এর মানদণ্ড int-এর মাপ নির্দিষ্ট করে দেয়নি — শুধু বলেছে “অন্তত ১৬ বিট”. আজকের প্রায় সব কম্পিউটারে int ৩২ বিট, কিন্তু পুরোনো DOS যুগের Turbo C-তে ছিল ১৬ বিট, আর আজও কিছু ছোট microcontroller-এ ১৬ বিট। আরেকটি সূক্ষ্ম কথা: সাধারণ char signed না unsigned, সেটিও compiler-এর ওপর নির্ভর করে — তাই সংখ্যা রাখতে চাইলে স্পষ্ট করে signed char বা unsigned char লিখুন।

দশমিকের জন্য প্রায় সবসময় double. float-এ মাত্র ৬–৭টি অঙ্ক নির্ভুল থাকে — ১,২৩,৪৫,৬৭৮.৯ লিখলেই শেষের দিকের অঙ্ক বদলে যেতে পারে। জায়গা বাঁচানো জরুরি না হলে (যেমন লাখ লাখ পিক্সেল বা মোবাইল GPU) double নিন। আর পর্ব ০৫-এর শিক্ষা মনে রাখুন: টাকার হিসাবে দুটির কোনোটিই নয় — পয়সায় পূর্ণসংখ্যা।

sizeof — compiler-কে জিজ্ঞেস করুন

মাপ মুখস্থ করার বদলে compiler-কেই জিজ্ঞেস করা যায়। sizeof একটি অপারেটর, যা কোনো টাইপ বা ভ্যারিয়েবলের মাপ বাইটে বলে দেয়। নিচের প্রোগ্রামটি চালিয়ে দেখুন — আর পারলে নিজের কম্পিউটারেও চালিয়ে মিলিয়ে নিন:

#include <stdio.h>
#include <stdint.h>

int main(void) {
    int age = 21;
    printf("char      : %zu\n", sizeof(char));
    printf("short     : %zu\n", sizeof(short));
    printf("int       : %zu\n", sizeof(int));
    printf("long      : %zu\n", sizeof(long));
    printf("long long : %zu\n", sizeof(long long));
    printf("float     : %zu\n", sizeof(float));
    printf("double    : %zu\n", sizeof(double));
    printf("int32_t   : %zu\n", sizeof(int32_t));
    printf("uint8_t   : %zu\n", sizeof(uint8_t));
    printf("age       : %zu\n", sizeof age);
    return 0;
}

প্রতিটি লাইন একটি টাইপের মাপ বাইটে ছাপে। sizeof-এর ফলের টাইপ size_t (একটি unsigned পূর্ণসংখ্যা), তাই ছাপতে লাগে %zu. Online compiler সাধারণত Linux-এ চলে, তাই long দেখাবে 8; একই কোড Windows-এ চালালে 4 দেখাবে. শেষ লাইনটি দেখায় যে ভ্যারিয়েবলের নাম দিয়েও sizeof করা যায়. sizeof(char) সবসময় 1 — এটি C-এর সংজ্ঞাতেই বাঁধা।

৫ · নির্দিষ্ট মাপের টাইপ আর সঠিক টাইপ বাছাই

যে কোড শুধু আপনার ল্যাপটপে চলবে, সেখানে int যথেষ্ট। কিন্তু যদি একই কোড Windows সার্ভার, Linux আর একটি ছোট microcontroller-এ চালাতে হয়, অথবা ফাইলে বা নেটওয়ার্কে ঠিক ৪ বাইট পাঠাতে হয়? তখন “সম্ভবত ৪ বাইট” চলবে না। এই সমস্যার সমাধান হিসেবে ১৯৯৯ সালের C99 মানদণ্ডে এসেছে <stdint.h>:

int8_t … int64_t

ঠিক ৮, ১৬, ৩২ বা ৬৪ বিটের signed পূর্ণসংখ্যা। নামেই মাপ লেখা — কোনো অনুমান নেই।

যেমন: int32_t, int64_t

uint8_t … uint64_t

একই মাপের unsigned রূপ। uint8_t মানে ঠিক একটি বাইট, 0 থেকে 255 — ছবির পিক্সেল বা নেটওয়ার্ক প্যাকেটের জন্য আদর্শ।

যেমন: uint8_t, uint32_t

এবার আসল প্রশ্ন: বাস্তবের ডেটার জন্য কোন টাইপ? চারটি পরিচিত উদাহরণ ধাপে ধাপে দেখুন — প্রতিটির জন্য প্রথমে পরিসর ভাবা হয়েছে, তারপর টাইপ:

একজন মানুষের বয়সপরিসর: 0 থেকে প্রায় 120
int age = 21;            /* সাধারণ ক্ষেত্রে যথেষ্ট */
uint8_t ages[1000000];   /* লাখ লাখ বয়স জমাতে হলে */
জনসংখ্যাবাংলাদেশ প্রায় ১৭ কোটি, পৃথিবী ৮০০ কোটির বেশি
int32_t bd_population    = 170000000;   /* ঘরে আঁটে: সীমা ~214 কোটি */
int64_t world_population = 8000000000;  /* int32_t-এ আঁটবে না */
ব্যাংক বা bKash ব্যালান্সপয়সায় পূর্ণসংখ্যা
int64_t balance_poisha = 1250050;   /* 12,500.50 টাকা */
/* int32_t-এর সীমা 2147483647 পয়সা = মাত্র ~2.15 কোটি টাকা */
মোবাইল নম্বরদেখতে সংখ্যা, আসলে লেখা
int phone = 01712345678;   /* error: invalid digit "8" in octal constant */
char phone[] = "01712345678";   /* সঠিক: লেখা হিসেবে */
  1. বয়স — ছোট পরিসর। ০ থেকে ১২০-এর মধ্যে থাকে, তাই একটি বাইটেই (uint8_t, 0–255) আঁটে। তবে একটি-দুটি ভ্যারিয়েবলে বাইট বাঁচিয়ে লাভ নেই — হিসাবের সময় এমনিতেই int-এ বদলে যায়। ছোট টাইপের আসল উপকার তখন, যখন লাখ লাখ মান একসাথে জমাতে হয়।
  2. জনসংখ্যা — আগে সীমা মাপুন। বাংলাদেশের প্রায় ১৭ কোটি int32_t-এর সীমা (প্রায় ২১৪ কোটি) পর্যন্ত যায় না, তাই নিরাপদ। কিন্তু পৃথিবীর জনসংখ্যা ৮০০ কোটির বেশি — ৩২ বিটে রাখলে overflow. তাই int64_t. প্রশ্নটা সবসময়: “এই মান সবচেয়ে বড় কত হতে পারে — আজ, আর দশ বছর পরে?”
  3. টাকা — পয়সায় ৬৪ বিট। দশমিকের ভুল এড়াতে টাকা রাখা হয় পয়সায়, পূর্ণসংখ্যা হিসেবে। কিন্তু ৩২ বিটে সর্বোচ্চ 2,147,483,647 পয়সা — মাত্র প্রায় ২ কোটি ১৫ লাখ টাকা! একটি মাঝারি কোম্পানির হিসাবও এর বেশি হয়। তাই int64_t, যেখানে সীমা কার্যত অফুরান।
  4. ফোন নম্বর — সংখ্যা নয়, লেখা। ফোন নম্বর দিয়ে কেউ যোগ-ভাগ করে না। তার ওপর শুরুর 0 হারিয়ে যায়, আর C-তে 0 দিয়ে শুরু হওয়া সংখ্যাকে compiler octal ধরে নেয় — যেখানে 8 বা 9 অঙ্কই নেই, তাই error. NID, রোল নম্বর, পোস্ট কোডও একই কারণে লেখা হিসেবে রাখা ভালো। লেখা রাখার পদ্ধতি বিস্তারিত দেখবেন পর্ব ১৯-তে।
টাইপ বাছাইয়ের প্রশ্ন: “সবচেয়ে বড় কত? ঋণাত্মক হবে? দশমিক লাগবে? এটা কি আসলে সংখ্যা?”

int64_t ছাপানোর ঝামেলা: এর জন্য সঠিক format specifier প্ল্যাটফর্মভেদে %ld বা %lld. নিরাপদ উপায় হলো <inttypes.h>-এর PRId64 ব্যবহার করা, অথবা (long long)-এ বদলে %lld দিয়ে ছাপা। Format specifier-এর খুঁটিনাটি পর্ব ১০-এ আসবে।

৬ · টাইপ যখন নিজে নিজে বদলায় — ভাগ, promotion আর cast

এক টাইপের মানকে আরেক টাইপে বদলানোকে বলে type conversion. C-তে এটি দুইভাবে ঘটে। আপনি না বললেও compiler নিজে যা করে, সেটি implicit conversion. আর আপনি নিজে বন্ধনীতে টাইপ লিখে যা করান, সেটি cast.

Compiler-এর মূল নিয়ম সহজ: একটি হিসাবের দুই পাশের টাইপ এক হতে হবে। CPU-তে পূর্ণসংখ্যা যোগ আর দশমিক যোগ আলাদা নির্দেশ — int আর double সরাসরি যোগ করার কোনো নির্দেশ নেই। তাই টাইপ আলাদা হলে compiler একটিকে আরেকটির টাইপে বদলে নেয়, সাধারণত “বড়” টাইপের দিকে: int → double. আর char বা short হিসাবে ঢোকার আগেই int হয়ে যায় — একে বলে integer promotion.

এর সবচেয়ে বিখ্যাত ফল হলো integer division. পাঁচটি প্রায় একই রকম লাইন দেখুন — ফল কিন্তু আলাদা:

7 / 2int ÷ int
7      /  2
int       int     → দুটিই পূর্ণসংখ্যা, পূর্ণসংখ্যার ভাগ
= 3             (3.5 নয়, দশমিক অংশ বাদ)
7 / 2.0int ÷ double
7      /  2.0
int       double  → টাইপ মেলে না
7.0    /  2.0     → int-কে double-এ তোলা হলো
= 3.5
(double)7 / 2আগে cast, তারপর ভাগ
(double)7  /  2
7.0        /  2      → cast আগে হয় (অগ্রাধিকার বেশি)
7.0        /  2.0    → 2-কে double-এ তোলা হলো
= 3.5
(double)(7 / 2)আগে ভাগ, তারপর cast
(double)(7 / 2)
(double)(3)       → বন্ধনীর ভেতর আগে: int ভাগ = 3
= 3.0              → দশমিক তো আগেই হারিয়েছে!
double avg = 7 / 2;গড় হিসাবের ক্লাসিক বাগ
double avg = 7 / 2;
double avg = 3;      → ডান পাশ আগে, নিজের টাইপে: int ভাগ
avg == 3.0          → বাঁ পাশের double কোনো কাজে আসেনি
  1. দুটিই int. দুটি পূর্ণসংখ্যার ভাগের ফলও পূর্ণসংখ্যা। ৩.৫ থেকে দশমিক অংশ সোজা কেটে ফেলা হয় — গোল করা হয় না। তাই ফল 3. ঋণাত্মক হলেও শূন্যের দিকে কাটে: -7 / 2 হয় -3. বাকি অংশ চাইলে 7 % 2, যা 1.
  2. একটি double থাকলেই যথেষ্ট। 2.0 একটি double. টাইপ মেলাতে compiler 7-কে 7.0 বানাল — এটি implicit conversion. এবার দশমিক ভাগ, ফল 3.5.
  3. Cast আগে হয়। (double) চিহ্নটি ভাগের চেয়ে বেশি অগ্রাধিকার পায়, তাই প্রথমে শুধু 7 হলো 7.0. তারপর ধাপ ২-এর মতোই 2 নিজে থেকে 2.0 হলো। ফল 3.5. ভ্যারিয়েবল দিয়ে গড় বের করার সঠিক উপায় এটাই: (double)total / count.
  4. দেরিতে cast — কোনো লাভ নেই। বন্ধনীর ভেতরের 7 / 2 আগে হিসাব হলো — পূর্ণসংখ্যার ভাগ, 3. তারপর 3-কে double বানালে হয় 3.0. হারানো 0.5 আর ফেরত আসে না। কোথায় cast বসাচ্ছেন, সেটাই আসল।
  5. বাঁ পাশ ডান পাশকে বাঁচায় না। অনেকে ভাবেন, ফল যেহেতু double-এ রাখছি, ভাগও দশমিকে হবে। না — C আগে ডান পাশ নিজের টাইপে হিসাব করে (int ভাগ, 3), তারপর ফলটি double-এ রাখে, তাই মান হয় 3.0. পর্ব ০৩-এ AI-এর লেখা গড়ের প্রোগ্রামে ঠিক এই বাগটিই দেখেছিলেন।

উল্টো দিকেও conversion হয়। double থেকে int-এ রাখলে দশমিক অংশ কেটে যায় — একে বলে truncation. int taka = 3.99; লিখলে taka হয় 3, 4 নয়. গোল করতে চাইলে <math.h>-এর round() লাগবে। আর মান যদি int-এর সীমার বাইরে হয় (যেমন 1e20), ফল undefined behavior.

সাধারণ -Wall -Wextra-ও double থেকে int-এ নিঃশব্দ বদল নিয়ে কিছু বলে না। এই ধরনের মান হারানো ধরতে GCC ও Clang-এ আলাদা flag আছে: -Wconversion. শুরুতে এটি অনেক warning দেবে, কিন্তু টাকা-পয়সা বা মাপজোখের কোডে এটি চালু রাখা ভালো অভ্যাস।

৭ · Signed আর unsigned মেশালে — সবচেয়ে চতুর ফাঁদ

এবার এমন একটি নিয়ম, যা অভিজ্ঞ প্রোগ্রামারদেরও বিপদে ফেলে। প্রশ্ন: -1 > 0u সত্য না মিথ্যা? (0u মানে unsigned int-এর শূন্য।) সাধারণ গণিতে অবশ্যই মিথ্যা। C-তে — সত্য। কেন? ধাপে ধাপে দেখুন:

  1. int x-1signed
  2. নিয়মint → unsignedটাইপ মেলানো
  3. নতুন অর্থ4294967295বিট একই
  4. তুলনা4294967295 > 0সত্য
x-এর ৩২টি বিটকখনো বদলায় না
11111111 11111111 11111111 11111111   (hex: 0xFFFFFFFF)
নিরাপদ তুলনাযেকোনো একটি পথ
if (x < 0 || (unsigned)x > y)   /* আগে চিহ্ন দেখুন */
if ((long long)x > (long long)y) /* দুটিকেই বড় signed টাইপে */
warning: comparison of integer expressions of different signedness
  1. শুরু। int x = -1; আর unsigned int y = 0; পর্ব ০৫-এর two's complement অনুযায়ী −1 মেমরিতে থাকে ৩২টি ১ হিসেবে। এখন আমরা লিখলাম x > y.
  2. টাইপ মেলানোর নিয়ম। তুলনার দুই পাশে টাইপ আলাদা: int আর unsigned int. C-এর নিয়ম (একে বলে usual arithmetic conversions) বলে, সমান মাপের signed আর unsigned মিশলে signed-টিকে unsigned বানানো হবে. কেউ আপনাকে জিজ্ঞেস করে না — নিঃশব্দে ঘটে।
  3. বিট একই, অর্থ বদলাল। ৩২টি ১-কে unsigned নিয়মে পড়লে মান হয় 232 − 1 = 4294967295. গণিতের ভাষায়: −1-এর সাথে 232 যোগ। কোনো বিট বদলায়নি — বদলেছে শুধু পড়ার চুক্তি, ঠিক যেমন অনুচ্ছেদ ১-এ বলেছিলাম।
  4. তুলনা “সত্য”। এখন CPU তুলনা করছে 4294967295 > 0 — অবশ্যই সত্য। প্রোগ্রাম ভুল শাখায় ঢুকল, অথচ কোনো error নেই। বাস্তবে এই বাগ আসে sizeof বা স্ট্রিংয়ের দৈর্ঘ্যের সাথে তুলনায়, কারণ সেগুলোর টাইপ size_t, যা একটি unsigned টাইপ।
  5. প্রতিকার। হয় তুলনার আগে চিহ্ন যাচাই করুন, নয়তো দুটিকেই এমন signed টাইপে নিন যাতে দুটোর সব মান আঁটে। আর সবচেয়ে বড় কথা — -Wextra চালু রাখুন; GCC তখন ঠিক এই warning-টি দেয়। এমন warning কখনো উপেক্ষা করবেন না।

এই ফাঁদের আরেকটি পরিচিত রূপ হলো পেছন দিকে গোনার লুপ: for (unsigned i = 9; i >= 0; i--). Unsigned কখনো ০-এর নিচে যায় না — ০ থেকে ১ কমালে হয় 4294967295 (পর্ব ০৫-এর wraparound), তাই শর্ত i >= 0 চিরকাল সত্য, লুপ আর থামে না। লুপ নিয়ে বিস্তারিত পর্ব ১২-এ।

এবার এই অনুচ্ছেদ আর আগের অনুচ্ছেদের সব নিয়ম একসাথে চালিয়ে দেখুন। শেষ অংশে compiler একটি warning দেবে — সেটিই এখানে দেখানোর বিষয়:

#include <stdio.h>

int main(void) {
    int a = 7, b = 2;
    printf("7 / 2         = %d\n", a / b);
    printf("7 %% 2         = %d\n", a % b);
    printf("7 / 2.0       = %.2f\n", a / 2.0);
    printf("(double)7 / 2 = %.2f\n", (double)a / b);
    printf("(double)(7/2) = %.2f\n", (double)(a / b));

    double price = 3.99;
    int taka = price;              /* fraction is cut, not rounded */
    printf("int taka      = %d\n", taka);

    int x = -1;
    unsigned int y = 0;
    printf("-1 > 0u ?     = %s\n", x > y ? "TRUE (surprise!)" : "false");
    return 0;
}

প্রথম পাঁচ লাইন স্টেপারের ভাগগুলোই চালায়: 3, 1, 3.50, 3.50, 3.00. printf-এ %% লিখলে একটি % ছাপে। int taka = price; লাইনে 3.99 কেটে 3 হলো — কোনো warning ছাড়াই. শেষ লাইনে x > y সত্য হয়ে “TRUE (surprise!)” ছাপে, আর compiler সাবধান করে: comparison of integer expressions of different signedness. কোডটি compile হয়, চলেও — কিন্তু ফল ভুল। এই কারণেই warning পড়তে শেখা এত জরুরি।

৮ · const আর ভালো নাম — কোড যেন নিজেই কথা বলে

const — যে মান বদলাবে না

কিছু মান প্রোগ্রামে কখনো বদলায় না — এক সপ্তাহে ৭ দিন, পাস নম্বর ৩৩, ভ্যাটের হার। এগুলোর সামনে const লিখলে compiler পাহারাদারের কাজ করে:

const int PASS_MARK = 33;
const double VAT_RATE = 0.15;

PASS_MARK = 30;   /* error: assignment of read-only variable 'PASS_MARK' */

const-এর তিনটি উপকার: ভুল করে মান বদলানো compile-এর সময়েই ধরা পড়ে; কোড পড়ে বোঝা যায় কোনটি স্থির; আর 33 সংখ্যাটি কোডের দশ জায়গায় ছড়িয়ে না রেখে এক জায়গায় নাম দিয়ে রাখা যায় — নিয়ম বদলালে এক লাইন বদলালেই হয়। নামহীন এমন সংখ্যাকে প্রোগ্রামাররা ঠাট্টা করে বলেন “magic number”. const ভ্যারিয়েবলকে তৈরির সময়েই মান দিতে হয়, কারণ পরে আর দেওয়ার সুযোগ নেই।

নাম রাখার নিয়ম — যা compiler মানে

ভ্যারিয়েবলের নাম একটি identifier. পর্ব ০৪-এ দেখেছেন, C case-sensitive; এবার নাম রাখার পুরো নিয়ম একসাথে:

  • অক্ষর (a–z, A–Z), অঙ্ক (0–9) আর _ দিয়ে তৈরি; অঙ্ক দিয়ে শুরু হতে পারে না — 2nd_roll চলবে না।
  • ফাঁকা জায়গা বা - চলবে না — total-marks মানে compiler-এর চোখে “total বিয়োগ marks”.
  • Keyword নাম হতে পারে না — int, float, return, const ইত্যাদি।
  • বড়-ছোট হাতের অক্ষর আলাদা (case-sensitive) — Age আর age দুটি আলাদা ভ্যারিয়েবল।
  • _-এর পর বড় হাতের অক্ষর দিয়ে শুরু নাম (যেমন _Value) C-এর নিজের ব্যবহারের জন্য সংরক্ষিত — নিয়মে আটকায় না, কিন্তু এড়িয়ে চলুন।

ভালো নাম — যা মানুষ বোঝে

Compiler-এর কাছে x আর monthly_salary একই রকম। কিন্তু ছয় মাস পরে কোড পড়তে আসা মানুষটি — যিনি প্রায়ই আপনি নিজেই — এর পার্থক্য হাড়ে হাড়ে টের পাবেন। C জগতে সবচেয়ে প্রচলিত রীতি হলো snake_case: সব ছোট হাতের অক্ষর, শব্দের মাঝে _. আর const মানের জন্য প্রায়ই সব বড় হাতের অক্ষর।

দুর্বল নামভালো নামকেন
xstudent_countকী গুনছে, নাম দেখেই বোঝা যায়
balbalance_poishaএককটিও নামে আছে — টাকা না পয়সা, আর ভুল হবে না
temp2max_temperature_c“temp” মানে অস্থায়ী, না তাপমাত্রা? এখন আর সন্দেহ নেই
flagis_paidis_ দিয়ে শুরু মানে হ্যাঁ/না প্রশ্ন

লুপের ছোট গণনায় i, j একদম ঠিক আছে — এটি সবার চেনা রীতি। নিয়মটা হলো: ভ্যারিয়েবল যত বেশি জায়গায় ব্যবহার হয়, নাম তত বেশি বর্ণনামূলক হওয়া উচিত।

৯ · AI-এর যুগে — টাইপের ভুল যা চোখ এড়িয়ে যায়

AI-এর লেখা কোড সাধারণত compile হয়, চলেও। টাইপের ভুলগুলো ঠিক এই কারণেই বিপজ্জনক — ছোট পরীক্ষায় সব ঠিক দেখায়, ভুল ধরা পড়ে বড় ডেটায়, অন্য কম্পিউটারে, বা মাসের শেষে হিসাব না মিললে।

AI-এর লেখা কোডে যেসব টাইপ-ভুল প্রায়ই থাকে

AI-এর ভুলকী ঘটেঠিক উপায়
টাকা float-এপয়সার গরমিল, == তুলনা ব্যর্থপয়সায় int64_t
বড় গণনা int-এ (মোট বিক্রি, ভিউ, বাইট)২১৪ কোটি পেরোলেই overflow, ঋণাত্মক ফলপরিসর হিসাব করে int64_t
“int মানে ৩২ বিট” বা “long মানে ৬৪ বিট” ধরে নেওয়াWindows বা microcontroller-এ ভুল ফল<stdint.h>-এর টাইপ
ভ্যারিয়েবলে প্রথম মান না দেওয়াকখনো ঠিক, কখনো আবর্জনা — ধরা কঠিনতৈরির সময়েই মান দেওয়া
sum / count দিয়ে গড়দশমিক অংশ হারায়(double)sum / count
signed-unsigned তুলনার warning উপেক্ষা-1 বড় হয়ে যায়, লুপ থামে নাটাইপ মেলানো, চিহ্ন যাচাই

AI-কে কোড লিখতে বলার সময় টাইপের সিদ্ধান্তগুলো তাকে দিয়েই ব্যাখ্যা করিয়ে নিন। নিচের prompt-টি যেকোনো C কোডের সাথে ব্যবহার করতে পারেন:

নিচের C কোডের প্রতিটি ভ্যারিয়েবলের জন্য একটি টেবিল বানাও: নাম, টাইপ, সম্ভাব্য সবচেয়ে বড় ও ছোট মান, আর এই টাইপে সেটি আঁটে কি না। [এখানে কোড পেস্ট করুন] এরপর বলো: ১. কোথাও overflow, integer division বা double থেকে int-এ দশমিক হারানোর ঝুঁকি আছে কি না। ২. কোথাও signed আর unsigned মিশিয়ে তুলনা বা হিসাব হয়েছে কি না। ৩. কোনো ভ্যারিয়েবল মান না পেয়েই পড়া হচ্ছে কি না। ৪. int বা long-এর মাপ নিয়ে কোনো অনুমান আছে কি না; থাকলে stdint.h-এর টাইপ সুপারিশ করো। পুরো কোড আবার লিখো না — শুধু যে লাইন বদলাতে হবে সেটি দেখাও আর কারণ বলো।

AI-এর কোড হাতে পেলে — টাইপ যাচাইয়ের চেকলিস্ট

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

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

প্র ০১এত বিপজ্জনক হলে C ভ্যারিয়েবলকে নিজে থেকে শূন্য করে দেয় না কেন?

কারণটা ইতিহাস আর দর্শন — দুটোই।

  • খরচ: শূন্য বসানো মানে একটি বাড়তি নির্দেশ। একটি ভ্যারিয়েবলে সেটা নগণ্য। কিন্তু ভাবুন, একটি ফাংশন প্রতি সেকেন্ডে লাখবার ডাকা হয়, আর তার ভেতরে ৪ কিলোবাইটের একটি buffer আছে যা পরের লাইনেই নেটওয়ার্ক থেকে আসা ডেটায় ভরে যাবে। আগে শূন্য বসানো তখন পুরোটাই অপচয়। ১৯৭০-এর দশকের ধীর কম্পিউটারে, যেখানে C জন্মেছিল, এই অপচয় সত্যিই গুরুত্বপূর্ণ ছিল।
  • দর্শন — “প্রোগ্রামারকে বিশ্বাস করো”: C-এর নকশার মূল কথা হলো, আপনি যা চাননি, ভাষা তা করবে না। আপনি যা লিখবেন, শুধু তা-ই হবে। এতে C ছোট, দ্রুত আর অনুমানযোগ্য — কিন্তু দায়িত্বটা পুরোপুরি আপনার।
  • Global কেন শূন্য হয়? কারণ সেগুলোর খরচ প্রায় শূন্য: প্রোগ্রাম খোলার সময় অপারেটিং সিস্টেম একবারে পুরো একটি শূন্য-ভরা মেমরি অংশ দেয়। ফাংশনের ভেতরের ভ্যারিয়েবল প্রতিবার ডাকায় নতুন করে তৈরি হয় — খরচ বারবার।

আজকের দুনিয়া কী করছে? নতুন ভাষাগুলো অন্য পথ নিয়েছে। Java-তে মান না দিয়ে local ভ্যারিয়েবল পড়লে compile-ই হয় না; Rust-এও একই। Go সবকিছু নিজে থেকে শূন্য করে। এমনকি C-এর জগতেও পরিবর্তন এসেছে — নতুন GCC ও Clang-এ এমন option আছে যা সব local ভ্যারিয়েবলকে নিজে থেকে শূন্য করে, আর Linux kernel-এর মতো বড় প্রজেক্ট নিরাপত্তার জন্য এমন সুবিধা চালু করার ব্যবস্থা রেখেছে। কারণ আজকের দ্রুত CPU-তে এর খরচ সামান্য, অথচ মান না দেওয়া মেমরি থেকে পুরোনো গোপন তথ্য (যেমন পাসওয়ার্ডের অংশ) ফাঁস হওয়ার ঘটনা বাস্তবে ঘটেছে।

মূল উপলব্ধি: প্রতিটি ভাষার নকশা তার যুগের সীমাবদ্ধতার প্রতিফলন। গতি বনাম নিরাপত্তার পাল্লা সময়ের সাথে নিরাপত্তার দিকে ঝুঁকছে — কিন্তু C-তে সেই দায়িত্ব এখনো আপনার অভ্যাসের ওপর।

প্র ০২int-এর মাপ কেন সব কম্পিউটারে এক নয়? মানদণ্ড একটি মাপ ঠিক করে দিল না কেন?

কারণ C-এর লক্ষ্য ছিল সব ধরনের যন্ত্রে দক্ষভাবে চলা। প্রতিটি CPU-র একটি “স্বাভাবিক” মাপ থাকে — যে মাপের সংখ্যা সে এক নির্দেশে সবচেয়ে দ্রুত যোগ-বিয়োগ করতে পারে। C-এর নির্মাতারা চেয়েছিলেন int হোক সেই স্বাভাবিক মাপ। C যে কম্পিউটারে জন্মেছিল (PDP-11), সেটি ছিল ১৬ বিটের, তাই সেখানে int ১৬ বিট। পরে ৩২ বিটের যুগে int ৩২ বিট হলো।

যদি মানদণ্ড বলত “int সবসময় ৩২ বিট”, তাহলে ১৬ বিটের যন্ত্রে প্রতিটি সাধারণ যোগই দুই ধাপে করতে হতো — ধীর। “অন্তত ১৬ বিট” বলে C প্রতিটি যন্ত্রকে নিজের সবচেয়ে দ্রুত মাপ বেছে নেওয়ার স্বাধীনতা দিয়েছে।

৬৪ বিটের যুগে নতুন জটিলতা: ৬৪ বিট CPU আসার পর প্রশ্ন উঠল — int আর long কত হবে? Linux আর Mac বেছে নিল “LP64”: int ৩২, long ও pointer ৬৪ বিট। Windows বেছে নিল “LLP64”: long-ও ৩২ বিট রইল, যাতে পুরোনো কোড আর ফাইল ফরম্যাট না ভাঙে। দুটিই যুক্তিসঙ্গত সিদ্ধান্ত — আর ফলাফল হলো, আজও একই কোড দুই OS-এ আলাদা আচরণ করতে পারে।

সমাধান এলো দুই দিক থেকে। ১৯৯৯ সালের C99 দিল <stdint.h> — যেখানে মাপ জরুরি, সেখানে int32_t. আর যেখানে মাপ জরুরি নয়, শুধু গতি দরকার, সেখানে সাধারণ int. নেটওয়ার্ক প্রোটোকল, ফাইল ফরম্যাট, এনক্রিপশন — এসব কোডে তাই প্রায় সবসময় নির্দিষ্ট মাপের টাইপ দেখবেন।

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

প্র ০৩C-কে বলা হয় “statically typed কিন্তু weakly typed” — এর মানে কী? Python বা JavaScript-এর সাথে তুলনা করুন।

টাইপ নিয়ে ভাষাগুলোকে দুটি আলাদা প্রশ্নে মাপা যায়।

  • প্রশ্ন ১ — টাইপ কখন জানা যায়? (static বনাম dynamic) C, Java, Rust-এ প্রতিটি ভ্যারিয়েবলের টাইপ compile-এর সময়েই ঠিক — এটি static. Python, JavaScript-এ টাইপ থাকে মানের সাথে, ভ্যারিয়েবলের সাথে নয়; একই নামে আজ সংখ্যা, কাল লেখা — এটি dynamic.
  • প্রশ্ন ২ — টাইপের নিয়ম কতটা কড়া? (strong বনাম weak) Python-এ "5" + 3 লিখলে সাথে সাথে error — Python টাইপ মেশাতে দেয় না, তাই এটি strong. JavaScript-এ একই কাজের ফল "53" — নিঃশব্দে বদলে দেয়। C-ও অনেকটা নিঃশব্দে বদলায়: int থেকে double, double থেকে int (দশমিক হারিয়ে), signed থেকে unsigned (অর্থ উল্টে) — কোনো error ছাড়াই। এজন্যই C-কে weakly typed বলা হয়।

C কেন এমন? হাতে লেখা যন্ত্রঘনিষ্ঠ কোডে প্রতিটি হিসাবে cast লিখতে হলে কোড অনেক লম্বা হতো। ১৯৭০-এর দশকে সুবিধাটাই বেশি গুরুত্ব পেয়েছিল। কিন্তু এই নিঃশব্দ বদলই অসংখ্য বাগের উৎস। Rust তাই উল্টো সিদ্ধান্ত নিয়েছে: সেখানে i32 আর i64 যোগ করতে চাইলেও নিজে লিখে বদলাতে হয় — একটু বেশি লেখা, কিন্তু নিঃশব্দ ভুলের সুযোগ নেই।

মাঝামাঝি সমাধান হলো compiler warning: -Wconversion, -Wsign-compare চালু করলে C-কে অনেকটা “strong” আচরণে আনা যায়।

মূল উপলব্ধি: টাইপ-ব্যবস্থা হলো ভাষার নিরাপত্তা-জাল। জাল যত ঢিলা, আপনার নিজের মনোযোগ তত বেশি লাগে — আর warning হলো সেই জাল নিজে শক্ত করার উপায়।

প্র ০৪signed-unsigned মেশানোর নিয়ম এত জটিল কেন? আর অনেক বড় কোম্পানি unsigned এড়িয়ে চলতে বলে কেন?

সমস্যার মূলে একটি অসম্ভব চাহিদা। ৩২ বিটের int রাখে প্রায় −২১৪ কোটি থেকে +২১৪ কোটি; ৩২ বিটের unsigned রাখে ০ থেকে প্রায় ৪২৯ কোটি। দুটিকে তুলনা করতে হলে একটি সাধারণ টাইপ লাগবে — কিন্তু ৩২ বিটের এমন কোনো টাইপ নেই যাতে দুই পরিসরই আঁটে। কাউকে না কাউকে হারতে হবে।

C বেছে নিয়েছে unsigned-এর পক্ষ: signed মানটিকে unsigned বানাও। যুক্তি ছিল, CPU-তে এতে কোনো খরচ নেই (বিট একই থাকে), আর বড় ধনাত্মক মান হারায় না। কিন্তু দাম দিতে হয় ঋণাত্মক সংখ্যাকে — −১ হয়ে যায় বিশাল।

ইতিহাসও জট বাড়িয়েছে। ১৯৮৯ সালের ANSI মানদণ্ডের আগে বিভিন্ন compiler ছোট unsigned টাইপ (যেমন unsigned char) হিসাবে তোলার সময় আলাদা নিয়ম মানত। মানদণ্ড কমিটি একটি নিয়ম বেছে নেয় ("value preserving") — ছোট unsigned টাইপ তোলা হয় signed int-এ। ফলে আজ নিয়ম দুই রকম: unsigned char হিসাবে ঢোকে signed int হয়ে, কিন্তু unsigned int থাকে unsigned. একই “unsigned” শব্দ, দুই রকম আচরণ।

ব্যবহারিক পরামর্শ: Google-এর C++ style guide-সহ অনেক অভিজ্ঞ দল বলে — শুধু “ঋণাত্মক হবে না” এই কারণে unsigned নেবেন না। unsigned নিন যখন সত্যিই বিট নিয়ে কাজ করছেন (মাস্ক, হ্যাশ, নেটওয়ার্ক প্যাকেট) বা wraparound আচরণটাই চান। গণনা আর হিসাবের জন্য signed টাইপ, আর ঋণাত্মক মান যাচাই আলাদা করে।

মূল উপলব্ধি: যখন কোনো সমাধানই সবার জন্য সঠিক নয়, তখন নিয়ম জটিল হতে বাধ্য। সেরা কৌশল হলো ফাঁদটা এড়িয়ে চলা — একটি হিসাবে signed আর unsigned না মেশানো।

প্র ০৫ভুল টাইপ বেছে নেওয়ার দাম বাস্তবে কতটা হতে পারে?

পর্ব ০৫-এ Ariane 5 রকেটের গল্প দেখেছেন — একটি ৬৪ বিটের দশমিক মান ১৬ বিটের পূর্ণসংখ্যায় ঢোকাতে গিয়ে কয়েকশো কোটি টাকার রকেট ধ্বংস। আরও কয়েকটি শিক্ষণীয় ঘটনা:

  • Patriot ক্ষেপণাস্ত্র (১৯৯১): উপসাগরীয় যুদ্ধে সৌদি আরবের দাহরানে একটি Patriot ব্যবস্থা ঘড়ির সময় রাখত সেকেন্ডের দশমাংশে, ২৪ বিটের নির্দিষ্ট-দশমিক ঘরে। ০.১ বাইনারিতে নিখুঁত লেখা যায় না (পর্ব ০৫!), তাই প্রতি টিকে সামান্য ভুল জমছিল। প্রায় ১০০ ঘণ্টা একটানা চলার পর ভুল দাঁড়ায় প্রায় এক-তৃতীয়াংশ সেকেন্ড — একটি দ্রুতগামী Scud ক্ষেপণাস্ত্র ধরার জন্য তা অনেক বেশি। ক্ষেপণাস্ত্রটি আঘাত হানে, ২৮ জন মার্কিন সৈন্য নিহত হন।
  • Boeing 787 (২০১৫): মার্কিন বিমান নিয়ন্ত্রক FAA সতর্ক করে যে এই বিমানের বিদ্যুৎ-নিয়ন্ত্রক ইউনিট ২৪৮ দিন একটানা চালু থাকলে সব বিদ্যুৎ বন্ধ হয়ে যেতে পারে। কারণ হিসেবে একটি সময়-গণনার counter-এর overflow-এর কথা জানা যায়। হিসাবটা মিলিয়ে দেখুন: counter যদি প্রতি সেকেন্ডে ১০০ বার বাড়ে, তবে signed ৩২ বিটের সীমা 231 পৌঁছাতে লাগে প্রায় ২৪৮.৫ দিন। সাময়িক সমাধান ছিল — নিয়মিত বিরতিতে বিদ্যুৎ বন্ধ করে আবার চালু করা।
  • ২০৩৮ সালের সমস্যা: বহু পুরোনো Unix সিস্টেম সময় রাখে ১৯৭০ সাল থেকে গোনা সেকেন্ডে, signed ৩২ বিটে। ১৯ জানুয়ারি ২০৩৮ তারিখে সেই সংখ্যা ২৩১ − ১ পেরিয়ে যাবে আর মান হয়ে যাবে ঋণাত্মক — অর্থাৎ ১৯০১ সাল! আধুনিক সিস্টেম ৬৪ বিটের সময়ে চলে গেছে, কিন্তু embedded যন্ত্র আর পুরোনো ফাইল ফরম্যাটে ঝুঁকি রয়ে গেছে।

তিনটি ঘটনার মিল লক্ষ করুন: প্রতিটি কোড পরীক্ষায় দিব্যি চলেছিল। ভুল ধরা পড়েছে শুধু তখন, যখন কোনো মান তার টাইপের সীমায় পৌঁছেছে — দীর্ঘ সময় পরে, বা অপ্রত্যাশিত পরিস্থিতিতে।

মূল উপলব্ধি: টাইপ বাছাই হলো ভবিষ্যতের সাথে একটি চুক্তি। “আজ চলে” যথেষ্ট নয় — জিজ্ঞেস করুন, “সবচেয়ে বড় মান কত হতে পারে, আর কত দিন পরে?”

অনুশীলন

এগুলোর বেশিরভাগে কোড লিখতে হবে না — আগে খাতায় উত্তর লিখুন, তারপর মিলিয়ে দেখুন।

  1. ফল আগে অনুমান করুন: int a = 9, b = 4; হলে নিচের প্রতিটির মান কত? (ক) a / b (খ) a % b (গ) (double)a / b (ঘ) (double)(a / b) (ঙ) a / b * b

    (ক) 2 — পূর্ণসংখ্যার ভাগ, ২.২৫ থেকে দশমিক বাদ। (খ) 1 — ৯-কে ৪ দিয়ে ভাগ করলে ভাগশেষ ১. (গ) 2.25 — আগে cast, তারপর দশমিক ভাগ। (ঘ) 2.0 — আগে পূর্ণসংখ্যার ভাগ, পরে cast; দশমিক আগেই হারিয়েছে। (ঙ) 8 — বাঁ থেকে ডানে: 9 / 4 = 2, তারপর 2 * 4 = 8. গণিতে a / b * b মানে a, কিন্তু C-তে নয় — এটি integer division-এর আরেকটি সূক্ষ্ম রূপ।

  2. টাইপ বেছে নিন: নিচের প্রতিটির জন্য টাইপ আর কারণ লিখুন — (ক) ক্লাসের রোল নম্বর (১–১২০) (খ) একজন ব্যাটারের ক্যারিয়ারের মোট রান (গ) ঢাকা থেকে চট্টগ্রামের দূরত্ব কিলোমিটারে, দশমিকসহ (ঘ) জাতীয় পরিচয়পত্রের (NID) নম্বর (ঙ) একটি কোম্পানির বার্ষিক লেনদেন, পয়সায়

    (ক) int — ছোট পরিসর, তবে হিসাব বা তুলনায় ব্যবহার হতে পারে। (রোল যদি শুধু পরিচয় হয়, যেমন “2023-CSE-045”, তবে লেখা।) (খ) int বা int32_t — টেস্ট ক্রিকেটের সর্বোচ্চ ক্যারিয়ার রানও কয়েক হাজারের ঘরে, সীমার অনেক নিচে। (গ) double — দশমিক মাপ, আর ছোটখাটো আনুমানিকতা এখানে সমস্যা নয়। (ঘ) লেখা (char-এর সারি) — এটি দিয়ে হিসাব হয় না, শুরুতে 0 থাকতে পারে, আর ১৩ বা ১৭ অঙ্কের NID তো ৩২ বিটে আঁটেই না। (ঙ) int64_t — ৩২ বিটে মাত্র প্রায় ২ কোটি ১৫ লাখ টাকা ধরে।

  3. নাম যাচাই: কোনগুলো নিয়মে অবৈধ, আর কোনগুলো বৈধ হলেও দুর্বল? 2nd_score, float, total-marks, x, student_count, _Value, Total ও total একই প্রোগ্রামে।

    অবৈধ: 2nd_score (অঙ্ক দিয়ে শুরু), float (keyword), total-marks (- চিহ্ন বিয়োগ হিসেবে পড়া হবে)।

    বৈধ কিন্তু দুর্বল: x (কিছুই বলে না); _Value (_ আর বড় হাতের অক্ষরে শুরু নাম C-এর জন্য সংরক্ষিত); Total ও total একসাথে — compiler-এর কাছে আলাদা, কিন্তু মানুষ সহজেই গুলিয়ে ফেলবে।

    ভালো: student_count.

  4. নিজের কম্পিউটারে মাপ দেখুন: উপরের sizes.c প্রোগ্রামটি নিজের কম্পিউটারে (Windows হলে MSYS2 দিয়ে) চালান, আর পাতার online ফলের সাথে মেলান। কোন লাইনে পার্থক্য পেলেন, আর কেন?

    Windows-এ সাধারণত শুধু long-এর লাইনে পার্থক্য দেখবেন: online (Linux) ফলে 8, Windows-এ 4. কারণ Windows ৬৪ বিটেও “LLP64” মডেল মানে, যেখানে long ৩২ বিটই থাকে। বাকি সব — int 4, long long 8, double 8 — দুই জায়গায় একই। আর int32_t, uint8_t সব জায়গায় একই থাকবেই — সেজন্যই তাদের জন্ম। Mac বা Linux-এ চালালে সাধারণত কোনো পার্থক্যই পাবেন না।

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

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

ক্রমএকটি নতুন ভ্যারিয়েবল তৈরির ভাবনা সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. 🔎 ডেটাটি কী আর এর পরিসর কত — সবচেয়ে বড়, সবচেয়ে ছোট
  2. 📏 পরিসর অনুযায়ী টাইপ বাছাই (int, int64_t, double, লেখা)
  3. 🏷️ অর্থবহ নাম ঠিক করা (snake_case)
  4. ✍️ ঘোষণার সাথেই প্রথম মান দেওয়া
  5. 🔒 মান কখনো না বদলালে const যোগ করা

একটি ফাংশনের ভেতরে int count; লিখে সাথে সাথে printf("%d", count); করলে কী ছাপবে?

  • সবসময় 0
  • Compile error হবে
  • অনির্দিষ্ট — আগে থেকে থাকা আবর্জনা মান
  • -1

double avg = 7 / 2; — avg-এর মান কত?

  • 3.5
  • 3.0
  • 4.0
  • Compile error

বাংলাদেশি মোবাইল নম্বর (যেমন 01712345678) রাখার সবচেয়ে ভালো উপায় কোনটি?

  • int
  • float
  • long long
  • লেখা হিসেবে — char-এর সারি

int x = -1; unsigned int y = 0; হলে x > y-এর ফল কী?

  • সত্য, কারণ -1 unsigned হয়ে বিশাল সংখ্যা হয়ে যায়
  • মিথ্যা, কারণ -1 < 0
  • Compile error
  • Undefined behavior

long-এর মাপ সম্পর্কে কোন কথাটি সঠিক?

  • সবসময় ৮ বাইট
  • সবসময় ৪ বাইট
  • Platform অনুযায়ী বদলায় — নির্দিষ্ট মাপ চাইলে stdint.h-এর টাইপ নিতে হয়
  • int-এর চেয়ে সবসময় ছোট

নিচের কোনটি বৈধ ও ভালো ভ্যারিয়েবল নাম?

  • 2nd_roll
  • student_count
  • total-marks
  • int

শব্দকোষ

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

মনে রাখুন

  1. ভ্যারিয়েবল = মেমরির নাম দেওয়া ঘর: নাম, ঠিকানা, মাপ, টাইপ. টাইপ ঠিক করে কত বাইট আর বিট পড়ার নিয়ম।
  2. ঘোষণা ≠ initialization ≠ assignment. মান না দেওয়া ঘরে থাকে garbage — তাই তৈরির সময়েই মান দিন।
  3. int, long-এর মাপ platform-নির্ভর; মাপ জরুরি হলে <stdint.h>, আর জানতে sizeof.
  4. 7 / 2 = 3; দশমিক চাইলে ভাগের আগে cast. double থেকে int-এ দশমিক কেটে যায়। signed আর unsigned মেশাবেন না।
  5. স্থির মানে const, নাম রাখুন অর্থবহ snake_case-এ; AI-এর কোডে প্রতিটি টাইপের পরিসর নিজে যাচাই করুন।

পরের পর্বে: ঘরে মান রাখা শিখলেন। এবার সেই মানগুলো নিয়ে কাজ — যোগ-বিয়োগ, তুলনা আর যুক্তি। দেখব a + b * c কোন ক্রমে হিসাব হয়, ++i আর i++-এর পার্থক্য কী, আর && কেন কখনো দ্বিতীয় অংশটি যাচাইই করে না।

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

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