Foundationপ্রথম নীতি থেকে
LEVEL 1লেসন ৩/১৪মাঝারি৫৫ মিনিট

Bit, Nibble, Byte, Word — সংগঠনের একক

Bits, Bytes, and Words

Bit থেকে nibble, byte, word — কতগুলো bit মিলে 'একটা জিনিস' হয় সেই সিদ্ধান্তই CPU-র register width, memory-র ঠিকানা, pointer-এর আকার, আর struct-এর ভেতরের ফাঁকা জায়গা পর্যন্ত সবকিছু নির্ধারণ করে।

এই লেসন শেষে আপনি পারবেন

  • bit, nibble, byte, word-এর সংজ্ঞা ও একে অপরের সাথে সম্পর্ক বলতে পারবেন
  • কেন ৮-bit byte জিতল সেটা একটা ঐতিহাসিক ও বাণিজ্যিক যুক্তি হিসেবে ব্যাখ্যা করতে পারবেন
  • word size কীভাবে register width, address space, আর pointer-এর আকার নির্ধারণ করে তা একটা concrete হিসাব (32-bit 4GB wall) দিয়ে দেখাতে পারবেন
  • memory-কে একটা byte-addressable array হিসেবে conceptually বর্ণনা করতে পারবেন
  • alignment কেন দরকার (single bus transaction যুক্তি) আর struct padding কীভাবে তার ফলাফল সেটা বলতে পারবেন
  • C-তে বিভিন্ন type-এর sizeof হাতে-কলমে যাচাই করতে পারবেন

আগে যা বোঝা থাকা দরকার

আগে এটা বুঝি

একটা প্রশ্ন দিয়ে শুরু করি যেটা প্রায় প্রতিটা C প্রোগ্রামার একদিন নিজেকে জিজ্ঞেস করে:

printf("%zu\n", sizeof(int));      // প্রায় সবসময় 4
printf("%zu\n", sizeof(long));      // Linux-এ 8, Windows-এ 4!
printf("%zu\n", sizeof(void*));     // 32-bit সিস্টেমে 4, 64-bit-এ 8

int কেন ৪ byte? সবসময়ই কি ৪? void* কেন সিস্টেমভেদে বদলায়, কিন্তু char কখনো বদলায় না?

গত দুই লেসনে আমরা বারবার “byte”, “৩২-bit”, “৪-byte integer” বলেছি, ধরে নিয়ে এগুলো সার্বজনীন সত্য। এই লেসনে আমরা সেই ধরে নেওয়া জিনিসটাকেই প্রশ্ন করব: কতগুলো bit মিলে “একটা জিনিস” গণ্য হয়, আর এই সিদ্ধান্তটা কোথা থেকে আসে?

উত্তরটা আংশিক সার্বজনীন (byte সবখানে ৮ bit — একটা ঐতিহাসিক যুদ্ধের চূড়ান্ত ফলাফল) আর আংশিক প্রেক্ষাপট-নির্ভর (word আপনার CPU-র উপর নির্ভর করে বদলায়)। এই পার্থক্যটাই আজকের কেন্দ্রীয় বিষয়।

মূল ধারণা

চারটা একক — সংজ্ঞা

এককআকারসম্পর্ক
Bit১ bitমৌলিক একক (লেসন ১)
Nibble৪ bitঅর্ধেক byte — ঠিক ১টা হেক্স digit (লেসন ২)
Byte৮ bit২টা nibble — আজকের universal ন্যূনতম addressable একক
WordCPU-নির্ভর (আজ সাধারণত ১৬/৩২/৬৪ bit)CPU-র স্বাভাবিক register ও bus width

লক্ষ্য করুন প্রথম তিনটা (bit, nibble, byte) সার্বজনীনভাবে নির্দিষ্ট — প্রতিটা আধুনিক কম্পিউটারে একটা byte মানে ঠিক ৮ bit, ব্যতিক্রম নেই। কিন্তু word একটা আপেক্ষিক একক — এটা মানে “এই নির্দিষ্ট CPU যতগুলো bit একসাথে স্বাভাবিকভাবে handle করে”, আর সেটা machine থেকে machine-এ আলাদা। এই অসামঞ্জস্যটাই এই লেসনের মূল জটিলতা, আর এটা কোনো দুর্ঘটনা নয় — বরং দুইটা সম্পূর্ণ আলাদা ঐতিহাসিক প্রক্রিয়ার ফল, যা আমরা এখন আলাদাভাবে দেখব।

৮-bit byte কীভাবে জিতল

আজ “byte মানে ৮ bit” এতটাই স্বতঃসিদ্ধ মনে হয় যে ভুলে যাওয়া সহজ এটা কখনো প্রশ্নবিদ্ধ ছিল। বাস্তবে ১৯৫০-৬০-এর দশকে byte-এর আকার নিয়ে রীতিমতো বিশৃঙ্খলা ছিল।

সিস্টেমByte / মৌলিক এককবছর
IBM 702৬-bit (BCD অক্ষরের জন্য)১৯৫৩
IBM 1401৬-bit + parity১৯৫৯
Setun (ternary, লেসন ১ দেখুন)balanced ternary, বাইনারি byte-ই নেই১৯৫৮
PDP-8১২-bit word, “byte” ধারণা অস্পষ্ট১৯৬৫
PDP-10৩৬-bit word, variable-size byte (১-৩৬ bit!)১৯৬৬
Unisys/CDC কিছু mainframe৯-bit byte১৯৬০-এর দশক
IBM System/360৮-bit byte, স্থায়ীভাবে১৯৬৪

System/360-ই নির্ণায়ক মুহূর্ত। IBM-এর আগের সিস্টেমগুলো একে অপরের সাথে ইনকম্প্যাটিবল ছিল — একেক machine-এর জন্য আলাদা software লিখতে হতো, একটা বিশাল বাণিজ্যিক সমস্যা। Gene Amdahl-এর নেতৃত্বে IBM সিদ্ধান্ত নেয় একটা একক architecture family বানাবে, ছোট থেকে বড় সব machine জুড়ে সামঞ্জস্যপূর্ণ।

