এই পর্বে যা পাবেন
- কাগজে-কলমে কোড চালানোর (dry run) একটি ধাপে ধাপে পদ্ধতি, যা যেকোনো পরীক্ষার খাতায় কাজে লাগে
- যে ১৩টি জায়গায় পরীক্ষার্থীরা সবচেয়ে বেশি ভুল করেন, তার একটি চেকলিস্ট
- কোর্সের সব মডিউল থেকে ৫৪টি প্রশ্ন — সহজ থেকে কঠিন, প্রতিটির পূর্ণ ব্যাখ্যাসহ
- নির্দিষ্ট আউটপুট, compile error, undefined behavior আর unspecified — চার রকম উত্তর আলাদা করে চেনা
- AI দিয়ে নতুন অনুশীলনের প্রশ্ন বানানো, আর তার উত্তর নিজে যাচাই করার নিয়ম
১ · মডিউল ৮ শুরু — পরীক্ষা ও ইন্টারভিউর প্রস্তুতি
অভিনন্দন! ৫২টি পর্বে আপনি C-এর ভিত্তি থেকে শুরু করে পয়েন্টার, ডেটা স্ট্রাকচার, অ্যালগরিদম আর সিস্টেম প্রোগ্রামিং পর্যন্ত এসেছেন। এবার শেখা জিনিস ঝালিয়ে নেওয়ার পালা — এমনভাবে, যাতে পরীক্ষার হলে বা ইন্টারভিউর টেবিলে চাপের মধ্যেও ঠিক উত্তরটি মাথায় আসে।
এই শেষ মডিউলে নতুন কোনো বিষয় নেই। আছে চারটি অনুশীলনের পর্ব, প্রতিটি একটি আলাদা ধরনের প্রশ্নের জন্য:
পর্ব ৫৩ · আউটপুট কী হবে?
এই পর্ব। ছোট কোড দেখে বলতে হবে কী ছাপা হবে। কোড মাথায় চালানোর দক্ষতা যাচাই করে — লিখিত পরীক্ষা, MCQ আর ইন্টারভিউ, তিন জায়গাতেই আসে।
কৌশল: trace tableপর্ব ৫৪ · MCQ প্রশ্নব্যাংক
ধারণা, সংজ্ঞা আর তথ্যের দ্রুত প্রশ্ন — টাইপের আকার, keyword, লাইব্রেরি ফাংশন, জটিলতা। চাকরির প্রাথমিক বাছাই পরীক্ষায় এ ধরনের প্রশ্ন প্রায়ই আসে।
কৌশল: সময় ধরে চর্চাপর্ব ৫৫ · লিখিত ও ভাইভা
“পার্থক্য লেখো”, “উদাহরণসহ ব্যাখ্যা করো”, “প্রোগ্রাম লেখো” আর মুখোমুখি প্রশ্নের উত্তর কীভাবে সাজাবেন।
কৌশল: উত্তরের কাঠামোপর্ব ৫৬ · কোডিং ইন্টারভিউ
সমস্যা শুনে প্রশ্ন করা, পরিকল্পনা বলা, কোড লেখা, টেস্ট করা — সফটওয়্যার কোম্পানির ইন্টারভিউর পুরো ধারা।
কৌশল: জোরে ভাবা| আপনার লক্ষ্য | শুরু করুন | তারপর |
|---|---|---|
| বিশ্ববিদ্যালয়ের লিখিত পরীক্ষা | পর্ব ৫৩ (আউটপুট) ও ৫৫ (লিখিত) | পর্ব ৫৪ দিয়ে দ্রুত ঝালাই |
| সরকারি বা ব্যাংকের IT/প্রোগ্রামার পদের পরীক্ষা | পর্ব ৫৪ (MCQ) ও ৫৩ | পর্ব ৫৫-এর লিখিত ও ভাইভা অংশ |
| সফটওয়্যার কোম্পানির ইন্টারভিউ | পর্ব ৫৬ (কোডিং) | পর্ব ৫৩ — অনেক টেকনিক্যাল রাউন্ড “এই কোড কী করবে?” দিয়েই শুরু হয় |
| শুধু নিজের শেখা যাচাই | এই পর্ব, প্রথম থেকে শেষ | যে ভাগে বেশি ভুল, সেই পর্বে ফিরে যান |
এই পাতা কীভাবে ব্যবহার করবেন
- প্রথম দফা — অপশন ঢেকে রাখুন। প্রতিটি প্রশ্নের কোড পড়ে খাতায় নিজের উত্তর লিখুন। তারপর অপশনে চাপুন। প্রথম চেষ্টায় কতগুলো ঠিক হলো, পাতার শেষে স্কোর দেখাবে।
- দ্বিতীয় দফা — ভুলগুলোর কারণ খুঁজুন। প্রতিটি ভুল অপশনের ব্যাখ্যা পড়ুন। প্রতিটি ভাগের শুরুতে লেখা আছে কোন পর্বগুলো ঝালাতে হবে।
- তৃতীয় দফা — নিজে চালান। প্রতিটি ভাগের “নিজে চালিয়ে দেখুন” কোড চালান, একটি সংখ্যা বা অপারেটর বদলে আগে ফল অনুমান করুন, তারপর চালিয়ে মেলান।
সব উত্তর সাধারণ 64-বিট Linux-এ GCC-এর জন্য: int 4 বাইট, long ও pointer 8 বাইট, char signed, বাইট-ক্রম little-endian (x86-64). কোডের টুকরোতে #include <stdio.h> ও <string.h> লেখা থাকে না, ধরে নিন আছে; আর main না থাকলে লাইনগুলো main()-এর ভেতরে আছে ধরে নিন। যে উত্তর প্ল্যাটফর্মের ওপর নির্ভর করে, প্রশ্নেই তা বলা আছে।
পরীক্ষা ও ইন্টারভিউতে “আউটপুট কী হবে?” প্রশ্ন সাধারণত এই চেহারায় আসে:
- “নিচের প্রোগ্রামের আউটপুট লেখো” — খাতায় trace table এঁকে দেখান; শুধু উত্তর নয়, ধাপগুলোও নম্বর পায়।
- “আউটপুট লেখো এবং কারণ ব্যাখ্যা করো” — ফাঁদটির নাম বলুন: integer division, fall-through, short-circuit…
- MCQ-তে চারটি আউটপুট থেকে বেছে নেওয়া — আগে নিজে হিসাব করুন, তারপর অপশন দেখুন; অপশন দেখে উল্টো হিসাব করলে ফাঁদে পড়ার সম্ভাবনা বেশি।
- ইন্টারভিউতে “এই কোডে কী সমস্যা?” — আউটপুট বলার পাশাপাশি UB বা বাগ থাকলে সেটি আগে বলুন; এটাই অভিজ্ঞতার লক্ষণ।
২ · কোড মাথায় চালানোর পদ্ধতি — trace table
আউটপুট বলার একটাই নির্ভরযোগ্য উপায়: কম্পিউটার যেভাবে চালায়, ঠিক সেভাবে এক লাইন এক লাইন করে চালানো, আর প্রতিটি পরিবর্তন লিখে রাখা। একে বলে dry run. চোখ বুলিয়ে “মনে হচ্ছে 10 আসবে” বলা আর dry run করা — দুটোর পার্থক্য পরীক্ষার খাতায় স্পষ্ট দেখা যায়।
ক্রিকেট মাঠের স্কোরারের কথা ভাবুন। তিনি “মোটামুটি ১৫০ হয়েছে” লেখেন না। প্রতিটি বলের পরে স্কোরবুকে লেখেন — কত রান, কে আউট, কোন ব্যাটার স্ট্রাইকে। খেলা যত লম্বাই হোক, যেকোনো মুহূর্তের অবস্থা বই খুলে বলে দেওয়া যায়। Trace table হলো কোডের স্কোরবুক: প্রতিটি কলামে একটি ভ্যারিয়েবল, প্রতিটি সারিতে একটি ধাপ, আর শেষ কলামে যা ছাপা হলো।
Trace table-এর পাঁচটি নিয়ম
- প্রতিটি ভ্যারিয়েবলের জন্য একটি কলাম, আর একটি কলাম আউটপুটের জন্য।
staticভ্যারিয়েবল আর global-এর জন্যও কলাম রাখুন — এগুলোই সবচেয়ে বেশি ভোলা হয়। - মান বদলালে তবেই নতুন সারি। শর্ত যাচাইয়ের ফলও (সত্য/মিথ্যা) পাশে লিখুন।
x++দেখলে দুটি জিনিস লিখুন: এক্সপ্রেশন কোন মান পেল (পুরোনো), আর ভ্যারিয়েবল এখন কত (নতুন)।- ফাংশন ডাকলে নতুন একটি ছোট টেবিল — নিজস্ব প্যারামিটার আর local নিয়ে। ফেরার সময় শুধু ফেরত মানটি নিয়ে আসুন।
- আউটপুট কলামে হুবহু লিখুন — space, কমা, newline সহ। MCQ-তে প্রায়ই দুটি অপশনের পার্থক্য শুধু একটি space বা লাইনের ক্রম।
নিচের ডায়াগ্রামে একটি ক্লাসিক প্রশ্ন trace table দিয়ে চালানো হয়েছে। এতে একসাথে দুটি ফাঁদ: static local ভ্যারিয়েবল আর post-increment. “পরে” চাপুন, আর ডানের টেবিলে প্রতিটি মান কখন বদলায় খেয়াল করুন। প্রথমে নিজে উত্তর ভেবে নিন — শেষ লাইনে কী ছাপা হবে?
1int next(void) {2 static int n = 5;3 return n++;4}56int main(void) {7 int s = 0;8 for (int i = 0; i < 3; i++)9 s += next();10 printf("%d %d\n", s, next());11}
| s | |
| i | |
| n (static) | |
| next() ফেরত দিল |
- শুরু।
static int n = 5প্রোগ্রাম শুরুর আগেই একবার তৈরি ও initialize হয়ে যায় —next()ডাকার সময় নয়। তাই টেবিলে n = 5 আগে থেকেই আছে। লাইন ৭-এ s = 0. - লুপ শুরু। i = 0. শর্ত 0 < 3 — সত্য, ভেতরে ঢুকছে।
- প্রথম ডাক।
return n++: ফেরত যায় পুরোনো মান 5, তারপর n হয় 6. s = 0 + 5 = 5. লক্ষ করুন — “ফেরত দিল” আর “n এখন কত” আলাদা কলামে লেখায় গুলিয়ে যাওয়ার সুযোগ নেই। - i = 1. 1 < 3 — সত্য।
- দ্বিতীয় ডাক। n আগের ডাকের মান মনে রেখেছে (6) — static না হলে আবার 5 থেকে শুরু হতো। ফেরত 6, n হয় 7. s = 5 + 6 = 11.
- i = 2. 2 < 3 — সত্য।
- তৃতীয় ডাক। ফেরত 7, n হয় 8. s = 11 + 7 = 18.
- i = 3. 3 < 3 — মিথ্যা, লুপ শেষ। অনেকে এখানেই থেমে যান, কিন্তু n এখন 8 — সেটাই পরের ফাঁদ।
- printf-এর ভেতরে আরেকটি ডাক।
next()ফেরত দেয় 8, n হয় 9. ছাপা হয় 18 8. যাঁরা static ভুলে যান তাঁরা লেখেন “15 5”, আর যাঁরা n++ কে ++n ভাবেন তাঁরা লেখেন “21 9”.
১৩টি ফাঁদ — কোড পড়ার সময় কোথায় চোখ রাখবেন
প্রায় সব “আউটপুট কী হবে?” প্রশ্ন নিচের কোনো না কোনো ফাঁদের ওপর তৈরি। কোড দেখেই আগে খুঁজুন এর কোনটি আছে:
| ফাঁদ | ছোট উদাহরণ | যা মনে রাখবেন | পর্ব |
|---|---|---|---|
| Integer division | 7 / 2 | দুটি int ভাগে দশমিক কাটা যায়: 3. ঋণাত্মকে শূন্যের দিকে: -7 / 2 = -3 | ০৭ |
| Integer promotion | unsigned char a = 200; a + a | হিসাবের আগে ছোট টাইপ int হয়ে যায়: 400 | ০৮ |
| Pre/post increment | y = x++; | postfix পুরোনো মান দেয়, prefix নতুন | ০৭ |
| Short-circuit | a || b++ | বাঁ দিক দিয়েই ফল ঠিক হলে ডান দিক চলেই না | ০৭ |
| static local | static int c = 0; | একবারই initialize, ডাকের মাঝে মান থাকে | ১৪ |
| Pointer arithmetic | p + 2 | বাইট নয়, element গোনে | ১৮ |
| String-এর শেষ চিহ্ন | "ab\0cd" | strlen ও %s প্রথম \0-তে থামে; sizeof থামে না | ১৯ |
| switch fall-through | case 2: ... (break নেই) | break না পাওয়া পর্যন্ত নিচে গড়িয়ে চলে | ১১ |
| লুপের off-by-one | i <= n বনাম i < n | 0 থেকে n পর্যন্ত, দুই প্রান্তসহ (<=) হলে n + 1 বার | ১২ |
| Operator precedence | x >> 1 + 1, *p++ | + আগে, shift পরে; postfix ++ আগে, * পরে | ০৭ |
| Signed/unsigned তুলনা | -1 < sizeof(int) | -1 বিশাল unsigned হয়ে যায়: মিথ্যা | ০৮ |
| sizeof: অ্যারে বনাম pointer | ফাংশনের ভেতরে sizeof(arr) | প্যারামিটার অ্যারে আসলে pointer: 8 | ১৮ |
| বন্ধনী ছাড়া macro | #define SQ(x) x * x | লেখা বসে, হিসাব নয়: SQ(2+3) = 11 | ২৭ |
চার রকম উত্তর হতে পারে
পরীক্ষার্থীরা ধরে নেন প্রতিটি কোড একটি নির্দিষ্ট লেখা ছাপবে। বাস্তবে উত্তর চার রকম হতে পারে, আর ভালো প্রশ্নকর্তা ঠিক এটাই যাচাই করেন:
✅ নির্দিষ্ট আউটপুট
C স্ট্যান্ডার্ড ফল পুরোপুরি ঠিক করে দেয়। যেকোনো সঠিক compiler-এ একই লেখা আসবে। বেশিরভাগ প্রশ্ন এই ধরনের।
যেমন: 7 / 2 = 3⛔ Compile error
প্রোগ্রাম তৈরিই হয় না, তাই কিছুই ছাপা হয় না। const লঙ্ঘন, অঘোষিত নাম, টাইপের অমিল খুঁজুন। পরীক্ষায় এর নাম compilation error.
যেমন: const-এ লেখা⚠️ Undefined behavior
নিয়ম ভাঙা কোড — স্ট্যান্ডার্ড কোনো ফলের নিশ্চয়তা দেয় না। একই compiler-এ আজ এক ফল, optimization বদলালে আরেক। UB চেনাটাই সঠিক উত্তর।
যেমন: i = i++🔀 Unspecified / implementation-defined
Unspecified: কয়েকটি বৈধ ফলের যেকোনোটি হতে পারে (যেমন মূল্যায়নের ক্রম)। Implementation-defined: compiler নিজের নিয়ম লিখে রাখে (যেমন char signed কি না)।
যেমন: f() + g()-এর ক্রমক্রমএকটি অচেনা “আউটপুট কী হবে?” প্রশ্ন পেলে ধাপগুলো কোন ক্রমে করবেন? (▲▼ চেপে সাজান)
- অনুমানগুলো ঠিক করা — int কত বিট, char signed কি না, কোন header ধরা আছে
- Compile হবে কি না দেখা — const, টাইপ, ঘোষণা, বানান
- UB-এর লক্ষণ খোঁজা — একই ভ্যারিয়েবল দুবার বদল, সীমার বাইরে, signed overflow
- Trace table এঁকে লাইন ধরে চালানো
- printf-এর format মিলিয়ে হুবহু আউটপুট লেখা — space ও newline সহ
৩ · অপারেটর, এক্সপ্রেশন ও টাইপ
সবচেয়ে বেশি প্রশ্ন আসে এই ভাগ থেকে, কারণ এখানে কোড ছোট কিন্তু ফাঁদ গভীর। প্রতিটি এক্সপ্রেশনে তিনটি প্রশ্ন করুন: কোন অপারেটর আগে (precedence)? হিসাব কোন টাইপে হচ্ছে (usual arithmetic conversions)? কোনো অংশ কি আদৌ চলছে না (short-circuit)?
ঝালিয়ে নিন: পর্ব ০৫ · ডেটা কীভাবে থাকে, পর্ব ০৬ · ভ্যারিয়েবল ও টাইপ, পর্ব ০৭ · অপারেটর, পর্ব ০৮ · টাইপ কনভার্শন, পর্ব ০৯ · বিটওয়াইজ অপারেশন.
(সহজ) নিচের অংশটি কী ছাপবে?
int a = 7, b = 2;
printf("%d %d\n", a / b, a % b);
3 13.5 13 04 1
(মাঝারি) ঋণাত্মক সংখ্যার ভাগ — কী ছাপা হবে?
printf("%d %d\n", -7 / 2, -7 % 2);
-4 1-3 -1-3 1-4 -1
(সহজ) তুলনা পরপর লিখলে কী হয়?
int r = 10 > 5 > 2;
printf("%d\n", r);
10Compile errorUndefined behavior
(মাঝারি) Operator precedence — কী ছাপা হবে?
int x = 16;
printf("%d\n", x >> 1 + 1);
94817
(মাঝারি) Short-circuit — কোন ভ্যারিয়েবল আদৌ বদলাবে?
int a = 1, b = 1;
int r = a-- || b++;
printf("%d %d %d\n", r, a, b);
1 0 21 0 10 0 11 1 1
(কঠিন) C-তে 'a'-এর আকার কত? (ধরুন int 32-বিট)
printf("%zu %zu\n", sizeof('a'), sizeof(char));
1 14 14 42 1
(কঠিন) Signed বনাম unsigned তুলনা — কোন লাইন ছাপা হবে?
if (-1 < sizeof(int))
printf("yes\n");
else
printf("no\n");
yesnoCompile errorUndefined behavior
(মাঝারি) Integer promotion — দুটি মান কী আসবে?
unsigned char a = 200, b = 100;
int s = a + b;
unsigned char t = a + b;
printf("%d %d\n", s, t);
44 44300 44300 30044 300
নিজে চালিয়ে দেখুন — অপারেটর ও টাইপের ছোট ছোট পরীক্ষা
ওপরের কয়েকটি প্রশ্ন আর কিছু নতুন, একটি প্রোগ্রামে। চালানোর আগে প্রতিটি লাইনের ফল খাতায় লিখুন, তারপর মিলিয়ে দেখুন। এরপর একটি সংখ্যা বদলে (যেমন -7-এর জায়গায় -8) আবার অনুমান করুন।
#include <stdio.h>
int main(void) {
int a = 7, b = 2;
printf("1) %d %d %.2f\n", a / b, a % b, (double)a / b);
printf("2) %d %d\n", -7 / 2, -7 % 2);
unsigned char x = 200, y = 100;
unsigned char t = (unsigned char)(x + y);
printf("3) %d %d\n", x + y, t);
printf("4) %d %d %d\n", 6 & 3, 6 | 3, 6 ^ 3);
int p = 1, q = 1;
int r = p-- || q++;
printf("5) r=%d p=%d q=%d\n", r, p, q);
printf("6) %zu %zu\n", sizeof('a'), sizeof(char));
printf("7) %d\n", (int)(16 >> (1 + 1)));
return 0;
}১) (double)a / b-এ cast আগে হয়, তাই ভাগ হয় double-এ: 3.50. ২) শূন্যের দিকে ছাঁটা: -3, ভাগশেষের চিহ্ন ভাজ্যের মতো: -1. ৩) x + y int-এ 300, কিন্তু unsigned char-এ রাখলে 300 − 256 = 44. ৪) 110 & 011 = 010 (2), | = 111 (7), ^ = 101 (5). ৫) বাঁ দিক সত্য, তাই q++ চলেনি। ৬) C-তে 'a' একটি int. ৭) বন্ধনী দিয়ে precedence স্পষ্ট করায় GCC আর warning দেয় না — ফল 16 >> 2 = 4.
৪ · printf, সিদ্ধান্ত ও switch
এই ভাগের প্রশ্নে কোড পড়তে হয় চোখ দিয়ে নয়, compiler-এর নিয়ম দিয়ে। Indentation compiler দেখে না; else জোড়া বাঁধে সবচেয়ে কাছের if-এর সাথে (dangling else); switch মিলে যাওয়া case-এ লাফ দিয়ে break না পাওয়া পর্যন্ত চলতে থাকে। আর printf-এর format specifier-এর প্রতিটি অক্ষর আউটপুটের চেহারা বদলায়।
(সহজ) printf নিজে কী ফেরত দেয়?
int n = printf("Dhaka");
printf(" %d\n", n);
Dhaka 5Dhaka 1Dhaka 05 Dhaka
(সহজ) double-এ রাখলেই কি দশমিক আসে?
double d = 7 / 2;
printf("%.1f %.1f\n", d, 7 / 2.0);
3.5 3.53.0 3.53.0 3.03 3.5
(মাঝারি) = আর == গুলিয়ে ফেললে কী হয়?
int x = 0;
if (x = 5)
printf("A %d\n", x);
else
printf("B %d\n", x);
B 0A 5A 0Compile error
(মাঝারি) Indentation-এ বিভ্রান্ত হবেন না — কী ছাপা হবে?
int a = 5, b = 10;
if (a > 10)
if (b > 5)
printf("X");
else
printf("Y");
printf("Z\n");
YZZXZXYZ
(মাঝারি) switch-এ break না থাকলে?
int k = 2;
switch (k) {
case 1: printf("one ");
case 2: printf("two ");
case 3: printf("three ");
break;
default: printf("other ");
}
printf("\n");
twotwo threetwo three otherone two three
(কঠিন) default যদি সবার ওপরে থাকে?
int k = 5;
switch (k) {
default: printf("D");
case 1: printf("1");
case 2: printf("2"); break;
case 3: printf("3");
}
printf("\n");
DD12D12312D
(মাঝারি) printf-এর width ও flag — ঠিক কী দেখা যাবে? (␣ মানে একটি space)
printf("%5.2f|%-4d|%04d\n", 3.14159, 7, 42);
␣3.14|7␣␣␣|00423.14|7|423.14␣|␣␣␣7|0042␣3.14|␣␣␣7|0042
উপরের কয়েকটি প্রশ্নে GCC নিজেই warning দেয় — suggest parentheses around assignment, suggest explicit braces to avoid ambiguous 'else', this statement may fall through. পরীক্ষার খাতায় compiler থাকে না, কিন্তু বাস্তবে থাকে। এই warning-গুলো ঠিক সেই ফাঁদগুলোর নাম, যা আপনি এইমাত্র অনুশীলন করলেন। তাই নিজের কোডে -Wall -Wextra কখনো বন্ধ করবেন না।
৫ · লুপ
লুপের প্রশ্নে তিনটি জিনিস গুনে রাখুন: শরীর কতবার চলল, শেষবার শর্ত যাচাইয়ের সময় ভ্যারিয়েবল কত ছিল, আর লুপ শেষে ভ্যারিয়েবলের মান কত। তৃতীয়টিই সবচেয়ে বেশি ভুল হয় — লুপ থামে শর্ত মিথ্যা হওয়ার পরে, তাই কাউন্টার সাধারণত সীমা ছাড়িয়ে এক ঘর এগিয়ে থাকে। লুপের পরে একটি বিপথগামী ; আর unsigned কাউন্টার — দুটোই নিঃশব্দ ঘাতক।
ঝালিয়ে নিন: পর্ব ১২ · লুপ — একই কাজ বারবার; unsigned-এর আচরণের জন্য পর্ব ০৫.
(সহজ) লুপটি কতবার চলবে?
int count = 0;
for (int i = 0; i <= 10; i++)
count++;
printf("%d\n", count);
1011912
(মাঝারি) লুপের শেষে একটি ছোট্ট সেমিকোলন
int i, sum = 0;
for (i = 1; i <= 5; i++);
sum += i;
printf("%d %d\n", i, sum);
6 156 65 15Compile error
(মাঝারি) শর্ত শুরুতেই মিথ্যা হলে do-while কী করে?
int n = 10;
do {
printf("%d ", n);
n++;
} while (n < 5);
printf("| %d\n", n);
| 1010 | 1110 | 10- অসীম লুপ
(মাঝারি) continue আর break একসাথে
for (int i = 1; i <= 10; i++) {
if (i % 2 == 0) continue;
if (i > 7) break;
printf("%d ", i);
}
printf("\n");
1 3 5 71 3 5 7 92 4 61 3 5
(মাঝারি) while (n--) — শেষে n কত?
int n = 3;
while (n--)
printf("%d ", n);
printf("| %d\n", n);
3 2 1 | 02 1 0 | -12 1 0 | 03 2 1 0 | -1
(কঠিন) unsigned দিয়ে উল্টো গোনা (count দিয়ে লুপ আটকানো আছে)
unsigned int i;
int count = 0;
for (i = 3; i >= 0; i--) {
count++;
if (count > 10) break;
}
printf("%d\n", count);
4113Undefined behavior
(কঠিন) Comma operator দিয়ে দুটি ভ্যারিয়েবল
int i, j;
for (i = 0, j = 10; i < j; i += 3, j -= 2)
;
printf("%d %d\n", i, j);
6 69 43 86 8
নিজে চালিয়ে দেখুন — লুপের শেষ মান
এই প্রোগ্রাম প্রতিটি লুপের ভেতরের ধাপও ছাপে, যাতে আপনার trace table-এর সাথে সারি ধরে মেলাতে পারেন:
#include <stdio.h>
int main(void) {
int n = 3;
while (n--)
printf("%d ", n);
printf("| n = %d\n", n);
int k = 10;
do {
printf("do-while ran once, k = %d\n", k);
k++;
} while (k < 5);
int i, j;
for (i = 0, j = 10; i < j; i += 3, j -= 2) {
printf("(%d,%d) ", i, j);
}
printf("-> stop at i=%d j=%d\n", i, j);
int count = 0;
for (int c = 0; c <= 10; c++)
count++;
printf("0..10 with <= runs %d times\n", count);
return 0;
}while (n--) শেষ যাচাইয়েও n কমায়, তাই শেষে -1. do-while-এর শরীর শর্ত মিথ্যা হলেও একবার চলে। কমা দিয়ে লেখা লুপের প্রতিটি ধাপ (0,10) (3,8) ছাপা হয়, আর (6,6)-এ শর্ত মিথ্যা হয়ে থামে — সেই সারিটি আর ছাপা হয় না। শেষ লাইনে <= থাকায় 11 বার। একটি পরীক্ষা করুন: প্রথম লুপকে while (--n) করে আগে ফল অনুমান করুন (উত্তর: 2 1, শেষে n = 0).
৬ · ফাংশন, স্কোপ, static ও রিকার্শন
ফাংশনের প্রশ্নে মূল প্রশ্ন একটাই: এই ভ্যারিয়েবলটি কার, কতক্ষণ বাঁচে? প্যারামিটার হলো কপি (pass by value); ভেতরের block-এর একই নামের ভ্যারিয়েবল বাইরেরটিকে ঢেকে দেয় (shadowing); static local ডাকের মাঝে মান ধরে রাখে। রিকার্শনে প্রতিটি ডাকের জন্য আলাদা ছোট trace table আঁকুন — অথবা একটি গাছের মতো ডাকের ছবি (call tree), আর ফেরার পথে কী ঘটে তা আলাদা করে লিখুন।
ঝালিয়ে নিন: পর্ব ১৩ · ফাংশন, পর্ব ১৪ · স্কোপ ও স্টোরেজ ক্লাস, পর্ব ১৫ · রিকার্শন.
(সহজ) ফাংশনের ভেতরে প্যারামিটার বদলালে?
void change(int x) { x = 100; }
int main(void) {
int a = 5;
change(a);
printf("%d\n", a);
return 0;
}
10050Compile error
(মাঝারি) static local ভ্যারিয়েবল ডাকের মাঝে মান মনে রাখে?
int f(void) {
static int x = 1;
x = x * 2;
return x;
}
int main(void) {
int a = f();
int b = f();
printf("%d %d %d\n", a, b, f());
return 0;
}
2 2 22 4 82 4 61 2 4
(মাঝারি) একই নামের তিনটি x — কোনটি কোথায় দেখা যায়?
int x = 10;
int main(void) {
int x = 20;
{
int x = 30;
printf("%d ", x);
}
printf("%d\n", x);
return 0;
}
30 2030 3030 10Compile error
(মাঝারি) রিকার্শনে n − 2 করে নামলে?
int f(int n) {
if (n <= 1) return 1;
return n * f(n - 2);
}
int main(void) {
printf("%d\n", f(7));
return 0;
}
50401054815
(কঠিন) রিকার্সিভ ডাকের আগে ও পরে printf
void p(int n) {
if (n == 0) return;
printf("%d ", n);
p(n - 1);
printf("%d ", n);
}
int main(void) {
p(3);
printf("\n");
return 0;
}
3 2 13 2 1 1 2 31 2 3 3 2 13 2 1 0 1 2 3
(কঠিন) অঙ্ক নিয়ে রিকার্শন — f(9875) কত?
int g(int n) {
if (n < 10) return n;
return g(n / 10) + n % 10;
}
int main(void) {
printf("%d\n", g(9875));
return 0;
}
292578911
(কঠিন) Global ভ্যারিয়েবল আর ফাংশনের পার্শ্বপ্রতিক্রিয়া
int g = 1;
int bump(void) { g = g * 10; return g; }
int main(void) {
int a = bump();
int b = g + bump();
printf("%d %d %d\n", a, b, g);
return 0;
}
10 20 10010 110 100Undefined behavior- a = 10, g = 100 নিশ্চিত; b হতে পারে 110 বা 200 — ক্রম unspecified
নিজে চালিয়ে দেখুন — static আর রিকার্শন একসাথে
অংশ ২-এর ডায়াগ্রামের প্রোগ্রাম, সাথে দুটি রিকার্শনের প্রশ্ন। চালানোর আগে তিনটি লাইনের উত্তর লিখে রাখুন:
#include <stdio.h>
int next(void) {
static int n = 5;
return n++;
}
void p(int n) {
if (n == 0) return;
printf("%d ", n);
p(n - 1);
printf("%d ", n);
}
int f(int n) {
return n <= 1 ? 1 : n * f(n - 2);
}
int main(void) {
int s = 0;
for (int i = 0; i < 3; i++)
s += next();
printf("s = %d, next() = %d\n", s, next());
p(3);
printf("\n");
printf("f(7) = %d\n", f(7));
return 0;
}প্রথম লাইন ডায়াগ্রামের সাথে মিলবে: s = 18, next() = 8. লক্ষ করুন, এখানে printf-এর দুটি আর্গুমেন্টের একটিতেই next() আছে, আর s আগেই হিসাব হয়ে গেছে — তাই ক্রম নিয়ে কোনো অনিশ্চয়তা নেই। p(3) নামার পথে 3 2 1, ফেরার পথে 1 2 3 ছাপে। f(7) = 7 × 5 × 3 × 1 = 105. পরীক্ষা: static শব্দটি মুছে দিন — প্রথম লাইন হবে s = 15, next() = 5.
৭ · অ্যারে ও পয়েন্টার
পয়েন্টারের প্রশ্নে trace table-এ একটু বদল লাগে: pointer-এর কলামে ঠিকানার সংখ্যা না লিখে লিখুন “কোন ঘরকে দেখাচ্ছে” — যেমন “a[1]”. আর অ্যারের প্রতিটি ঘরের জন্য আলাদা কলাম রাখুন, কারণ pointer দিয়ে লিখলে অ্যারের মান বদলায়। মনে রাখুন: *p++ সরায় pointer, (*p)++ বাড়ায় মান; আর ফাংশনে পাঠানো অ্যারে পৌঁছায় pointer হয়ে (array decay).
ঝালিয়ে নিন: পর্ব ১৬ · অ্যারে, পর্ব ১৭ · পয়েন্টার ১, পর্ব ১৮ · পয়েন্টার ২, পর্ব ২১ · বহুমাত্রিক অ্যারে, পর্ব ৪৫ · উন্নত পয়েন্টার — const.
প্রশ্নে যাওয়ার আগে pointer-এর সবচেয়ে বিখ্যাত ফাঁদটি ধাপে ধাপে দেখে নিন — *p++ বনাম (*p)++:
1int a[] = {5, 10, 15};2int *p = a;3int x = *p++;4int y = (*p)++;5printf("%d %d %d %d\n", x, y, a[1], *p);
| a[0] | |
| a[1] | |
| a[2] | |
| p দেখাচ্ছে | |
| x | |
| y |
- অ্যারে তৈরি। তিনটি ঘর: 5, 10, 15. প্রতিটি ঘরের জন্য টেবিলে আলাদা সারি রাখা হয়েছে, কারণ pointer দিয়ে লিখলে এগুলো বদলাতে পারে।
p = a. অ্যারের নাম এখানে প্রথম ঘরের ঠিকানায় রূপ নেয়, তাই p দেখাচ্ছে a[0]-কে।*p++=*(p++). Postfix ++ এর precedence * এর চেয়ে বেশি, তাই ++ লাগে p-তে। p++ পুরোনো ঠিকানা (a[0]) দেয়, * সেখান থেকে পড়ে: x = 5. তারপর p সরে a[1]-এ। অ্যারের কোনো মান বদলায়নি।(*p)++. বন্ধনী আগে: *p মানে a[1]-এর ঘর। Postfix ++ পুরোনো মান দেয়: y = 10, আর a[1] হয় 11. এবার pointer নড়েনি, মান বদলেছে।- ছাপা। x = 5, y = 10, a[1] = 11, *p = a[1] = 11 → 5 10 11 11. মনে রাখার সূত্র: বন্ধনী না থাকলে ++ যায় pointer-এ, বন্ধনী থাকলে মানে।
(সহজ) অ্যারের কিছু ঘর initialize না করলে?
int a[5] = {1, 2};
printf("%d %d %d\n", a[0], a[2], a[4]);
1 0 01 2 01 2 2Undefined behavior
(মাঝারি) Pointer arithmetic — p কোথায় দাঁড়িয়ে?
int a[] = {10, 20, 30, 40};
int *p = a;
p += 2;
printf("%d %d\n", *p, *(p - 1) + 1);
30 2130 3112 1120 11
(কঠিন) *p++ আর (*p)++ — পার্থক্য কোথায়?
int a[] = {5, 10, 15};
int *p = a;
int x = *p++;
int y = (*p)++;
printf("%d %d %d %d\n", x, y, a[1], *p);
5 10 11 116 11 11 115 11 11 115 10 10 15
(কঠিন) ফাংশনে পাঠানো অ্যারের sizeof (64-বিট Linux, int 4 বাইট)
void f(int arr[]) {
printf("%zu ", sizeof(arr));
}
int main(void) {
int a[10];
printf("%zu ", sizeof(a));
f(a);
printf("%zu\n", sizeof(a) / sizeof(a[0]));
return 0;
}
40 40 1040 8 1010 10 1040 4 10
(মাঝারি) Pointer অদলবদল করলে কি মান অদলবদল হয়?
void sw(int *x, int *y) { int *t = x; x = y; y = t; }
int main(void) {
int a = 1, b = 2;
sw(&a, &b);
printf("%d %d\n", a, b);
return 0;
}
2 11 2Compile errorUndefined behavior
(কঠিন) 2D অ্যারে আর pointer notation
int m[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};
printf("%d %d %zu\n", m[1][2], *(*(m + 2) + 1),
sizeof(m[1]) / sizeof(int));
7 10 46 10 47 10 37 9 12
(মাঝারি) দুটি pointer-এর বিয়োগফল
int a[] = {2, 4, 6, 8, 10};
int *p = &a[1], *q = &a[4];
printf("%td %d\n", q - p, *q - *p);
12 63 63 34 6
(কঠিন) const int * দিয়ে লিখতে গেলে?
int x = 5, y = 7;
const int *p = &x;
p = &y;
*p = 9;
printf("%d\n", *p);
97Compile error5
নিজে চালিয়ে দেখুন — pointer-এর হিসাব
#include <stdio.h>
void show(int *arr) {
printf("inside show(): sizeof(arr) = %zu\n", sizeof(arr));
}
int main(void) {
int a[] = {5, 10, 15};
int *p = a;
int x = *p++;
int y = (*p)++;
printf("x=%d y=%d a[1]=%d *p=%d\n", x, y, a[1], *p);
int b[] = {2, 4, 6, 8, 10};
int *u = &b[1], *v = &b[4];
printf("v-u=%td *v-*u=%d 2[b]=%d\n", v - u, *v - *u, 2[b]);
printf("in main: sizeof(b) = %zu, elements = %zu\n",
sizeof(b), sizeof(b) / sizeof(b[0]));
show(b);
int m[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};
printf("m[1][2]=%d *(*(m+2)+1)=%d\n", m[1][2], *(*(m + 2) + 1));
return 0;
}প্রথম লাইন ডায়াগ্রামের ফল। v - u বাইটে নয়, element-এ: 3 (ছাপতে %td, কারণ টাইপ ptrdiff_t). 2[b] লেখাটি অদ্ভুত হলেও বৈধ: a[i] আসলে *(a + i), আর যোগ উল্টালেও একই — তাই 2[b] = b[2] = 6. main-এ sizeof(b) = 5 × 4 = 20, কিন্তু show()-এর ভেতরে শুধু pointer-এর আকার, 8. এজন্যই অ্যারের সাথে তার দৈর্ঘ্যও আলাদা প্যারামিটারে পাঠাতে হয়।
৮ · স্ট্রিং, struct ও union
স্ট্রিংয়ের প্রশ্নে প্রথমে দেখুন: এটি অ্যারে (char s[]) নাকি pointer (char *p)? অ্যারে হলে sizeof দেয় পুরো আকার, শেষের \0 সহ; pointer হলে দেয় শুধু 8. struct-এর প্রশ্নে মনে রাখুন, এটি পুরো কপি হয় আর compiler member-এর মাঝে ফাঁকা বাইট (padding) বসাতে পারে। union-এ সব member একই মেমরি ভাগ করে।
ঝালিয়ে নিন: পর্ব ১৯ · স্ট্রিং, পর্ব ২২ · struct ও union; বাইট-ক্রমের (endianness) জন্য পর্ব ০৫.
(সহজ) sizeof আর strlen — দুটি কি এক?
char s[] = "Dhaka";
printf("%zu %zu\n", sizeof(s), strlen(s));
5 56 55 68 5
(মাঝারি) String-এর মাঝখানে \0 থাকলে?
char s[] = "ab\0cd";
printf("%zu %zu %s\n", sizeof(s), strlen(s), s);
6 2 ab5 5 abcd6 5 ab3 2 ab
(মাঝারি) Pointer দিয়ে string (64-বিট সিস্টেম)
const char *p = "Bangladesh";
printf("%zu %zu %c\n", sizeof(p), strlen(p), *(p + 4));
11 10 l8 10 l8 10 g10 10 l
(মাঝারি) == দিয়ে দুটি string তুলনা
char a[] = "hi", b[] = "hi";
printf("%d %d\n", a == b, strcmp(a, b) == 0);
1 10 11 00 0
(মাঝারি) দুই ঘর করে লাফিয়ে string পড়া
char s[] = "C-LANG";
for (char *p = s; *p; p += 2)
putchar(*p);
putchar('\n');
CLN-AGCLNGC-L
(সহজ) struct কপি করলে কী হয়?
struct P { int x, y; };
int main(void) {
struct P a = {1, 2};
struct P b = a;
b.x = 10;
printf("%d %d\n", a.x, b.x);
return 0;
}
10 101 10Compile error1 1
(কঠিন) struct-এর আকার আর padding (সাধারণ x86-64 GCC)
struct S { char c; int i; char d; };
struct T { char c; char d; int i; };
printf("%zu %zu\n", sizeof(struct S), sizeof(struct T));
6 612 812 128 8
(কঠিন) union-এর ভেতরে উঁকি (x86-64, little-endian)
union U { int i; char c[4]; };
union U u;
u.i = 0x41424344;
printf("%zu %c\n", sizeof(u), u.c[0]);
8 A4 D4 AUndefined behavior
নিজে চালিয়ে দেখুন — স্ট্রিং ও struct-এর আকার
#include <stdio.h>
#include <string.h>
struct S { char c; int i; char d; };
struct T { char c; char d; int i; };
union U { int i; char c[4]; };
int main(void) {
char s[] = "Dhaka";
char z[] = "ab\0cd";
const char *p = "Bangladesh";
printf("1) %zu %zu\n", sizeof(s), strlen(s));
printf("2) %zu %zu %s\n", sizeof(z), strlen(z), z);
printf("3) %zu %zu %c\n", sizeof(p), strlen(p), p[4]);
char a[] = "hi", b[] = "hi";
printf("4) same text? %d\n", strcmp(a, b) == 0);
printf("5) %zu %zu\n", sizeof(struct S), sizeof(struct T));
union U w;
w.i = 0x41424344;
printf("6) %zu %c\n", sizeof(w), w.c[0]);
return 0;
}১) অ্যারেতে 6 বাইট, strlen 5. ২) মাঝের \0-এর পরের অংশ strlen ও %s দেখে না, কিন্তু sizeof দেখে। ৩) pointer-এর sizeof 8, string যত লম্বাই হোক। ৪) বিষয়বস্তু মেলাতে strcmp, ঠিকানা নয়। ৫) একই member, ভিন্ন ক্রম — 12 আর 8 বাইট। ৬) little-endian-এ প্রথম বাইট 0x44, অর্থাৎ 'D'. struct-এর আকার আর বাইট-ক্রম প্ল্যাটফর্মের ওপর নির্ভরশীল, তাই পরীক্ষায় এমন প্রশ্নে অনুমানটি (x86-64) লিখে দিন।
৯ · প্রিপ্রসেসর, ফাংশন পয়েন্টার ও Undefined Behavior
এই ভাগে দুই ধরনের প্রশ্ন। প্রথমটি macro নিয়ে: preprocessor হিসাব করে না, লেখা কেটে বসায় (macro expansion). তাই আগে খাতায় macro খুলে পুরো এক্সপ্রেশনটি লিখুন, তারপর হিসাব করুন। দ্বিতীয়টি এমন কোড, যার কোনো নির্দিষ্ট আউটপুট নেই। এখানে সংখ্যা বের করার চেষ্টা নয়, নিয়মটি চেনাই উত্তর।
ঝালিয়ে নিন: পর্ব ২৭ · প্রিপ্রসেসর ও ম্যাক্রো, পর্ব ২৮ · ফাংশন পয়েন্টার, পর্ব ৪৬ · Undefined Behavior ও নিরাপদ কোডিং, পর্ব ৪৭ · আধুনিক C.
প্রিপ্রসেসর ও ফাংশন পয়েন্টার
(মাঝারি) বন্ধনী ছাড়া macro
#define SQ(x) x * x
int main(void) {
printf("%d\n", SQ(2 + 3));
return 0;
}
251113Compile error
(কঠিন) Macro-তে x++ পাঠালে
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int main(void) {
int x = 5, y = 3;
int m = MAX(x++, y);
printf("%d %d\n", m, x);
return 0;
}
5 66 75 7Undefined behavior
(কঠিন) # (stringize) কি macro খুলে দেয়?
#define STR(x) #x
#define N 10
int main(void) {
printf("%s %d\n", STR(N), N);
return 0;
}
10 10N 10N NCompile error
(মাঝারি) Function pointer-এর অ্যারে
int add(int a, int b) { return a + b; }
int mul(int a, int b) { return a * b; }
int main(void) {
int (*op[2])(int, int) = {add, mul};
printf("%d %d\n", op[0](3, 4), op[1](3, 4));
return 0;
}
7 1212 7Compile error7 7
যখন সঠিক উত্তর “বলা যায় না”
নিচের প্রশ্নগুলোতে মন দিয়ে নিয়ম মিলিয়ে নিন। মনে রাখবেন: UB-এর কোডে একটি compiler কী ছাপল, সেটা উত্তর নয় — অন্য compiler, অন্য optimization বা কোডের পাশের একটি লাইন বদলালেই ফল বদলে যেতে পারে। একই কারণে বিভিন্ন বই বা ওয়েবসাইটে এ ধরনের প্রশ্নের “উত্তর” আলাদা আলাদা লেখা থাকে।
(মাঝারি) বিখ্যাত i = i++ প্রশ্ন
int i = 5;
i = i++ + 1;
printf("%d\n", i);
67Compile errorUndefined behavior
(কঠিন) int-এর সর্বোচ্চ মানে 1 যোগ (int 32-বিট)
int x = 2147483647; /* INT_MAX */
x = x + 1;
printf("%d\n", x);
-214748364821474836480Undefined behavior
(মাঝারি) unsigned-এ 0 থেকে 1 বিয়োগ (unsigned int 32-বিট)
unsigned int u = 0;
u = u - 1;
printf("%u\n", u);
-142949672950Undefined behavior
(মাঝারি) অ্যারের সীমার বাইরে এক ঘর
int a[3] = {1, 2, 3};
printf("%d\n", a[3]);
03Compile errorUndefined behavior
(মাঝারি) String literal বদলানোর চেষ্টা
char *s = "hello";
s[0] = 'H';
printf("%s\n", s);
HellohelloCompile errorUndefined behavior
নিজে চালিয়ে দেখুন — macro খুলে দেখা
#include <stdio.h>
#define SQ_BAD(x) x * x
#define SQ(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define STR(x) #x
#define XSTR(x) STR(x)
#define N 10
int main(void) {
printf("1) %d %d\n", SQ_BAD(2 + 3), SQ(2 + 3));
int x = 5, y = 3;
int m = MAX(x++, y);
printf("2) m=%d x=%d\n", m, x);
printf("3) %s %s\n", STR(N), XSTR(N));
unsigned int u = 0;
u = u - 1;
printf("4) %u\n", u);
return 0;
}১) বন্ধনীহীন macro 11, বন্ধনীসহ 25. ২) MAX-এ x++ দুবার বসে, তাই x শেষে 7 (এই কোডে UB নেই, কারণ ?:-এর শর্তের পরে sequence point আছে)। ৩) STR(N) লেখা হুবহু string করে "N"; এক ধাপ ঘুরিয়ে XSTR(N) আগে N খোলে, তাই "10". ৪) unsigned-এর wraparound নিয়মে নির্দিষ্ট। macro খোলা রূপ নিজের চোখে দেখতে চাইলে নিজের কম্পিউটারে gcc -E macros.c চালান — preprocessor-এর পরের কোড দেখাবে।
সব প্রশ্নের উত্তর দেওয়া শেষ হলে এখানে আপনার প্রথম চেষ্টার স্কোর দেখাবে:
১০ · AI-এর যুগে — কোড পড়তে পারাই যাচাইয়ের চাবি
আজ অনেকেই কোড লেখান AI দিয়ে। কিন্তু AI-এর দেওয়া কোড ঠিক আছে কি না, সেটা বুঝতে হলে আপনাকে ঠিক এই পর্বের কাজটিই করতে হবে — কোড পড়ে মাথায় চালানো। যিনি *p++ আর (*p)++-এর পার্থক্য চোখে ধরতে পারেন, তিনি AI-এর কোডের ভুলও ধরতে পারেন।
আরেকটি গুরুত্বপূর্ণ সত্য: AI নিজেও এ ধরনের প্রশ্নে প্রায়ই ভুল করে। ভাষা-মডেল কোড “চালায়” না; সে অনুরূপ লেখা দেখে সম্ভাব্য উত্তর অনুমান করে। লম্বা লুপ, integer promotion, platform-নির্ভর আকার আর UB — এসব জায়গায় সে আত্মবিশ্বাসী সুরে একটি নির্দিষ্ট সংখ্যা বলে দিতে পারে।
আর UB-এর কোডে দুটোর কোনোটিই প্রমাণ নয় — প্রমাণ হলো নিয়ম।
AI সাধারণত যেখানে আউটপুট ভুল বলে
| যে ধরনের কোড | AI-এর সাধারণ ভুল | কীভাবে ধরবেন |
|---|---|---|
i = i++, printf("%d %d", i, i++) | একটি নির্দিষ্ট সংখ্যা বলে, “UB” উল্লেখই করে না | -Wall দিয়ে compile — may be undefined warning আসে |
unsigned char, short-এর হিসাব | integer promotion ভুলে overflow ধরে নেয় | নিজে চালিয়ে দেখুন; টাইপ বদলে আবার চালান |
sizeof(long), struct-এর আকার | platform না জেনে একটি সংখ্যা বলে | Linux-এ long 8 বাইট, 64-বিট Windows-এ 4 — প্রশ্নেই platform বলে দিন |
| লম্বা লুপ বা রিকার্শন | মাঝের কোনো ধাপ বাদ দিয়ে হিসাব করে | AI-কে trace table দেখাতে বলুন, প্রতিটি সারি নিজে মেলান |
| Macro | macro-কে ফাংশনের মতো হিসাব করে | gcc -E দিয়ে খোলা রূপ দেখুন |
AI দিয়ে নতুন অনুশীলনের প্রশ্ন বানাতে পারেন — শর্ত একটাই, উত্তর যাচাইয়ের দায়িত্ব আপনার। নিচের prompt-টি AI-কে এমনভাবে বাঁধে, যাতে সে আগে নিজের যুক্তি দেখায় আর UB লুকাতে না পারে:
AI-এর দেওয়া প্রশ্ন বা কোড হাতে পেলে — যাচাইয়ের চেকলিস্ট
যে ফাঁদে সবাই পড়ে
পরীক্ষা, বই আর অনলাইন আলোচনায় বারবার ফিরে আসা ছয়টি ফাঁদ। প্রতিটি নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।
প্র ০১printf("%d", sizeof(int)); — উত্তর তো 4, তাহলে সমস্যা কোথায়?
বেশিরভাগ মেশিনে এটি সত্যিই 4 ছাপে, আর সেজন্যই ফাঁদটি এত টেকসই। সমস্যা হলো টাইপে। sizeof-এর ফলের টাইপ size_t, যা 64-বিট Linux-এ unsigned long (8 বাইট)। আর %d আশা করে একটি int (4 বাইট)। printf একটি variadic ফাংশন, তাই compiler আর্গুমেন্টকে format অনুযায়ী বদলে দেয় না — যা পাঠানো হয়েছে, printf ধরে নেয় সেটাই %d-এর টাইপ।
- স্ট্যান্ডার্ড কী বলে: format specifier আর আর্গুমেন্টের টাইপ না মিললে আচরণ undefined. x86-64-এ ঘটনাচক্রে ঠিক মান আসে, কারণ printf 8 বাইটের মানটির শুধু নিচের ৪ বাইট পড়ে, আর ছোট মানের পুরোটা সেখানেই থাকে। অন্য আর্কিটেকচারে বা বেশি আর্গুমেন্ট থাকলে পরের মানগুলো গুলিয়ে যেতে পারে।
- GCC ধরিয়ে দেয়:
-Wall-এformat '%d' expects argument of type 'int', but argument 2 has type 'long unsigned int'. - সঠিক লেখা: C99 থেকে
%zu. খুব পুরোনো কিছু compiler-এ (যেমন পুরোনো Windows টুলচেইন) %zu ছিল না; সেখানেprintf("%lu", (unsigned long)sizeof(int))নিরাপদ।
পরীক্ষায় যদি এই লাইনটি দেওয়া থাকে আর প্রশ্ন হয় “আউটপুট কী?”, তাহলে “সাধারণত 4 ছাপে” লিখে পাশে এক লাইনে বলে দিন যে format-টি ভুল, সঠিক হলো %zu — এটাই পূর্ণ উত্তর।
মূল উপলব্ধি: “চলছে” আর “ঠিক” এক কথা নয়। যে কোড ঘটনাচক্রে ঠিক ফল দেয়, সেটি অন্য মেশিনে, অন্য compiler-এ বা অন্য দিনে ভুল ফল দিতে পারে — compiler-এর warning সেই ভবিষ্যতের বাগের আগাম খবর।
প্র ০২i = i++; বা printf("%d %d", i++, i++); — বইয়ে তো উত্তর লেখা আছে, তাহলে UB কেন?
পুরোনো অনেক বই আর প্রশ্নব্যাংকে এ ধরনের প্রশ্নের একটি নির্দিষ্ট সংখ্যা “উত্তর” হিসেবে লেখা থাকে — সাধারণত লেখক যে compiler-এ (প্রায়ই পুরোনো Turbo C) চালিয়েছিলেন, তার ফল। কিন্তু C স্ট্যান্ডার্ড একদম শুরু (C89) থেকেই স্পষ্ট বলে: দুটি sequence point-এর মাঝে একই ভ্যারিয়েবল দুবার বদলানো, অথবা বদলানোর পাশাপাশি অন্য কাজে তার মান পড়া — undefined behavior.
i = i++:i++i-কে বদলায়,=-ও i-কে বদলায় — কোনটি আগে, নিয়মে বলা নেই। C11 এই ভাষা বদলে “sequenced before” সম্পর্ক দিয়ে লিখেছে, কিন্তু সিদ্ধান্ত একই: unsequenced দুটি পরিবর্তন মানে UB.printf("%d %d", i++, i++): ফাংশনের আর্গুমেন্টগুলো কোন ক্রমে মূল্যায়ন হবে তা unspecified, আর দুটি আর্গুমেন্ট একই i-কে বদলায় কোনো ক্রম ছাড়া — তাই পুরোটাই UB. ভিন্ন compiler-এ “5 6”, “6 5” বা অন্য কিছু আসতে পারে।f() + g()(দুটি ফাংশন আলাদা কিছু ছাপে): এটি UB নয়, শুধু unspecified — ফল দুটি বৈধ ক্রমের যেকোনোটি। পার্থক্যটি সূক্ষ্ম কিন্তু গুরুত্বপূর্ণ; এই পাতার ৬ নম্বর ভাগে এমন একটি প্রশ্ন আছে।
তাহলে পরীক্ষায় কী লিখবেন? MCQ-তে “Undefined behavior” বা “Compiler dependent” অপশন থাকলে সেটিই নিন। লিখিত প্রশ্নে লিখুন: “আচরণ অনির্ধারিত (undefined), কারণ sequence point ছাড়া i দুবার পরিবর্তিত হয়েছে; GCC সাধারণত X দেখায়, কিন্তু তা নিশ্চিত নয়।” কারণসহ এই উত্তর একটি সংখ্যার চেয়ে অনেক বেশি জ্ঞানের পরিচয় দেয়।
মূল উপলব্ধি: C-তে এক্সপ্রেশন লেখা হয় বাঁ থেকে ডানে, কিন্তু চলে compiler-এর সুবিধামতো ক্রমে। এক লাইনে একটি ভ্যারিয়েবল একবারই বদলান — তাহলে এই প্রশ্ন আপনার কোডে কখনো উঠবেই না।
প্র ০৩char c = 255; printf("%d", c); — 255, নাকি -1?
উত্তর: নির্ভর করে — আর ঠিক কীসের ওপর নির্ভর করে, সেটা বলাই পূর্ণ উত্তর। সাধারণ char signed নাকি unsigned, C স্ট্যান্ডার্ড তা compiler-এর ওপর ছেড়ে দিয়েছে (implementation-defined).
- x86 ও x86-64-এ GCC: char signed (-128 থেকে 127). 255 এই সীমায় ধরে না; signed টাইপে সীমার বাইরের মান রাখার ফলও implementation-defined, আর GCC নিয়ম লিখে রেখেছে মডুলো 2⁸ — তাই 255 − 256 = -1 ছাপে।
- ARM-এ Linux (যেমন Raspberry Pi, Android-এর অনেক ডিভাইস): সাধারণ char ডিফল্টভাবে unsigned, তাই 255 ছাপে।
- GCC-তে
-funsigned-charবা-fsigned-charদিয়ে এই আচরণ বদলানোও যায়।
এই পার্থক্য থেকে একটি বিখ্যাত বাগ জন্মায়: char c; while ((c = getchar()) != EOF). getchar() ফেরত দেয় int, আর EOF সাধারণত -1. unsigned char-এর প্ল্যাটফর্মে c কখনো -1 হয় না, তাই লুপ কখনো থামে না। signed char-এর প্ল্যাটফর্মে উল্টো সমস্যা: ফাইলে 0xFF বাইট থাকলে সেটিকেই EOF ভেবে লুপ আগেভাগে থেমে যায়। সমাধান একটাই: int c;.
পরীক্ষায় এমন প্রশ্নে লিখুন: “char signed হলে (যেমন x86-এ GCC) -1, unsigned হলে 255; এটি implementation-defined.” সংখ্যা নিয়ে কাজ করতে চাইলে নিজের কোডে স্পষ্ট টাইপ লিখুন — signed char, unsigned char বা <stdint.h>-এর uint8_t.
মূল উপলব্ধি: “কোন মেশিনে?” — এই প্রশ্ন করতে শেখা একজন সিস্টেম প্রোগ্রামারের লক্ষণ। যে উত্তর প্ল্যাটফর্মের ওপর নির্ভর করে, সেখানে অনুমানটি লিখে দেওয়াই পেশাদারি।
প্র ০৪if (0.1 + 0.2 == 0.3) — কেন মিথ্যা হয়?
কারণ কম্পিউটার দশমিক সংখ্যা রাখে বাইনারিতে, আর 0.1-কে বাইনারিতে পুরোপুরি লেখা যায় না — ঠিক যেমন 1/3-কে দশমিকে লেখা যায় না (0.3333…)। IEEE 754 double-এ 0.1 আসলে রাখা হয় তার সবচেয়ে কাছের বাইনারি মান হিসেবে, যা সামান্য বেশি। 0.2-ও তাই। যোগ করলে ক্ষুদ্র ভুলগুলো জমে দাঁড়ায় 0.30000000000000004, আর 0.3-এর নিজের সবচেয়ে কাছের মানটি 0.29999999999999998 — দুটি ভিন্ন বিট-প্যাটার্ন। তাই == মিথ্যা, শর্তের মান 0.
- নিজে দেখুন:
printf("%.17f\n", 0.1 + 0.2);ছাপে0.30000000000000004. - float-এর ফাঁদ:
float f = 0.1f; if (f == 0.1)— এটিও মিথ্যা, কারণ f ধরে float-এর কাছের মান আর 0.1 একটি double; দুটির নির্ভুলতা আলাদা। - সঠিক তুলনা: একটি ছোট tolerance দিয়ে —
fabs(a - b) < 1e-9. টাকার হিসাবে আরও ভালো: পয়সায় পূর্ণসংখ্যা রাখুন (১২৫.৫০ টাকা = 12550 পয়সা)।
এই ক্ষুদ্র ভুল বাস্তবে প্রাণঘাতীও হয়েছে। ১৯৯১ সালের উপসাগরীয় যুদ্ধে সৌদি আরবের ধাহরানে একটি Patriot ক্ষেপণাস্ত্র-প্রতিরক্ষা ব্যবস্থা একটি আগত Scud ক্ষেপণাস্ত্র ঠেকাতে ব্যর্থ হয়, আর ২৮ জন মার্কিন সৈন্য নিহত হন। পরে মার্কিন সরকারি তদন্তে দেখা যায়, সিস্টেমটি সময় গুনত 0.1 সেকেন্ডের এককে, আর 0.1-কে তার ২৪-বিট রেজিস্টারে পুরোপুরি রাখা যেত না। টানা প্রায় ১০০ ঘণ্টা চলার পর জমে থাকা ভুল দাঁড়িয়েছিল প্রায় এক সেকেন্ডের তিন ভাগের এক ভাগ — দ্রুতগামী ক্ষেপণাস্ত্রের ক্ষেত্রে যা কয়েকশো মিটারের পার্থক্য।
মূল উপলব্ধি: Floating point সংখ্যা আসল সংখ্যার একটি কাছাকাছি প্রতিনিধি, হুবহু নয়। তাদের == দিয়ে তুলনা করবেন না, আর অনেকবার যোগ করলে ভুল জমবে — এটা ধরে নিয়েই নকশা করুন।
প্র ০৫while (i < n); — একটি সেমিকোলনে প্রোগ্রাম আটকে গেল কেন?
C-তে একা একটি ; নিজেই একটি পূর্ণ statement — “কিছু করো না” (null statement). তাই while (i < n); মানে: “যতক্ষণ i < n, ততক্ষণ কিছুই করো না।” পরের লাইনের { ... i++; } লুপের অংশই নয়। i কখনো বাড়ে না, শর্ত কখনো মিথ্যা হয় না — প্রোগ্রাম চিরকাল ঘুরতে থাকে (infinite loop), কোনো আউটপুট নেই, কোনো error নেই।
for (...);: লুপ চলে ঠিকই, কিন্তু খালি শরীরে। এই পাতার ৫ নম্বর ভাগের প্রশ্নে sum পেল শুধু একবারের যোগ।if (x > 0);: শর্ত যা-ই হোক, পরের block সবসময় চলে। GCC এখানে-Wextra-এsuggest braces around empty bodywarning দেয়।- Compiler সব জায়গায় সতর্ক করে না। GCC
if-এর খালি শরীর ধরে, কিন্তুwhile-এর খালি শরীরে সব সময় warning দেয় না; অনেক ক্ষেত্রে শুধু indentation দেখে-Wmisleading-indentationজানায়।
পরীক্ষায় “আউটপুট কী?” প্রশ্নে এমন কোড থাকলে উত্তর হতে পারে “কিছুই ছাপবে না, প্রোগ্রাম অসীম লুপে আটকে থাকবে।” এটা একটি সম্পূর্ণ বৈধ উত্তর, আর অনেকে চোখ এড়িয়ে লুপের হিসাব করে একটি সংখ্যা লিখে দেন।
অভ্যাস হিসেবে সবসময় { } ব্যবহার করুন, এক লাইনের শরীরেও। আর ইচ্ছাকৃত খালি লুপ লিখলে ;-টি আলাদা লাইনে রাখুন বা { } লিখুন, যাতে পাঠক বোঝেন এটা ইচ্ছাকৃত।
মূল উপলব্ধি: সবচেয়ে বিপজ্জনক বাগ সেটাই, যা বৈধ কোড। Compiler ব্যাকরণ যাচাই করে, উদ্দেশ্য নয় — উদ্দেশ্য যাচাইয়ের দায়িত্ব আপনার চোখের, আর একটি ভালো formatter-এর।
প্র ০৬int x; printf("%d", x); — উত্তর কি “garbage value”?
পরীক্ষার খাতায় “garbage value” লেখা খুব প্রচলিত, আর ধারণাটা মোটামুটি ঠিক: local (automatic) ভ্যারিয়েবল initialize না করলে তার মান নির্দিষ্ট নয়, সেই মেমরিতে আগে যা ছিল তাই থাকতে পারে (garbage value). কিন্তু পুরো সত্যটা আরও গভীর।
- কোন ভ্যারিয়েবল 0 পায়: global আর
staticভ্যারিয়েবল initialize না করলেও নিশ্চিতভাবে 0 হয়। আংশিক initializer দেওয়া অ্যারের বাকি ঘরও 0 (৭ নম্বর ভাগের প্রথম প্রশ্ন)। - কোনটি পায় না: ফাংশনের ভেতরের সাধারণ local ভ্যারিয়েবল। তার মান “অনির্ধারিত” (indeterminate), আর যে local-এর ঠিকানা কখনো নেওয়া হয়নি, তার এমন মান পড়া C11 অনুযায়ী undefined behavior.
- কেন এটা শুধু “এলোমেলো সংখ্যা” নয়: optimizer ধরে নিতে পারে এমন পড়া ঘটে না, ফলে একই ভ্যারিয়েবল দুবার ছাপলে দুটি ভিন্ন মান, বা
if (x == 0)আরif (x != 0)দুটোই মিথ্যা — এমন অদ্ভুত আচরণও দেখা গেছে।
GCC এটি ধরতে পারে: -Wall-এ 'x' is used uninitialized (অনেক ক্ষেত্রে optimization চালু থাকলে আরও ভালো ধরে)। Valgrind আর MemorySanitizer-এর মতো টুল চলার সময় এটি হাতেনাতে ধরে।
তাই পরীক্ষায় লিখুন: “x initialize করা হয়নি; local ভ্যারিয়েবল বলে মান অনির্ধারিত (garbage) — নির্দিষ্ট আউটপুট বলা যায় না, আচরণ undefined.” আর global বা static হলে উত্তর নিশ্চিতভাবে 0.
মূল উপলব্ধি: ঘোষণার সাথেই মান দিন — int x = 0;. এক সেকেন্ডের এই অভ্যাস এমন বাগ থেকে বাঁচায়, যা শুধু কখনো কখনো, শুধু কোনো কোনো মেশিনে দেখা দেয়।
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বে ব্যবহৃত সব শব্দ এখানে একসাথে:
মনে রাখুন
- আউটপুট অনুমান নয় — trace table এঁকে লাইন ধরে চালান; প্রতিটি পরিবর্তন আর হুবহু আউটপুট লিখুন।
- প্রথমে খুঁজুন ১৩টি ফাঁদের কোনটি আছে: integer division, promotion, ++, short-circuit, static, pointer arithmetic, \0, fall-through, off-by-one, precedence, signed/unsigned, sizeof, macro.
- উত্তর চার রকম: নির্দিষ্ট আউটপুট, compile error, undefined behavior, unspecified/implementation-defined — শেষ তিনটি চেনাও সঠিক উত্তর।
- UB-এর কোডে compiler যা ছাপল তা উত্তর নয়; নিয়মটি আর কারণটি লিখুন। Platform-নির্ভর উত্তরে অনুমানটি লিখে দিন।
- AI-এর আউটপুট-অনুমানও যাচাই করুন: নিজে trace করুন,
-Wall -Wextraদিয়ে চালান, sanitizer দিয়ে UB খুঁজুন।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৫৪ · MCQ প্রশ্নব্যাংক — যেকোনো পরীক্ষার প্রস্তুতিপরবর্তী পর্বপুরো কোর্সের ধারণা যাচাইয়ের বিষয়ভিত্তিক MCQ.
- পর্ব ৫৫ · লিখিত ও মৌখিক (ভাইভা) প্রশ্নলিখিত পরীক্ষা“পার্থক্য লেখো”, “প্রোগ্রাম লেখো” আর ভাইভার উত্তর সাজানোর কৌশল।
- পর্ব ৫৬ · কোডিং ইন্টারভিউ — সমস্যা থেকে সমাধানইন্টারভিউসমস্যা বোঝা, পরিকল্পনা, কোড আর টেস্ট — ইন্টারভিউর পুরো ধারা।
- পর্ব ৪৬ · Undefined Behavior ও নিরাপদ কোডিংগভীরে যানকেন UB-এর কোডের কোনো নির্দিষ্ট আউটপুট নেই, আর optimizer তা কীভাবে কাজে লাগায়।
- C প্রোগ্রামিং · কোর্স হোম৫৬টি পর্বপুরো কোর্সের রোডম্যাপ — যে ভাগে ভুল বেশি হলো, সেই পর্বে ফিরে যান।