Module ২ · Variable আর data type
Sizeof আর limits.h: আন্দাজ না করে মেশিনকে জিজ্ঞেস করা
এই lesson-এ যা শিখবে
- Table মুখস্থ না করে
sizeofদিয়ে মেশিনকে type-র মাপ জিজ্ঞেস করতে পারবে। <limits.h>-র প্রতিটা integer সীমা আর<float.h>-র প্রতিটা দশমিক সীমা ছাপতে পারবে।- মাপটা প্রতিটা মেশিনে এক হতে হলে কোন fixed-width type নিতে হবে বলতে পারবে।
Kenji-র program এক বছর ধরে ওর laptop-এ চলে। তারপর সেটা একটা card reader-র ছোট chip-এ তোলা হয়, আর 32,767-র উপরের প্রতিটা সংখ্যা ভুল বেরোয়।
ও bug লেখেনি। ও একটা মাপ মাথায় লিখে রেখেছিল, আর সেটা নিয়ে এমন একটা মেশিনে গিয়েছিল যে একমত না। এই পুরো lesson একটাই অভ্যাস: জিজ্ঞেস করো, আন্দাজ করো না।
sizeof: যে operator byte-এ উত্তর দেয়
sizeof দেখতে function-র মতো, আর ওটা function না। ওটা ভাষার ভেতরে বসানো একটা operator, আর program চালু হওয়ার আগেই compiler তার উত্তর বের করে ফেলে।
sizeof-র দুই রূপ
sizeof(int) একটা TYPE-র জন্য bracket লাগবেই
sizeof marks একটা EXPRESSION-র জন্য লাগে না
sizeof(marks) এটাও ঠিক, আর বেশির ভাগ মানুষ এটাই লেখে
sizeof marks + 1 মানে (sizeof marks) + 1, যেটা 5
sizeof (marks + 1) মানে expression-টার মাপ, যেটা 4
- উত্তরটা byte-র গোনা, আর একটা byte হলো
CHAR_BITbit, যেটা তুমি যেখানেই যাও 8। - যত C implementation কোনোদিন ছিল, সবগুলোতেই
sizeof(char)সংজ্ঞা অনুযায়ী 1। - ভেতরের expression-টা কখনো চলে না।
sizeof(x / 0)একটা মাপ, crash না। - এর type হলো
size_t, আর তার specifier%zu।
তাই type-র জন্য bracket বাধ্যতামূলক আর variable-র জন্য ঐচ্ছিক, আর ওই card-র শেষ দুই লাইন এমন একটা ফাঁদ যেটা দুইবার পড়া দরকার।
size_t, আর তার সঙ্গের %zu
sizeof একটা int ফেরত দেয় না। ও ফেরত দেয় একটা size_t, একটা unsigned type, যেটা মেশিনের রাখতে পারা সবচেয়ে বড় জিনিসের মাপ ধরার মতো চওড়া।
Playground-এ আর যেকোনো 64 bit মেশিনে ওটা 8 byte, আর ছোট একটা chip-এ 2 byte। এটাই ওর মানে: ওকে মেশিন ঠিক করে, কারো বেছে নেওয়া কোনো সংখ্যা না।
Unsigned বলেই size_t কখনো ঋণাত্মক হতে পারে না, আর শূন্য পেরিয়ে বিয়োগ করলে lesson 2-র বর্ণনামতোই ঘুরে যায়।
তাই ওটা %zu দিয়ে ছাপো, আর অঙ্কের জন্য সাধারণ int চাইলে cast-টা নিজে লেখো আর বুঝে লেখো।
limits.h আর float.h: ছাপা, টুকে রাখা না
দুইটা header-এ প্রতিটা সীমার মান থাকে, তুমি যে মেশিনে আছ তার জন্য আগে থেকেই ঠিক করা।
#include <stdio.h>
#include <limits.h>
#include <float.h>
int main(void)
{
printf("bits in a byte : %d\n", CHAR_BIT);
printf("int range : %d to %d\n", INT_MIN, INT_MAX);
printf("long long top : %lld\n", LLONG_MAX);
printf("float digits : %d\n", FLT_DIG);
printf("double digits : %d\n", DBL_DIG);
printf("double largest : %e\n", DBL_MAX);
return 0;
}
bits in a byte : 8
int range : -2147483648 to 2147483647
long long top : 9223372036854775807
float digits : 6
double digits : 15
double largest : 1.797693e+308
FLT_DIG হলো 6 আর DBL_DIG হলো 15। Lesson 3 বলেছিল "প্রায় 7" আর "প্রায় 15 বা 16"; এগুলোই সেই সংখ্যা, যেগুলোর পিছনে মেশিন সত্যিই দাঁড়াবে।
তাই তুমি যে সীমা ছেপেছ সেটা আজকের মেশিন নিয়ে একটা সত্য। মুখস্থ থেকে লেখা সীমা একটা আশা।
long-কেই সন্দেহ করতে হবে
এখানে এমন একটা তথ্য, যেটা তুমি নিজে যাচাই করতে পারো, আর এই পুরো lesson থাকার কারণও এটাই।
Playground-এ, যেটা 64 bit Linux চালায়, sizeof(long) হলো 8। একই processor আর একই compiler-র একটা Windows মেশিনে ওটা 4।
একই code, একই chip, একই GCC। দুইটা উত্তর। কোনোটাতেই কিছু ভুল নেই: standard কেবল কথা দেয় long প্রায় দুইশো কোটির এদিক-ওদিক পর্যন্ত পৌঁছাবে, আর দুইটা মেশিনই সেই কথা রাখে।
এই module-র বাকি প্রতিটা মাপ দুই জায়গাতেই মেলে: char 1, short 2, int 4, long long 8, float 4, double 8।
তাই এই track long long লেখে আর কখনো long না, আর নিচের অনুশীলনটা তোমাকে এমন একটা মাপের তালিকা ছাপতে বলে যেখানে long ইচ্ছে করেই নেই।
sizeof থেকে এসেছে। শেষ দণ্ডের ফুটকি দেওয়া অর্ধেকটাই এই track-র long long লেখার কারণ।stdint.h: মাপটা সব জায়গায় এক হতে হলে
মাঝে মাঝে "অন্তত 4 byte" যথেষ্ট না। একটা network packet, একটা file format আর একটা hash-র এমন একটা মান লাগে যেটা প্রতিটা মেশিনে, চিরকাল, ঠিক 32 bit চওড়া।
<stdint.h> তোমাকে সেই type-গুলো দেয়, আর তাদের নামেই চওড়াটা লেখা থাকে।
| Type | চওড়া | সীমা | কীসের জন্য |
|---|---|---|---|
int8_t | 8 bit | -128 থেকে 127 | ঠাসা structure-এ ছোট একটা signed মান |
uint8_t | 8 bit | 0 থেকে 255 | কাঁচা একটা byte, রঙের একটা ভাগ |
int16_t | 16 bit | -32,768 থেকে 32,767 | একটা audio sample |
uint16_t | 16 bit | 0 থেকে 65,535 | একটা port number |
int32_t | 32 bit | প্রায় দুইশো কোটির এদিক-ওদিক | সাধারণ গোনার ঘর, যার মাপ বদলানো চলবে না |
uint32_t | 32 bit | 0 থেকে 4,294,967,295 | একটা IPv4 address, একটা checksum |
int64_t | 64 bit | প্রায় 9.2 কুইন্টিলিয়নের এদিক-ওদিক | একটা timestamp, একটা file-র মাপ |
এগুলো নতুন type না। এগুলো সাধারণ type-গুলোরই অন্য নাম, প্রতিটা মেশিনে এমনভাবে বাছা যাতে চওড়াটা ঠিক থাকে। Playground-এ int32_t হলো int।
এগুলো ছাপতে একটু সাবধানতা লাগে, কারণ নিচের আসল type মেশিনভেদে আলাদা। এই module-র ভেতরে একটা fixed-width মান long long আর %lld দিয়ে পড়ো আর ছাপো, যেটা সবগুলোর জন্যই যথেষ্ট চওড়া।
তাই সাধারণ গোনার জন্য সাধারণ int নাও, আর bit-র সংখ্যাটা যখন নকশারই অংশ তখন একটা fixed-width type নাও।
sizeof-র দুই রূপ, একটা type-র উপর আর একটা variable-র উপর, আর শেষ লাইনে অগ্রাধিকারের ফাঁদ।
#include <stdio.h>
int main(void)
{
int marks = 90;
printf("sizeof(int) : %zu\n", sizeof(int));
printf("sizeof marks : %zu\n", sizeof marks);
printf("sizeof marks + 1 : %zu\n", sizeof marks + 1);
printf("sizeof (marks + 1) : %zu\n", sizeof (marks + 1));
return 0;
}
sizeof(int) : 4
sizeof marks : 4
sizeof marks + 1 : 5
sizeof (marks + 1) : 4
তৃতীয় লাইনটা 5, কারণ sizeof +-র চেয়ে শক্ত করে ধরে। ও মাপটা চেয়েছে, 4 পেয়েছে, তারপর তাতে 1 যোগ করেছে। Bracket দিলে প্রশ্নটাই থাকে না।
এই module-র প্রতিটা মাপ, মেশিনের ছাপা। long ইচ্ছে করেই বাদ, আর শেষ লাইনটা কারণটা বলে।
#include <stdio.h>
int main(void)
{
printf("char : %zu\n", sizeof(char));
printf("short : %zu\n", sizeof(short));
printf("int : %zu\n", sizeof(int));
printf("long long : %zu\n", sizeof(long long));
printf("float : %zu\n", sizeof(float));
printf("double : %zu\n", sizeof(double));
printf("size_t : %zu\n", sizeof(size_t));
printf("long : ask your own machine\n");
return 0;
}
char : 1
short : 2
int : 4
long long : 8
float : 4
double : 8
size_t : 8
long : ask your own machine
ওই সাতটা সংখ্যা Playground-এ আর একটা Windows laptop-এ এক। long-র জন্য একটা লাইন যোগ করো, আর দুইটা মেশিন আর একমত থাকে না।
ছোট একটা packet-র header: একটা version byte, একটা port আর একটা timestamp। প্রতিটা ঘরের চওড়া নকশারই অংশ।
#include <stdio.h>
#include <stdint.h>
int main(void)
{
uint8_t version = 4;
uint16_t port = 443;
int64_t timestamp = 1758240000;
printf("version : %lld in %zu byte\n", (long long)version, sizeof version);
printf("port : %lld in %zu bytes\n", (long long)port, sizeof port);
printf("timestamp : %lld in %zu bytes\n", (long long)timestamp, sizeof timestamp);
return 0;
}
version : 4 in 1 byte
port : 443 in 2 bytes
timestamp : 1758240000 in 8 bytes
ছাপার জন্য প্রতিটা মান long long-এ চওড়া করা হয়েছে, যেটা সবসময় কাজ করে আর কখনো চমকে দেয় না। <inttypes.h>-র সঠিক specifier-গুলো Module 15-র বিষয়।
এটা কোথায় কাজে লাগছে
- TCP-র header। উৎস আর গন্তব্যের port হলো
uint16_t, sequence number হলোuint32_t। ওই চওড়াগুলো গোটা internet যে standard মানে তাতেই লেখা, তাই "আরও চওড়া হতেও পারে" এমন একটা C type সেখানে অকেজো। - PNG file format। প্রতিটা chunk শুরু হয় 32 bit big-endian একটা দৈর্ঘ্য দিয়ে। প্রতিটা ভাষার প্রতিটা PNG reader ওই ঘরটাকে ঠিক 32 bit হিসেবেই declare করে, আর
libpngসেটা করে<stdint.h>-র নাম দিয়ে। - SHA-256। পুরো hash-টা সংজ্ঞায়িত 32 bit unsigned গণিতে, ঘুরে যাওয়া সহ। সাধারণ
intদিয়ে লিখলে একটা 16 bit chip-এ অন্য উত্তর আসত, তাই প্রতিটা implementationuint32_tব্যবহার করে। - Embedded firmware। একটা 8 bit microcontroller-এ
int2 byte, তাই laptop-এ চলা একই source সেখানে 32,767-এ overflow করে। Firmware-র codeint16_tআরint32_tব্যবহার করে ঠিক এটাকে চমক হতে না দেওয়ার জন্য।
যে ভুলগুলো সবাই করে
১. sizeof-র ফল %d দিয়ে ছাপা।
printf("%d\n", sizeof(int));
Playground-এ নীরব, কারণ format-র পরীক্ষাটার জন্য -Wall লাগে। ও সাধারণত 4 ছাপে আর তাতেও ভুলই থাকে। নিজের মেশিনে gcc -Wall বলে warning: format '%d' expects argument of type 'int', but argument 2 has type ..., আর ওই message-র শেষটায় Linux আর Windows-এ আলাদা type-র নাম আসে, যেটা এক লাইনেই পুরো lesson-টা। লেখো %zu।
২. Header ছাড়াই একটা সীমার দিকে হাত বাড়ানো।
#include <stdio.h>
int main(void)
{
printf("%d\n", INT_MAX);
return 0;
}
GCC 12 বলে error: 'INT_MAX' undeclared (first use in this function) আর তারপর note: 'INT_MAX' is defined in header '<limits.h>'। Note-টা তোমার উপকারই করছে। প্রতিটা note পড়ো, কেবল তার উপরের error না।
৩. Bracket ছাড়া sizeof-এ যোগ করা।
int marks = 90;
printf("%zu\n", sizeof marks + 1);
কোনো message নেই, আর ছাপে 5। তুমি expression-টার মাপ চেয়েছিলে আর পেয়েছ variable-টার মাপ, আরও একটা বেশি। যেটা বোঝাতে চাও সেটা হলে লেখো sizeof(marks + 1)।
৪. মাপটা জিজ্ঞেস না করে লিখে রাখা।
long total_bytes = 8; /* "a long is 8 bytes" */
কোনো message নেই, আর Playground-এ এটা ঠিক আর Windows-এ ভুল। Bug-টা comment-টাই: ও একটা আন্দাজকে তথ্য হিসেবে লিখে রাখছে। sizeof(long) লেখো, আর program-টার আর ওই comment-র দরকারই থাকে না।
এই module যে ছয়টা type ব্যবহার করেছে তাদের মাপ একটা নির্দিষ্ট চেহারায় ছাপো, মুখস্থ থেকে কিছু না লিখে।
Input. নেই।
Output. ছয় লাইন, এই ক্রমে: char, short, int, long long, float, double। প্রতিটা লাইনে type-র নাম, একটা কোলন, একটা space, আর byte-এ মাপ।
Constraints. প্রতিটা সংখ্যা sizeof থেকে আসবে। long লিখো না, আর কেন লিখলে না সেটা বলতে তৈরি থেকো।
Sample. প্রথম লাইনটা char: 1।
#include <stdio.h>
int main(void)
{
printf("char: %zu\n", sizeof(char));
/* Five more lines, same shape. */
return 0;
}
এই module-এ judge করা হয় না। এর একটা judge করা রূপ ওই মুহূর্তেই ভেঙে পড়ত যখন কেউ এমন মেশিনে চালাত যেখানে long 4 byte, আর ওটাই type-টা বাদ থাকার কারণ।
Amara হিসাব করছে reading-র একটা table-র জন্য কত memory লাগবে, চাওয়ার আগেই।
Input. এক লাইনে একটা পূর্ণসংখ্যা n, reading-র সংখ্যা।
Output. এক লাইনে কত byte লাগবে, যদি double ধরনের n-টা মান রাখতে হয়।
Constraints. 0 <= n <= 1000000000। double-র মাপ sizeof থেকে নাও, নিজের লেখা কোনো সংখ্যা থেকে না।
Sample. Input 10 দিলে 80।
#include <stdio.h>
int main(void)
{
long long n = 0;
scanf("%lld", &n);
/* Multiply by sizeof(double), print with %lld. */
return 0;
}
bytes-for-doubles নামে judge করা হয়। সবচেয়ে বড় n-র উত্তরটা দুইশো কোটি ছাড়িয়ে যায়, তাই গুণটা একটা চওড়া বাক্সে হতে হবে।
Zara একটা file format-র কলাম নকশা করছে আর তার দরকার সবচেয়ে সরু signed fixed-width type, যেটা কলামের প্রতিটা মান ধরে।
Input. এক লাইনে দুইটা পূর্ণসংখ্যা lo hi, কলামের সবচেয়ে ছোট আর সবচেয়ে বড় মান।
Output. এক লাইনে bit-এ প্রস্থ: 8, 16, 32 বা 64। এটা int8_t, int16_t, int32_t আর int64_t-র মধ্যে সবচেয়ে ছোটটাকে বোঝায়, যেটা পুরো সীমা ধরে।
Constraints. -9000000000000000000 <= lo <= hi <= 9000000000000000000।
Sample. Input 0 100 দিলে 8। Input -200 300 দিলে 16। Input 0 5000000000 দিলে 64।
#include <stdio.h>
int main(void)
{
long long lo = 0;
long long hi = 0;
scanf("%lld %lld", &lo, &hi);
/* Lesson 4: a comparison is a 1 or a 0, and two of them multiplied is "both". */
return 0;
}
type-fits নামে judge করা হয়। কোনো শর্ত লাগবে না, loop-ও না। আগে বের করো কয়টা সরু type ধরে, তারপর ওই গোনাটাকে একটা প্রস্থে বদলাও।
যে প্রশ্নগুলো সবার মনে আসে
sizeof-র কি চলার সময় কোনো খরচ আছে?নেই। সাধারণ একটা type-র জন্য compiler ওটাকে compile করার সময়ই একটা সংখ্যা দিয়ে বদলে দেয়, তাই চলন্ত program কাউকে কিছু জিজ্ঞেসই করে না।
%zuএত দৃষ্টিকটু কেন?zমানে "একটাsize_t-র দৈর্ঘ্য" আরuমানে unsigned। এটা C99-তে যোগ হয়েছিল ঠিক এই কারণেই যেsize_t-র নির্দিষ্ট চওড়া নেই, তাই আগের কোনো specifier সব জায়গায় ঠিক হতে পারত না।সব জায়গায়
int-র বদলেint32_tনেব?না। সাধারণ গোনার জন্য
intনাও, আর চওড়াটা যখন একটা file format, protocol বা hardware register-র অংশ তখন fixed-width type নাও।একটা নামে কাজের অক্ষর কয়টা,
sizeofকি সেটাই?না, আর ওই গোলমালটা আসে Module 10-এ। Character-র একটা array-তে
sizeofবাক্স গোনে;strlenশেষ চিহ্নের আগের অক্ষরগুলো গোনে।একটা byte কি কখনো 8 bit না হতে পারে?
পারে, কিছু digital signal processor-এ, যেখানে
CHAR_BIT16 বা 32। তুমি সম্ভবত কখনোই একটার দেখা পাবে না, আর 8-র বদলেCHAR_BITলিখতে কোনো খরচ নেই।
মূল কথা
sizeofএকটা operator, উত্তর দেয় compile-র সময়, byte-এ, আর ভেতরের expression কখনো চালায় না।- Type-র জন্য bracket লাগে, variable-র জন্য লাগে না, আর
sizeof x + 1দেখতে যা মনে হয় তা না। - এর type
size_t, ছাপে%zuদিয়ে, unsigned আর মেশিনের মাপে। <limits.h>আর<float.h>-এ প্রতিটা সীমার মান আছে, তোমার মেশিনের জন্য ঠিক করা।longPlayground-এ 8 byte আর Windows-এ 4; এই module-র বাকি প্রতিটা মাপ মেলে।- Protocol, file format আর hardware-র জন্য
<stdint.h>নির্দিষ্ট চওড়া দেয়।
পরেরটা Problems lesson: এই module-র সবকিছুর উপরে দশটা judge করা সমস্যা, লুকানো test-র বিরুদ্ধে।
lesson ৫ শেষ
শেষ হলে চিহ্ন দিন, অগ্রগতি আপনার সাথে থাকবে।
পরেরটা: Problem: variable আর type