৮-bit বাছার পেছনে যুক্তি ছিল ব্যবহারিক, তাত্ত্বিক নয়:

  • ৬-bit BCD যথেষ্ট ছিল না — আপার-কেস, লোয়ার-কেস, সংখ্যা, আর punctuation সব মিলিয়ে ৬৪-এর বেশি character দরকার ছিল (26=642^6 = 64 যথেষ্ট নয়)।
  • ৮-bit দেয় 28=2562^8 = 256 টা সম্ভাব্য code — যথেষ্ট বেশি মার্জিন রেখে সব দরকারি character ধরার জন্য।
  • ৮, ২-এর ঘাত — লেসন ২-তেই আমরা দেখেছি এটা hex-এর সাথে নিখুঁত মেলে (8=2×48 = 2 \times 4), যদিও এই যুক্তিটা সিদ্ধান্তের কারণ ছিল না, বরং একটা fortunate পরবর্তী ফলাফল — hex নিজেই তখনো ব্যাপক ব্যবহৃত হয়নি।
  • System/360 বিশাল বাণিজ্যিক সাফল্য পায়, আর তার সাথে সাথে ৮-bit byte industry-wide de facto standard হয়ে যায় — পরবর্তী প্রতিটা architecture (Intel, Motorola, ARM, সব) এই কনভেনশন মেনে নেয়, শুধু compatibility আর প্রশিক্ষিত প্রকৌশলীদের অভ্যাসের কারণে।

Word size — CPU-নির্ভর, তাই ইতিহাসের ধারা ভিন্ন

Byte-এর গল্প “একটা সংখ্যায় স্থির হয়ে যাওয়া”। Word-এর গল্প সম্পূর্ণ ভিন্ন — এটা প্রতিটা নতুন প্রজন্মের CPU-র সাথে বদলেছে, আর আজও বদলাচ্ছে না এমন নয় (embedded 8-bit microcontroller আজও তৈরি হয়)।

যুগউদাহরণ CPUWord sizeAddress spaceসাল
প্রাথমিক microprocessorIntel 8080৮ bit৬৪ KB১৯৭৪
প্রথম IBM PCIntel 8086১৬ bit১ MB১৯৭৮
৩২-bit যুগIntel 80386৩২ bit৪ GB১৯৮৫
আধুনিকx86-64 (AMD64)৬৪ bitতাত্ত্বিক ১৬ EB (ব্যবহারিক ~২৫৬ TB)২০০৩
EmbeddedARM Cortex-M0৩২ bitচিপ-ভেদেআজও

Word size-এর সবচেয়ে সরাসরি প্রভাব: address space-এর সীমা। একটা n-bit address দিয়ে সর্বোচ্চ 2n2^n টা আলাদা memory location চেনানো যায় — combinatorics-এর সেই product rule আবার, এবার address-এর প্রতিটা bit-এর জন্য।

৩২-bit-এর বিখ্যাত “4GB wall” — সম্পূর্ণ হিসাব

232=4,294,967,296 byte=4 GiB (ঠিক)2^{32} = 4,294,967,296 \text{ byte} = 4 \text{ GiB (ঠিক)}

এর মানে একটা ৩২-bit system-এ কোনো একক process কখনোই ৪ GB-র বেশি memory address করতে পারে না — address-টাই মাত্র ৩২ bit চওড়া, তার বাইরে কোনো সংখ্যা লেখাই যায় না, RAM যতই থাকুক না কেন।

বাস্তবে সংখ্যাটা আরও খারাপ ছিল। ৩২-bit Windows-এ user process সাধারণত পুরো ৪ GB পেত না — অপারেটিং সিস্টেম নিজেই address space-এর একটা অংশ (সাধারণত উপরের ২ GB, বা /3GB boot switch দিলে ১ GB) সংরক্ষিত রাখত kernel-এর নিজের ব্যবহারের জন্য। তাই বাস্তব ব্যবহারযোগ্য সীমা ছিল প্রায় ২-৩ GB, পুরো ৪ নয়।

৩২-bit address space (৪ GB), typical বিভাজন:

0x00000000 ┌─────────────────────────┐
           │   User process          │  ~2-3 GB
0x80000000 ├─────────────────────────┤
           │   Kernel space          │  ~1-2 GB
0xFFFFFFFF └─────────────────────────┘

এই “দেয়াল”টাই ২০০০-এর দশকের মাঝামাঝি ৬৪-bit-এ transition-এর সবচেয়ে বড় চালিকাশক্তি। RAM সস্তা হচ্ছিল, ৪ GB-র বেশি RAM সাশ্রয়ী হয়ে উঠছিল, কিন্তু ৩২-bit address space সেটা ব্যবহারই করতে দিত না। AMD ২০০৩ সালে AMD64 (পরে x86-64 নামে পরিচিত) স্থাপত্য চালু করে ঠিক এই সমস্যার সমাধানে — Intel প্রথমে নিজস্ব ভিন্ন পথ (Itanium, IA-64) চেষ্টা করেছিল, কিন্তু backward compatibility-র অভাবে বাজারে হারে, আর শেষে Intel-ও AMD64-এর নকশা (EM64T নামে) গ্রহণ করে।

Pointer size — word size-এর সরাসরি পরিণতি

একটা pointer একটা memory address সংরক্ষণ করে। তাই একটা pointer-এর আকার হতে হবে অন্তত address space চেনানোর জন্য যথেষ্ট bit — যা সরাসরি CPU-র word/address bus width-এর সমান।

Systemsizeof(void*)
৩২-bit (x86, ARM32)৪ byte
৬৪-bit (x86-64, ARM64)৮ byte

এখান থেকেই একটা বাস্তব ও প্রায়ই বিস্ময়কর ফলাফল আসে: ৩২-bit থেকে ৬৪-bit-এ move করলে pointer-নির্ভর data structure-এর memory খরচ প্রায় দ্বিগুণ হয়ে যায়, শুধু pointer-এর কারণে।

struct Node {
    int   value;   // 4 byte, উভয় system-এ
    Node* next;    // 32-bit: 4 byte,  64-bit: 8 byte
};
// 32-bit: sizeof(Node) = 8 byte (padding ছাড়া)
// 64-bit: sizeof(Node) = 16 byte (alignment-এর কারণে, নিচে দেখুন)

একটা ১ কোটি node-এর linked list-এ এই পার্থক্যটা কয়েক মেগাবাইট থেকে লাফিয়ে কয়েক গিগাবাইট পর্যন্ত পৌঁছাতে পারে — এই কারণেই memory-সংবেদনশীল system (game engine, database) প্রায়ই raw pointer এড়িয়ে ৩২-bit index (array-এর মধ্যে অবস্থান) ব্যবহার করে, যদিও system নিজে ৬৪-bit।

ভেতরে কী ঘটছে

Memory — একটা বিশাল byte-addressable array (conceptual মডেল)

এই পর্যায়ে আমরা একটা সরলীকৃত কিন্তু অত্যন্ত শক্তিশালী মানসিক মডেল প্রতিষ্ঠা করব, যেটা Level 2 আর Level 3-এ hardware-এর আসল বাস্তবায়ন দিয়ে পূর্ণ হবে।

মডেল: Memory একটা বিশাল array, যার প্রতিটা byte-এর একটা unique address (নিজেও একটা সংখ্যা) আছে। address 0 থেকে শুরু করে ধারাবাহিকভাবে বাড়ে।

