Bit, Nibble, Byte, Word — সংগঠনের একক
Bits, Bytes, and Words
Bit থেকে nibble, byte, word — কতগুলো bit মিলে 'একটা জিনিস' হয় সেই সিদ্ধান্তই CPU-র register width, memory-র ঠিকানা, pointer-এর আকার, আর struct-এর ভেতরের ফাঁকা জায়গা পর্যন্ত সবকিছু নির্ধারণ করে।
আগে এটা বুঝি
একটা প্রশ্ন দিয়ে শুরু করি যেটা প্রায় প্রতিটা 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-এ 8int কেন ৪ byte? সবসময়ই কি ৪? void* কেন সিস্টেমভেদে বদলায়,
কিন্তু char কখনো বদলায় না?
গত দুই লেসনে আমরা বারবার “byte”, “৩২-bit”, “৪-byte integer” বলেছি, ধরে নিয়ে এগুলো সার্বজনীন সত্য। এই লেসনে আমরা সেই ধরে নেওয়া জিনিসটাকেই প্রশ্ন করব: কতগুলো bit মিলে “একটা জিনিস” গণ্য হয়, আর এই সিদ্ধান্তটা কোথা থেকে আসে?
উত্তরটা আংশিক সার্বজনীন (byte সবখানে ৮ bit — একটা ঐতিহাসিক যুদ্ধের চূড়ান্ত ফলাফল) আর আংশিক প্রেক্ষাপট-নির্ভর (word আপনার CPU-র উপর নির্ভর করে বদলায়)। এই পার্থক্যটাই আজকের কেন্দ্রীয় বিষয়।
মূল ধারণা
চারটা একক — সংজ্ঞা
| একক | আকার | সম্পর্ক |
|---|---|---|
| Bit | ১ bit | মৌলিক একক (লেসন ১) |
| Nibble | ৪ bit | অর্ধেক byte — ঠিক ১টা হেক্স digit (লেসন ২) |
| Byte | ৮ bit | ২টা nibble — আজকের universal ন্যূনতম addressable একক |
| Word | CPU-নির্ভর (আজ সাধারণত ১৬/৩২/৬৪ 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 দরকার ছিল ( যথেষ্ট নয়)।
- ৮-bit দেয় টা সম্ভাব্য code — যথেষ্ট বেশি মার্জিন রেখে সব দরকারি character ধরার জন্য।
- ৮, ২-এর ঘাত — লেসন ২-তেই আমরা দেখেছি এটা hex-এর সাথে নিখুঁত মেলে (), যদিও এই যুক্তিটা সিদ্ধান্তের কারণ ছিল না, বরং একটা 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 আজও তৈরি হয়)।
| যুগ | উদাহরণ CPU | Word size | Address space | সাল |
|---|---|---|---|---|
| প্রাথমিক microprocessor | Intel 8080 | ৮ bit | ৬৪ KB | ১৯৭৪ |
| প্রথম IBM PC | Intel 8086 | ১৬ bit | ১ MB | ১৯৭৮ |
| ৩২-bit যুগ | Intel 80386 | ৩২ bit | ৪ GB | ১৯৮৫ |
| আধুনিক | x86-64 (AMD64) | ৬৪ bit | তাত্ত্বিক ১৬ EB (ব্যবহারিক ~২৫৬ TB) | ২০০৩ |
| Embedded | ARM Cortex-M0 | ৩২ bit | চিপ-ভেদে | আজও |
Word size-এর সবচেয়ে সরাসরি প্রভাব: address space-এর সীমা।
একটা n-bit address দিয়ে সর্বোচ্চ টা আলাদা memory
location চেনানো যায় — combinatorics-এর সেই product rule আবার,
এবার address-এর প্রতিটা bit-এর জন্য।
৩২-bit-এর বিখ্যাত “4GB wall” — সম্পূর্ণ হিসাব
এর মানে একটা ৩২-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-এর সমান।
| System | sizeof(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 সবচেয়ে গুরুত্বপূর্ণ ( নাকি ?) — সেটা একটা আলাদা প্রশ্ন, endianness, যা এই module-এর পরের একটা লেসনের বিষয়। এই লেসনে আমরা শুধু প্রতিষ্ঠা করছি: একটা multi-byte value কয়েকটা পরপর byte-এর একটা contiguous group, আর সেই গ্রুপের প্রথম byte-এর address-ই পুরো value-র “address” হিসেবে গণ্য হয়।
Alignment — কেন address-ও গুরুত্বপূর্ণ, শুধু ক্রম নয়
এতক্ষণ আমরা বলেছি একটা int “৪টা পরপর byte” দখল করে। কিন্তু
প্রশ্ন: কোন ৪টা byte? যেকোনো ৪টা পরপর byte, নাকি কিছু
নির্দিষ্ট address-ই ভালো?
নিয়ম: একটা n-byte value-কে সাধারণত এমন address-এ রাখা হয়
যা n-এর গুণিতক। একে বলে alignment। int (৪ 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 + জোড়া লাগানোপরিণতি:
- 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 যোগফল: byte। কিন্তু alignment মেনে আসল layout সম্পূর্ণ ভিন্ন:
| Offset | Field | আকার | মন্তব্য |
|---|---|---|---|
| 0 | flag | 1 | — |
| 1–7 | (padding) | 7 | value (৮-align) পেতে হলে offset 8 লাগবে |
| 8–15 | value | 8 | ৮-এর গুণিতক offset-এ ✓ |
| 16–19 | count | 4 | ৪-এর গুণিতক offset-এ ✓ |
| 20–23 | name | 8?? | সমস্যা — offset 20, ৮-এর গুণিতক নয়! |
এখানে থামুন — name (pointer, ৮-byte alignment দরকার) কে
offset 20-এ বসানো যাবে না। Compiler তাই count-এর পরে আরও ৪
byte padding দেয়:
| Offset | Field | আকার |
|---|---|---|
| 0 | flag | 1 |
| 1–7 | padding | 7 |
| 8–15 | value | 8 |
| 16–19 | count | 4 |
| 20–23 | padding | 4 |
| 24–31 | name | 8 |
| 32–33 | code | 2 |
| 34–39 | padding (tail) | 6 |
মোট: sizeof(Record) = 40 byte — যদিও field-গুলোর প্রকৃত
তথ্য মাত্র byte। প্রায় ৪২% জায়গা শুধু
padding।
নিজে চালিয়ে দেখুন
sizeof — বিভিন্ন type, বিভিন্ন সিস্টেম
#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 && ./sizesFixed-width type (int8_t, int32_t...) সব platform-এ একই আকার দেয়, কিন্তু language-এর built-in type (int, long, void*) platform ও architecture-নির্ভর — 'int সবসময় 4 byte' একটা বিপজ্জনক ধরে-নেওয়া কথা।
Struct padding সরাসরি দেখা — offsetof
#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 একটা তাত্ত্বিক ধারণা নয়, সরাসরি পরিমাপযোগ্য।
নিজে বানান
Stack Memory Layout Visualizer
- কয়েকটা ভিন্ন type-এর local variable ঘোষণা করুন একটা function-এর ভেতর
- প্রতিটার address প্রিন্ট করুন (&variable)
- পরপর address-গুলোর মধ্যে ব্যবধান (byte-এ) হিসাব করুন
- ব্যবধানগুলো 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 নেই।
নিজে বাড়ান:
- একটা array আর একটা struct একসাথে ঘোষণা করে তাদের ভেতরের element/field-এর address-ও দেখান — array-এর element গুলো কি সবসময় ঠিক sizeof(type) ব্যবধানে থাকে?
-O0বনাম-O2-এ কম্পাইল করে ব্যবধান বদলায় কি না দেখুন- একই কোড ৩২-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-এর মূল চালিকাশক্তি ছিল আজকের লেসনের একটাই হিসাব: GB।
-
Y2K38 সমস্যা। Unix-এর
time_tঐতিহাসিকভাবে একটা signed ৩২-bit integer — সেকেন্ড গোনে ১৯৭০ সাল থেকে। এই সংখ্যা overflow করবে ২০৩৮ সালের ১৯ জানুয়ারি, ঠিক signed ৩২-bit-এর সর্বোচ্চ মান ( সেকেন্ড) পার হয়ে গেলে। আধুনিক সিস্টেমে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 আলোচনার সাথে সরাসরি সংযুক্ত একটা নিয়ম।
বুঝেছেন কি না দেখুন
1System/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 খরচ কতটা
বাড়বে?
প্রয়োগ
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)মোট: byte 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মোট: byte MB (প্রায়)।
ফলাফল: 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 বের করুন।
যুক্তি
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;:
| Offset | Field | আকার | মন্তব্য |
|---|---|---|---|
| 0 | a (char) | 1 | — |
| 1 | padding | 1 | b (short, ২-align) পেতে offset 2 লাগবে |
| 2–3 | b (short) | 2 | ২-এর গুণিতক ✓ |
| 4 | c (char) | 1 | — |
| 5–7 | padding | 3 | d (int, ৪-align) পেতে offset 8 লাগবে |
| 8–11 | d (int) | 4 | ৪-এর গুণিতক ✓ |
sizeof = 12 byte (প্রকৃত তথ্য মাত্র byte,
৩৩% padding)।
পুনর্বিন্যস্ত — বড় থেকে ছোট alignment: int d; short b; char a; char c;:
| Offset | Field | আকার |
|---|---|---|
| 0–3 | d (int) | 4 |
| 4–5 | b (short) | 2 |
| 6 | a (char) | 1 |
| 7 | c (char) | 1 |
sizeof = 8 byte — কোনো padding লাগেনি, কারণ প্রতিটা field
নিজে থেকেই তার আগের field-গুলোর যোগফলের সাথে align হয়ে গেছে।
সাধারণ নিয়ম যা এখান থেকে বের হয়: field-গুলোকে বড় থেকে
ছোট alignment অনুযায়ী সাজালে সাধারণত padding সর্বনিম্ন হয়
(যদিও সবসময় শূন্য নিশ্চিত নয়, নির্ভর করে সব field-এর মোট আকার
সবচেয়ে বড় alignment-এর গুণিতক কি না তার উপর — এখানে ,
আর নিজেই 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-তেও দিন)?
প্রয়োগ
টা আলাদা address সম্ভব একটা ২০-bit address bus দিয়ে
(combinatorics-এর product rule — প্রতিটা bit স্বাধীনভাবে 0
বা 1 হতে পারে, মোট সমাবেশ )।
সর্বোচ্চ ১ MB memory address করা যাবে — এই bus-এ RAM যতই লাগানো হোক (২ MB, ১৬ MB), সিস্টেম শুধু প্রথম ১ MB-ই “দেখতে” পাবে, কারণ address-এর জন্য bit-ই নেই বাকিটা চেনানোর।
ঐতিহাসিক প্রসঙ্গ: এটা ঠিক আসল IBM PC (Intel 8086, ১৯৭৮)-এর সীমা — 8086-এর address bus ছিল ২০-bit, তাই ঠিক 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-এর পূর্বপ্রস্তুতি