address:  0    1    2    3    4    5    6    7    ...
byte:    [4a] [00] [00] [00] [ff] [ff] [ff] [ff]  ...

এখানে গুরুত্বপূর্ণ পয়েন্ট: addressing byte-ভিত্তিক, bit-ভিত্তিক নয়। আপনি একটা নির্দিষ্ট bit-এর address চাইতে পারবেন না — শুধু byte-এর। এটা byte-কে “ন্যূনতম addressable একক” বানায়, আর এটাই সম্ভবত ৮-bit byte জেতার সবচেয়ে গভীর দীর্ঘমেয়াদী পরিণতি: পুরো memory hierarchy, প্রতিটা pointer, প্রতিটা array indexing — সব কিছু এই একক ধরে নিয়ে বানানো।

একটা int (৪ byte ধরে নিলে) memory-তে ৪টা পরপর byte দখল করে:

address:     100    101    102    103
int value:   [ b0 ] [ b1 ] [ b2 ] [ b3 ]
              └──────── একটা int (৪ byte) ────────┘

কোন byte সবচেয়ে গুরুত্বপূর্ণ (b3b_3 নাকি b0b_0?) — সেটা একটা আলাদা প্রশ্ন, endianness, যা এই module-এর পরের একটা লেসনের বিষয়। এই লেসনে আমরা শুধু প্রতিষ্ঠা করছি: একটা multi-byte value কয়েকটা পরপর byte-এর একটা contiguous group, আর সেই গ্রুপের প্রথম byte-এর address-ই পুরো value-র “address” হিসেবে গণ্য হয়।

Alignment — কেন address-ও গুরুত্বপূর্ণ, শুধু ক্রম নয়

এতক্ষণ আমরা বলেছি একটা int “৪টা পরপর byte” দখল করে। কিন্তু প্রশ্ন: কোন ৪টা byte? যেকোনো ৪টা পরপর byte, নাকি কিছু নির্দিষ্ট address-ই ভালো?

নিয়ম: একটা n-byte value-কে সাধারণত এমন address-এ রাখা হয় যা n-এর গুণিতক। একে বলে alignmentint (৪ byte) সাধারণত address 0, 4, 8, 12, ...-এ থাকে, কখনো 1, 2, 3, 5-এ নয়।

কেন — এখানেই bus-এর ভূমিকা। CPU আর memory-র মধ্যে data আসা-যাওয়া করে একটা fixed-width bus-এর মধ্য দিয়ে (এই bus-এর প্রস্থও প্রায়ই word size-এর সমান)। একটা ৩২-bit bus একবারে ৪টা পরপর, address 0 মডিউলো 4-এ শুরু হওয়া byte নিয়ে আসতে পারে — এটাকে বলা যায় একটা “৪-byte স্লট”, ঠিক যেমন একটা বইয়ের তাকে বই রাখার জন্য fixed slot থাকে।

Aligned read (address 4-এ শুরু, ৪-byte value):
   একটা bus transaction-এই পুরো value চলে আসে ✓

Misaligned read (address 6-এ শুরু, ৪-byte value):
   address 6 পড়ে দুইটা byte পাওয়া যায় (৬,৭ — slot [4-7]-এর অংশ)
   বাকি দুইটা byte (৮,৯) পরের slot [8-11]-এ — দ্বিতীয় bus transaction লাগে!
   তারপর দুই transaction-এর টুকরো জোড়া লাগাতে হয় (shift + combine)
memory:  [0][1][2][3][4][5][6][7][8][9][10][11]
          └─slot 0──┘ └─slot 1──┘ └─slot 2───┘

Aligned int, address 4:
          পুরোটাই slot 1-এর ভেতরে → ১ transaction

Misaligned int, address 6:
          অর্ধেক slot 1, অর্ধেক slot 2 → ২ transaction + জোড়া লাগানো
Aligned বনাম misaligned access — একই ৪ byte, ভিন্ন সংখ্যক bus transaction।

পরিণতি:

  • x86/x86-64 misaligned access সমর্থন করে, কিন্তু ধীরে — extra bus transaction আর জোড়া লাগানোর জন্য অতিরিক্ত cycle লাগে।
  • কিছু ARM (বিশেষত পুরনো/embedded) misaligned access-এ সরাসরি hardware fault (crash) দেয় — কোনো automatic জোড়া লাগানোর ব্যবস্থাই নেই, শুধু performance loss নয়, program বন্ধই হয়ে যায়।

এই পার্থক্যটাই কেন একই C কোড x86-এ নীরবে (কিন্তু ধীরে) চললেও কিছু ARM board-এ সরাসরি crash করতে পারে — একটা বাস্তব portability বিপদ, যা আমরা misconception section-এ আরও দেখব।

Struct padding — alignment-এর সরাসরি দৃশ্যমান ফলাফল

Compiler struct-এর প্রতিটা field-কে তার নিজের প্রাকৃতিক alignment-এ রাখতে চায় — এমনকি এর জন্য মাঝে ফাঁকা byte (padding) ঢোকাতে হলেও।

struct Bad {
    char  a;    // 1 byte, address 0
    int   b;    // 4 byte — কিন্তু address 1-এ misaligned!
    char  c;    // 1 byte
};

Compiler b-কে address 1-এ রাখবে না (misaligned)। বরং address 1, 2, 3 — এই তিন byte padding হিসেবে ফাঁকা রেখে b-কে address 4-এ বসাবে (৪-এর গুণিতক)। তারপর c address 8-এ, আর পুরো struct-এর আকারও ৪-এর গুণিতক করতে শেষে আরও ৩ byte padding যোগ হবে।

offset:  0    1    2    3    4    5    6    7    8    9   10   11
field:  [a] [ ] [ ] [ ] [───── b ─────] [c] [ ] [ ] [ ]
         └pad──┘                        └──pad───┘

sizeof(struct Bad) = 12, যদিও a+b+c = মাত্র 6 byte দরকার!

পরের section-এ আমরা এই padding সরাসরি চোখে দেখব offsetof আর sizeof দিয়ে, আর field-এর ক্রম বদলে কীভাবে জায়গা বাঁচানো যায় তা দেখাব।

উদাহরণ

সম্পূর্ণ worked example — struct layout

চলুন একটা বাস্তব struct-এর জন্য পুরো memory layout হাতে হিসাব করি, ৬৪-bit x86-64 সিস্টেম ধরে নিয়ে (সাধারণ alignment নিয়ম: char→১, short→২, int/float→৪, long/double/pointer→৮)।

struct Record {
    char   flag;      // 1 byte
    double value;      // 8 byte
    int    count;      // 4 byte
    char*  name;        // 8 byte (pointer, 64-bit system)
    short  code;         // 2 byte
};

Naive যোগফল: 1+8+4+8+2=231 + 8 + 4 + 8 + 2 = 23 byte। কিন্তু alignment মেনে আসল layout সম্পূর্ণ ভিন্ন:

OffsetFieldআকারমন্তব্য
0flag1
1–7(padding)7value (৮-align) পেতে হলে offset 8 লাগবে
8–15value8৮-এর গুণিতক offset-এ ✓
16–19count4৪-এর গুণিতক offset-এ ✓
20–23name8??সমস্যা — offset 20, ৮-এর গুণিতক নয়!

এখানে থামুন — name (pointer, ৮-byte alignment দরকার) কে offset 20-এ বসানো যাবে না। Compiler তাই count-এর পরে আরও ৪ byte padding দেয়:

OffsetFieldআকার
0flag1
1–7padding7
8–15value8
16–19count4
20–23padding4
24–31name8
32–33code2
34–39padding (tail)6

মোট: sizeof(Record) = 40 byte — যদিও field-গুলোর প্রকৃত তথ্য মাত্র 1+8+4+8+2=231+8+4+8+2 = 23 byte। প্রায় ৪২% জায়গা শুধু padding।

নিজে চালিয়ে দেখুন

EXPERIMENT

sizeof — বিভিন্ন type, বিভিন্ন সিস্টেম

GCC / Clang, যেকোনো OS· ১০ মিনিট
#include <stdio.h>
#include <stdint.h>

int main(void) {
    printf("── fixed-width (সবসময় নির্দিষ্ট) ──\n");
    printf("int8_t   : %zu byte\n", sizeof(int8_t));
    printf("int16_t  : %zu byte\n", sizeof(int16_t));
    printf("int32_t  : %zu byte\n", sizeof(int32_t));
    printf("int64_t  : %zu byte\n", sizeof(int64_t));

    printf("\n── built-in (platform-নির্ভর) ──\n");
    printf("char     : %zu byte\n", sizeof(char));
    printf("short    : %zu byte\n", sizeof(short));
    printf("int      : %zu byte\n", sizeof(int));
    printf("long     : %zu byte\n", sizeof(long));
    printf("long long: %zu byte\n", sizeof(long long));
    printf("void*    : %zu byte\n", sizeof(void*));
    printf("size_t   : %zu byte\n", sizeof(size_t));

    return 0;
}

৬৪-bit Linux/macOS-এ (LP64 data model) সাধারণ আউটপুট:

── fixed-width (সবসময় নির্দিষ্ট) ──
int8_t   : 1 byte
int16_t  : 2 byte
int32_t  : 4 byte
int64_t  : 8 byte

── built-in (platform-নির্ভর) ──
char     : 1 byte
short    : 2 byte
int      : 4 byte
long     : 8 byte     ← Linux/macOS-এ 8, Windows-এ 4!
long long: 8 byte
void*    : 8 byte
size_t   : 8 byte

৬৪-bit Windows-এ (LLP64 data model) long-এর জন্য ভিন্ন ফল:

long     : 4 byte     ← Windows ইচ্ছাকৃতভাবে long-কে 32-bit রেখেছে
                          32-bit কোড-এর backward compatibility-র জন্য

এটাই এই experiment-এর মূল শিক্ষা: C standard শুধু সর্বনিম্ন আকার নির্ধারণ করে (int কমপক্ষে ১৬ bit, long কমপক্ষে int-এর সমান), বাস্তব আকার নির্ধারণ করে compiler আর OS-এর data model সিদ্ধান্ত। এই কারণেই network protocol বা file format-এ কখনো plain int/long ব্যবহার করা উচিত না — সবসময় int32_t, uint64_t-এর মতো fixed-width type, যাতে যেকোনো platform-এ ফলাফল predictable থাকে।

gcc -o sizes sizes.c && ./sizes
এটা কী প্রমাণ করে

Fixed-width type (int8_t, int32_t...) সব platform-এ একই আকার দেয়, কিন্তু language-এর built-in type (int, long, void*) platform ও architecture-নির্ভর — 'int সবসময় 4 byte' একটা বিপজ্জনক ধরে-নেওয়া কথা।

EXPERIMENT

Struct padding সরাসরি দেখা — offsetof

GCC / Clang· ১০ মিনিট
#include <stdio.h>
#include <stddef.h>   /* offsetof */

struct Record {
    char   flag;
    double value;
    int    count;
    char*  name;
    short  code;
};

struct RecordPacked {
    double value;
    char*  name;
    int    count;
    short  code;
    char   flag;
};

int main(void) {
    printf("── struct Record (naive ordering) ──\n");
    printf("flag  offset: %zu\n", offsetof(struct Record, flag));
    printf("value offset: %zu\n", offsetof(struct Record, value));
    printf("count offset: %zu\n", offsetof(struct Record, count));
    printf("name  offset: %zu\n", offsetof(struct Record, name));
    printf("code  offset: %zu\n", offsetof(struct Record, code));
    printf("sizeof       : %zu\n\n", sizeof(struct Record));

    printf("── struct RecordPacked (বড় থেকে ছোট ordering) ──\n");
    printf("value offset: %zu\n", offsetof(struct RecordPacked, value));
    printf("name  offset: %zu\n", offsetof(struct RecordPacked, name));
    printf("count offset: %zu\n", offsetof(struct RecordPacked, count));
    printf("code  offset: %zu\n", offsetof(struct RecordPacked, code));
    printf("flag  offset: %zu\n", offsetof(struct RecordPacked, flag));
    printf("sizeof       : %zu\n", sizeof(struct RecordPacked));

    return 0;
}

আউটপুট (৬৪-bit সিস্টেমে) — আগের example section-এর হাতের হিসাবের সাথে মিলিয়ে দেখুন:

── struct Record (naive ordering) ──
flag  offset: 0
value offset: 8
count offset: 16
name  offset: 24
code  offset: 32
sizeof       : 40

── struct RecordPacked (বড় থেকে ছোট ordering) ──
value offset: 0
name  offset: 8
count offset: 16
code  offset: 20
flag  offset: 22
sizeof       : 24

হুবহু মিলে গেল — 40 বনাম 24, ঠিক আগের হাতের হিসাবের মতো।

gcc -Wall -o padding padding.c && ./padding

নিজে যাচাই করুন: #pragma pack(1) (বা GCC-তে __attribute__((packed))) যোগ করে দেখুন sizeof কী হয় — padding সম্পূর্ণ বন্ধ হয়ে যাবে, কিন্তু তখন misaligned access-এর ঝুঁকি আসবে, বিশেষত ARM-এ।

এটা কী প্রমাণ করে

offsetof() দিয়ে আমাদের হাতে-করা worked example-এর হিসাব হুবহু কম্পাইলারের বাস্তব সিদ্ধান্তের সাথে মিলে যায় — padding একটা তাত্ত্বিক ধারণা নয়, সরাসরি পরিমাপযোগ্য।

নিজে বানান

BUILD IT

Stack Memory Layout Visualizer

C · ●●○○○
  1. কয়েকটা ভিন্ন type-এর local variable ঘোষণা করুন একটা function-এর ভেতর
  2. প্রতিটার address প্রিন্ট করুন (&variable)
  3. পরপর address-গুলোর মধ্যে ব্যবধান (byte-এ) হিসাব করুন
  4. ব্যবধানগুলো variable-এর sizeof আর alignment-এর সাথে মিলিয়ে ব্যাখ্যা করুন
#include <stdio.h>
#include <stdint.h>

int main(void) {
    char    c1 = 'A';
    int     i1 = 100;
    char    c2 = 'B';
    double  d1 = 3.14;
    int64_t big = 123456789;

    printf("c1  : %p  (size %zu)\n", (void*)&c1, sizeof(c1));
    printf("i1  : %p  (size %zu)\n", (void*)&i1, sizeof(i1));
    printf("c2  : %p  (size %zu)\n", (void*)&c2, sizeof(c2));
    printf("d1  : %p  (size %zu)\n", (void*)&d1, sizeof(d1));
    printf("big : %p  (size %zu)\n", (void*)&big, sizeof(big));

    /* address-গুলোকে integer বানিয়ে ব্যবধান হিসাব করি */
    uintptr_t a1 = (uintptr_t)&c1;
    uintptr_t a2 = (uintptr_t)&i1;
    printf("\nc1 → i1 ব্যবধান: %ld byte\n", (long)(a1 > a2 ? a1 - a2 : a2 - a1));

    return 0;
}

সতর্কতা: stack layout compiler, optimization level, আর ABI-র উপর নির্ভর করে — compiler variable reorder করতে পারে, padding দিতে পারে, এমনকি register-এ রাখতে পারে (address-ই না থাকতে পারে যদি optimize করে ফেলে)। -O0-এ কম্পাইল করুন যাতে প্রতিটা variable আসলেই stack-এ যায়, আর ফলাফল compiler-ভেদে আলাদা হবে বলে ধরে নিন — এটাই শেখার বিষয়, কোনো একটা “সঠিক” layout নেই।

নিজে বাড়ান:

  1. একটা array আর একটা struct একসাথে ঘোষণা করে তাদের ভেতরের element/field-এর address-ও দেখান — array-এর element গুলো কি সবসময় ঠিক sizeof(type) ব্যবধানে থাকে?
  2. -O0 বনাম -O2-এ কম্পাইল করে ব্যবধান বদলায় কি না দেখুন
  3. একই কোড ৩২-bit-এ কম্পাইল করে (gcc -m32, যদি সম্ভব হয়) pointer-সংক্রান্ত sizeof-এর পার্থক্য যাচাই করুন

বাস্তব সিস্টেমে

Byte আর word size যেখানে বাস্তবে সিদ্ধান্ত নেয়

  • IBM System/360 (১৯৬৪)। ৮-bit byte-কে commercial standard করে দেওয়া সিদ্ধান্ত — উপরে বিস্তারিত। আজও প্রতিটা char, প্রতিটা uint8_t তার উত্তরাধিকার বহন করে।

  • AMD64 / x86-64 transition (২০০৩-২০০৭)। ৪ GB address wall পার হতে পুরো industry ৬৪-bit-এ move করে — Windows XP x64 (২০০৫), macOS-এর ধাপে ধাপে ৬৪-bit transition, Linux distro-র amd64 build। এই পুরো migration-এর মূল চালিকাশক্তি ছিল আজকের লেসনের একটাই হিসাব: 232=42^{32} = 4 GB।

  • Y2K38 সমস্যা। Unix-এর time_t ঐতিহাসিকভাবে একটা signed ৩২-bit integer — সেকেন্ড গোনে ১৯৭০ সাল থেকে। এই সংখ্যা overflow করবে ২০৩৮ সালের ১৯ জানুয়ারি, ঠিক signed ৩২-bit-এর সর্বোচ্চ মান (23112.1×1092^{31}-1 \approx 2.1 \times 10^9 সেকেন্ড) পার হয়ে গেলে। আধুনিক সিস্টেমে time_t-কে ৬৪-bit করা এই একই word-size সমস্যার সরাসরি সমাধান — পরের module-এর signed integer overflow লেসনে এই আক্ষরিক গণিতটা ফিরে আসবে।

  • ARM alignment fault। পুরনো/embedded ARM (ARMv5 আর তার আগে) misaligned memory access-এ সরাসরি hardware exception তোলে — কোনো silent slow-path নেই। এই কারণে x86-এ নিখুঁত চলা C কোড (যা কাকতালীয়ভাবে misalignment-নির্ভর, যেমন network buffer-কে সরাসরি struct pointer-এ cast করা) ARM board-এ (রাউটার, IoT device) সরাসরি crash করতে পারে।

  • Protocol Buffers ও network header-এ explicit byte packing। Struct padding নেটওয়ার্কে পাঠানো ডেটার জন্য অপচয়, তাই network protocol header (TCP/IP, ইত্যাদি) কখনো raw C struct সরাসরি “wire”-এ পাঠায় না — বরং প্রতিটা field-এর অবস্থান explicit ভাবে সংজ্ঞায়িত করা হয়, #pragma pack বা manual byte-by-byte serialization দিয়ে। Level 7-এর networking module-এ এই বিস্তারিত।

  • Database page ও row alignment। PostgreSQL-এর মতো database internally row-এর ভেতর column-কে alignment মেনে সাজায় (আমাদের struct-এর মতোই), আর column-এর ক্রম বদলে (বড় থেকে ছোট) storage বাঁচানো একটা পরিচিত পারফরম্যান্স-টিউনিং কৌশল — ঠিক আমাদের RecordPacked উদাহরণের মতো, বাস্তব production database-এ। Level 8-এ বিস্তারিত।

  • 32-bit থেকে 64-bit pointer bloat — Java-র ফলাফল। ৬৪-bit JVM-এ প্রতিটা object reference pointer-এর মতোই ৮ byte হয়ে যায় (৩২-bit-এ ছিল ৪), memory ব্যবহার উল্লেখযোগ্য বেড়ে যায় — এই কারণেই JVM-এ Compressed Oops ফিচার আছে, যা ছোট heap-এ (< ৩২ GB) reference-কে আবার ৪-byte-এ ফিরিয়ে আনে, ৮-byte pointer-কে একটা scaled index হিসেবে encode করে।

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

“Byte আর word একই জিনিস, শুধু ভিন্ন নাম।”

সম্পূর্ণ ভিন্ন ধরনের একক। Byte সার্বজনীনভাবে ৮ bit — প্রতিটা আধুনিক system-এ, ব্যতিক্রমহীন। Word CPU-নির্ভর — এই লেসনেই দেখলাম ৮০৮০-এর ৮-bit word থেকে আজকের ৬৪-bit word পর্যন্ত পুরো range।

একটা সহজ পরীক্ষা: “একটা word কত byte?” প্রশ্নের কোনো সার্বজনীন উত্তর নেই — উত্তর নির্ভর করে “কোন CPU?” প্রশ্নের উপর। কিন্তু “একটা byte কত bit?” প্রশ্নের উত্তর সবসময়, সব জায়গায় ৮।

“Word সবসময় 32 bit — এটাই 'standard' word size।”

৩২-bit একটা সময়ের (মূলত ১৯৮৫-২০০৫) dominant word size ছিল, কিন্তু “standard” নয়। আজকের ডেস্কটপ/সার্ভার CPU-র word ৬৪-bit; অনেক microcontroller-এ এখনো ৮ বা ১৬-bit; কিছু বিশেষায়িত DSP chip-এ ২৪-bit word-ও দেখা যায় (audio processing-এ সাধারণ, কারণ ২৪-bit precision audio-র জন্য যথেষ্ট আর ৩২-bit-এর চেয়ে সাশ্রয়ী)।

“Word” শব্দটা নিজেই একটা আপেক্ষিক পরিভাষা — “এই CPU-র স্বাভাবিক একক” বোঝাতে, কোনো নির্দিষ্ট সংখ্যা বোঝাতে নয়। এই কারণেই কোনো প্রসঙ্গে “word” বললে সবসময় স্পষ্ট করে বলা উচিত কোন CPU/architecture-এর কথা হচ্ছে।

“sizeof(int) সবসময় 4, এটা নিয়ে ভাবার দরকার নেই।”

আজকের বেশিরভাগ mainstream desktop/server platform-এ ঠিকই — কিন্তু C standard শুধু নিশ্চয়তা দেয় sizeof(int) >= 2 bytes (কমপক্ষে ১৬ bit)। কিছু পুরনো/embedded compiler-এ int এখনও ১৬-bit।

আরও সূক্ষ্মভাবে, experiment section-এ দেখেছি long-ও সার্বজনীন নয় — Linux/macOS-এ ৮ byte, Windows-এ ৪ byte, যদিও দুটোই ৬৪-bit system। এই পার্থক্যকে বলে data model (LP64 বনাম LLP64)।

ব্যবহারিক নিয়ম: যদি আকার গুরুত্বপূর্ণ হয় (file format, network protocol, cross-platform code), কখনো plain int/long নয় — সবসময় <stdint.h>-এর int32_t, uint64_t-এর মতো fixed-width type ব্যবহার করুন, যেগুলো সংজ্ঞা অনুযায়ীই platform-নিরপেক্ষ।

“Misaligned memory access হয় সবসময় crash করে, নয়তো সবসময় ঠিকঠাক চলে — এটা এক ধরনের সার্বজনীন আচরণ।”

দুটোই ভুল, আর সঠিক উত্তর “architecture-নির্ভর” — ঠিক hood section-এ যা দেখেছি।

  • x86/x86-64: misaligned access সমর্থিত, শুধু ধীর (extra bus transaction)। Crash করে না।
  • কিছু ARM (বিশেষত পুরনো): misaligned access সরাসরি hardware fault — program বন্ধ হয়ে যায়।
  • আধুনিক ARM64: ডিফল্টে misaligned access সমর্থিত (কিছু ব্যতিক্রম ছাড়া, যেমন atomic instruction), কিন্তু performance penalty থাকে।

এই তিন সারির বাস্তবতাই দেখায় কেন “portable” কোড লেখার সময় alignment নিয়ে ধরে-নেওয়া বিপজ্জনক — যা আপনার x86 laptop-এ নীরবে চলে, সেটাই production ARM server বা IoT device-এ crash করতে পারে। এই কারণে memcpy (যা compiler-এর জন্য alignment-safe হিসেবে গ্যারান্টিযুক্ত) সবসময় raw pointer cast-এর চেয়ে নিরাপদ — লেসন ১-এর type-punning আলোচনার সাথে সরাসরি সংযুক্ত একটা নিয়ম।

বুঝেছেন কি না দেখুন

1

System/360-এর আগে byte-এর আকার নিয়ে industry-তে বিশৃঙ্খলা ছিল (৬-bit, ৯-bit, ইত্যাদি)। System/360-এর ৮-bit সিদ্ধান্তটা কি একটা “গাণিতিকভাবে সেরা” পছন্দ ছিল, নাকি অন্য কিছু? ব্যাখ্যা করুন।

যুক্তি

গাণিতিকভাবে “সেরা” ছিল না — বাণিজ্যিকভাবে যথেষ্ট আর প্রভাবশালী ছিল।

কোনো গাণিতিক প্রমাণ নেই যে ৮ bit “সেরা” byte size — এটা শুধু একটা প্রকৌশলগত trade-off: ৬-bit যথেষ্ট character-space দেয় না (মাত্র ৬৪টা code), আর তার চেয়ে বড় (১০, ১২ bit) হলে অহেতুক জায়গা নষ্ট হতো তখনকার character set-এর জন্য যেখানে ২৫৬টা code point-ই যথেষ্টের চেয়ে বেশি ছিল।

প্রকৃত সিদ্ধান্তকারী শক্তি ছিল বাণিজ্যিক আধিপত্য: System/360 এত ব্যাপকভাবে সফল হয় যে পুরো software industry তার convention-এর সাথে মানিয়ে নেয়, শুধু compatibility আর নেটওয়ার্ক-এফেক্টের কারণে (যত বেশি সিস্টেম ৮-bit ব্যবহার করে, তত বেশি নতুন সিস্টেমের জন্য ৮-bit বাছা “নিরাপদ” পছন্দ হয়ে ওঠে)। এটাই asymptotic-notation লেসনের সেই “asymptotically ভালো সবসময় বাস্তবে সেরা নয়” নীতির আরেকটা রূপ — এখানে প্রশ্নটা optimality নিয়েই না, বরং কে প্রথমে বাজারে জিতে গেল তা নিয়ে। যে কোনো একটা যুক্তিসঙ্গত পছন্দ (হয়তো ৭-bit, হয়তো ৮-bit) “জিতে” যেতে পারত যদি সঠিক কোম্পানি সেটা বাছত আর বাজারে সফল হতো — এটা একটা path-dependent historical outcome, প্রমাণযোগ্য গাণিতিক optimum নয়।

তুলনা করুন keyboard layout-এর সাথে: QWERTY “সেরা” arrangement নয় (Dvorak-এর মতো বিকল্প বিদ্যমান, কিছু গবেষণায় দ্রুততর), তবু QWERTY জিতেছে আগে থেকে established হয়ে যাওয়ায়। ৮-bit byte-এর গল্পও একই শ্রেণির — standardization-এর মূল্য প্রায়ই যেকোনো একটা নির্দিষ্ট বিকল্পের “অপ্টিমালিটি”-র চেয়ে বেশি গুরুত্বপূর্ণ।

2

একটা ৩২-bit system-এ একটা প্রোগ্রাম ১ কোটি (10,000,000) struct Node { int value; Node* next; } element-এর একটা linked list বানায়। একই প্রোগ্রাম ৬৪-bit system-এ চালালে (alignment-সহ, ৬৪-bit-এ ৮-byte pointer alignment ধরে নিন) মোট memory খরচ কতটা বাড়বে?

প্রয়োগ

৩২-bit system-এ:

struct Node {
    int   value;   // offset 0, 4 byte
    Node* next;     // offset 4, 4 byte (৩২-bit pointer, 4-align)
};
sizeof(Node) = 8 byte     (কোনো padding লাগে না — উভয় field 4-byte-aligned)

মোট: 10,000,000×8=80,000,00010,000,000 \times 8 = 80,000,000 byte =80= 80 MB (প্রায়)।

৬৪-bit system-এ:

struct Node {
    int   value;   // offset 0, 4 byte
    Node* next;     // ৮-byte pointer, ৮-align দরকার — কিন্তু offset 4 থেকে শুরু করলে misaligned
};

next কে offset 4-এ রাখা যাবে না (৮-এর গুণিতক নয়), তাই ৪ byte padding লাগবে value-এর পর, next শুরু হবে offset 8-এ:

offset 0-3  : value (4 byte)
offset 4-7  : padding (4 byte)
offset 8-15 : next (8 byte)
sizeof(Node) = 16 byte

মোট: 10,000,000×16=160,000,00010,000,000 \times 16 = 160,000,000 byte =160= 160 MB (প্রায়)।

ফলাফল: 8016080 \to 160 MB — ঠিক দ্বিগুণ, শুধু pointer আকার আর তার সাথে আসা padding-এর কারণে, যদিও প্রতিটা node-এর “প্রকৃত তথ্য” (int value) অপরিবর্তিত। এটাই আগে concept section-এ উল্লেখ করা “linked list-এ ৬৪-bit-এর দ্বিগুণ খরচ” দাবির সম্পূর্ণ, সংখ্যায়িত প্রমাণ — আর এই কারণেই বড়, memory-সংবেদনশীল data structure-এ (যেমন game engine বা large-scale server) ইঞ্জিনিয়াররা প্রায়ই raw pointer-এর বদলে ৩২-bit array index ব্যবহার করেন, এমনকি ৬৪-bit system-এও — pointer-এর নমনীয়তা ছেড়ে দিয়ে memory বাঁচানোর জন্য।

3

হাতে হিসাব করে বলুন struct { char a; short b; char c; int d; } (alignment নিয়ম: char→১, short→২, int→৪ byte alignment)-এর sizeof কত হবে (offset-সহ পুরো layout দেখান)? তারপর field-এর ক্রম বদলে সর্বনিম্ন সম্ভব sizeof বের করুন।

যুক্তি

মূল ক্রম — char a; short b; char c; int d;:

OffsetFieldআকারমন্তব্য
0a (char)1
1padding1b (short, ২-align) পেতে offset 2 লাগবে
2–3b (short)2২-এর গুণিতক ✓
4c (char)1
5–7padding3d (int, ৪-align) পেতে offset 8 লাগবে
8–11d (int)4৪-এর গুণিতক ✓

sizeof = 12 byte (প্রকৃত তথ্য মাত্র 1+2+1+4=81+2+1+4=8 byte, ৩৩% padding)।

পুনর্বিন্যস্ত — বড় থেকে ছোট alignment: int d; short b; char a; char c;:

OffsetFieldআকার
0–3d (int)4
4–5b (short)2
6a (char)1
7c (char)1

sizeof = 8 byte — কোনো padding লাগেনি, কারণ প্রতিটা field নিজে থেকেই তার আগের field-গুলোর যোগফলের সাথে align হয়ে গেছে।

সাধারণ নিয়ম যা এখান থেকে বের হয়: field-গুলোকে বড় থেকে ছোট alignment অনুযায়ী সাজালে সাধারণত padding সর্বনিম্ন হয় (যদিও সবসময় শূন্য নিশ্চিত নয়, নির্ভর করে সব field-এর মোট আকার সবচেয়ে বড় alignment-এর গুণিতক কি না তার উপর — এখানে 4+2+1+1=84+2+1+1=8, আর 88 নিজেই int-এর ৪-alignment-এর গুণিতক, তাই কোনো tail padding-ও লাগেনি)। কিছু আধুনিক compiler (Rust-এর ডিফল্ট layout, এবং কিছু C compiler flag দিয়ে) স্বয়ংক্রিয়ভাবেই field reorder করে এই সাশ্রয় পায়, কিন্তু C স্ট্যান্ডার্ড অনুযায়ী struct-এর field ক্রম source code-এর ক্রম অনুসরণ করতে বাধ্য — তাই C-তে এই optimization প্রোগ্রামারকেই হাতে করতে হয়।

4

একটা embedded system-এ address bus মাত্র ২০-bit চওড়া। এই সিস্টেম সর্বোচ্চ কত memory address করতে পারবে (byte-এ, আর মানুষের পড়ার সুবিধার একক যেমন KB/MB-তেও দিন)?

প্রয়োগ

2202^{20} টা আলাদা address সম্ভব একটা ২০-bit address bus দিয়ে (combinatorics-এর product rule — প্রতিটা bit স্বাধীনভাবে 0 বা 1 হতে পারে, মোট সমাবেশ 2202^{20})।

220=1,048,576 byte=1,024 KiB=1 MiB (ঠিক)2^{20} = 1,048,576 \text{ byte} = 1,024 \text{ KiB} = 1 \text{ MiB (ঠিক)}

সর্বোচ্চ ১ MB memory address করা যাবে — এই bus-এ RAM যতই লাগানো হোক (২ MB, ১৬ MB), সিস্টেম শুধু প্রথম ১ MB-ই “দেখতে” পাবে, কারণ address-এর জন্য bit-ই নেই বাকিটা চেনানোর।

ঐতিহাসিক প্রসঙ্গ: এটা ঠিক আসল IBM PC (Intel 8086, ১৯৭৮)-এর সীমা — 8086-এর address bus ছিল ২০-bit, তাই ঠিক 220=12^{20} = 1 MB address space। এই একই সীমার কারণে DOS-যুগে বিখ্যাত “conventional memory” (৬৪০ KB, বাকিটা BIOS/video card-এর জন্য সংরক্ষিত) সমস্যা জন্ম নেয় — যা এই লেসনের concept section-এর “৩২-bit 4GB wall”-এর ঠিক আগের প্রজন্মের সংস্করণ, একই গাণিতিক নীতির (address bus width → সর্বোচ্চ addressable space) আরেকটা প্রয়োগ, শুধু সংখ্যাটা ছোট।

5

আপনি একটা নতুন file format ডিজাইন করছেন যেখানে লক্ষ লক্ষ ছোট record থাকবে, প্রতিটাতে কয়েকটা field (একটা ID, একটা timestamp, একটা status flag, একটা ছোট string)। Memory/disk জায়গা বাঁচানো এই ডিজাইনের প্রধান লক্ষ্য। এই লেসনে শেখা কোন কোন নীতি প্রয়োগ করবেন, আর কেন?

ডিজাইন

এই প্রশ্নটা লেসনের প্রায় প্রতিটা ধারণা একসাথে প্রয়োগ করতে বলছে।

১. Fixed-width type বাছুন, platform-নির্ভর type নয়। int, long নয় — int32_t, uint64_t (timestamp-এর জন্য), uint8_t (status flag-এর জন্য, যদি কয়েকটা মাত্র অবস্থা থাকে)। এতে ফাইলটা platform-নিরপেক্ষ থাকবে, আর প্রতিটা field-এর আকার সবচেয়ে ছোট যথেষ্ট size-এ রাখা যাবে (একটা status flag-এর জন্য পুরো ৪-byte int অপচয়)।

২. Field-গুলো বড় থেকে ছোট alignment অনুযায়ী সাজান। যেমন RecordPacked উদাহরণে দেখা গেছে, শুধু ক্রম বদলে ৩০-৪০% পর্যন্ত padding বাঁচানো সম্ভব — লক্ষ লক্ষ record-এ এটা যথেষ্ট বড় সাশ্রয়।

৩. Explicit packing বিবেচনা করুন, কিন্তু trade-off বুঝে। #pragma pack(1) দিয়ে padding সম্পূর্ণ বন্ধ করা যায় — জায়গা সর্বোচ্চ বাঁচে, কিন্তু (ক) misaligned access-এর ঝুঁকি (কিছু ARM-এ crash), (খ) CPU-তে load করে ব্যবহারের সময় প্রতিবার misaligned penalty। যদি ফাইলটা মূলত disk/network-এ সংরক্ষিত থাকবে আর কম-ঘন ঘন পড়া হবে, packing worth it — disk জায়গা আর I/O bandwidth সাশ্রয় বেশি গুরুত্বপূর্ণ। যদি record বারবার in-memory প্রসেস করা হবে (hot path), alignment বজায় রেখে সামান্য বেশি জায়গা খরচ করাই ভালো — CPU cycle বাঁচবে।

৪. Variable-length data (string) আলাদাভাবে সামলান। একটা “ছোট string” যদি নির্দিষ্ট max length-এর না হয়, সরাসরি struct-এ বসানো যাবে না (compile-time known size লাগে)। সাধারণ সমাধান: হয় একটা fixed-size buffer (waste হতে পারে ছোট string-এ), অথবা string-টাকে আলাদা section-এ রেখে struct-এ শুধু একটা offset+length (এই module-এর পরের একটা লেসন — serialization — এই প্যাটার্নটা বিস্তারিত দেখাবে)।

৫. Endianness ঠিক করুন explicit ভাবে। যেহেতু ফাইলটা সম্ভবত ভিন্ন সিস্টেমে পড়া হবে (বা অন্তত ভবিষ্যতে হতে পারে), byte order একটা নির্দিষ্ট convention-এ (সাধারণত little-endian, আধুনিক CPU-র সাথে মিলিয়ে) স্থির করে রাখুন এবং documentation-এ লিখে রাখুন — পরের একটা লেসনের বিষয়, কিন্তু ডিজাইনের এই পর্যায়েই সিদ্ধান্ত নেওয়া উচিত।

সংক্ষেপে: এই একটা design প্রশ্ন এই পুরো লেসনের প্রতিটা নীতি (fixed-width type, alignment, padding, packing trade-off) একসাথে বাস্তব প্রকৌশল সিদ্ধান্তে রূপান্তরিত করে — যা প্রতিটা বাস্তব binary file format (PNG chunk, ELF section header, database page format) ডিজাইনারকে ঠিক এই প্রশ্নগুলোর মুখোমুখি হতে হয়।

এরপর কী

এরপর কী

তিনটা লেসনে আমরা bit pattern-এর “ভাষা” আর “সংগঠন” প্রতিষ্ঠা করলাম: bit মানে কী আর কেন binary (লেসন ১), সেই bit পড়া-লেখার জন্য কোন base ব্যবহার হয় আর কেন (লেসন ২), আর কতগুলো bit মিলে “একটা জিনিস” গণ্য হয় — byte, word, আর তার সাথে আসা address, pointer, আর alignment-এর পুরো জাল (এই লেসন)।

এখন প্রশ্নটা সরাসরি সংখ্যায় ফেরা যাক: একটা byte বা word-কে আসলে একটা সংখ্যা হিসেবে পড়ব কীভাবে? এতক্ষণ আমরা ধরে নিয়েছি “positional notation প্রয়োগ করুন” — কিন্তু এটা শুধু unsigned (অ-ঋণাত্মক) সংখ্যার জন্য সোজা। ঋণাত্মক সংখ্যা কীভাবে encode হবে? Sign আলাদা একটা bit দিয়ে চিহ্নিত করলে কী সমস্যা হয় (এবং কেন +0 আর −0 দুটো আলাদা representation তৈরি হয়ে যায়)? আর কীভাবে two’s complement — যা Level 0-এর modular arithmetic লেসনে আমরা ইতিমধ্যে mod 2ⁿ arithmetic হিসেবে সংজ্ঞায়িত করেছি — এই সমস্যাগুলো একবারে সমাধান করে দেয়? পরের লেসনগুলোতে (unsigned integer representation, তারপর signed integer ও two’s complement) আমরা ঠিক এই প্রশ্নগুলোর উত্তর খুঁজব — এই তিনটা লেসনে গড়ে তোলা ভিত্তির উপর দাঁড়িয়ে।

আরও পড়ুন

  • Computer Organization and Design, Chapter 2 — David Patterson, John Hennessy · Word size, addressing, ও alignment-এর প্রামাণ্য আলোচনা
  • IBM System/360 Principles of Operation (1964) — IBM Corporation · ৮-bit byte ও ৩২-bit word-কে commercial standard হিসেবে প্রতিষ্ঠাকারী মূল ডকুমেন্ট
  • What Every Programmer Should Know About Memory — Ulrich Drepper (2007) · Alignment ও memory access cost নিয়ে গভীর, ব্যবহারিক আলোচনা — Level 3/11-এর পূর্বপ্রস্তুতি