এই পর্বে যা শিখবেন
- একটি
.cppফাইল কোন কোন ধাপ পেরিয়ে চালানো যায় এমন প্রোগ্রাম হয়, আর C++-এ build কেন C-র চেয়ে ভারী - g++, clang++ আর MSVC — তিন compiler চেনা, আর নিজের OS-এ একটি বসানোর উপায়
-std=,-Wall,-Wextra,-g,-O2— প্রতিটি flag-এর কাজ- একাধিক ফাইলের প্রজেক্ট আলাদা compile করে link করা, আর compile, link ও runtime error আলাদা করে চেনা
- AI-এর দেওয়া build কমান্ড যাচাইয়ের একটি চেকলিস্ট
১ · C++ কোড থেকে প্রোগ্রাম — পাইপলাইনটা কেমন
একটি পরিচিত দৃশ্য দিয়ে শুরু করি। তানিয়া একটি অনলাইন কোর্স থেকে C++ শিখছেন। AI-কে দিয়ে একটি ছোট প্রোগ্রাম লিখিয়ে নিজের ল্যাপটপে compile করতেই স্ক্রিনে এলো:
main.cpp:4:10: error: 'println' is not a member of 'std'
অথচ একই কোড বন্ধুর কম্পিউটারে দিব্যি চলছে! কোড একই, তাহলে পার্থক্যটা কোথায়? পার্থক্য কোডে নয় — পার্থক্য toolchain-এ: কোন compiler, কোন version, কোন standard মেনে চলতে বলা হয়েছে। এই পর্ব শেষে আপনি এমন মেসেজ দেখেই বুঝবেন সমস্যাটি কোথায়।
আগের দুই পর্বে দেখেছেন C++ কেন আছে আর সমস্যাকে কীভাবে বস্তু ও দায়িত্বে ভাঙতে হয়। এবার প্রশ্ন — আপনার লেখা লেখাটুকু CPU পর্যন্ত পৌঁছায় কীভাবে? CPU বোঝে শুধু machine code. মাঝখানের অনুবাদ কয়েকটি ধাপে হয়। নিচের ডায়াগ্রামে “পরে” চাপুন, আর প্রতিটি ধাপে ফাইলের ভেতরের জিনিস কীভাবে বদলায় খেয়াল করুন।
- আপনার কোড
main.cpp৬ লাইন - Preprocess
main.iiহাজার হাজার লাইন - Compile
main.sassembly - Assemble
main.oফাঁকাসহ machine code - Link
main.exe+ libstdc++
#include <iostream>
int main() {
std::cout << "Hello, Dhaka!\n";
return 0;
}
// <iostream> নিজে আরও অনেক header টানে: // <ostream>, <istream>, <string>, <ios> … namespace std { template<typename _CharT, typename _Traits> class basic_ostream : virtual public basic_ios<_CharT, _Traits> { // … হাজার হাজার লাইন template কোড … extern ostream cout; } int main() { std::cout << "Hello, Dhaka!\n"; return 0; }
main:
sub rsp, 8
mov esi, OFFSET FLAT:.LC0 ; "Hello, Dhaka!\n"
mov edi, OFFSET FLAT:_ZSt4cout ; std::cout
call _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
xor eax, eax
add rsp, 8
ret
48 83 ec 08 be ?? ?? ?? ?? bf ?? ?? ?? ?? e8 ?? ?? ?? ?? 31 c0 48 83 c4 08 c3 ↑ ?? = “std::cout আর operator<< কোথায় আছে জানি না”
$ g++ main.cpp -o main
$ ./main
Hello, Dhaka!
- ধাপ ১ — আপনার কোড। মাত্র ৬ লাইনের
main.cpp. মানুষ পড়তে পারে, CPU কিছুই বোঝে না। C++ ফাইলের প্রচলিত এক্সটেনশন.cpp(কোথাও.ccবা.cxx). - ধাপ ২ — Preprocess. Preprocessor
#include <iostream>-এর জায়গায় পুরো header-এর লেখা বসিয়ে দেয়, আর সেই header আবার আরও অনেক header টানে। ৬ লাইনের ফাইল ফুলে হয়ে যায় হাজার হাজার লাইন — বেশিরভাগই template কোড। এই বড় লেখাটিকেই বলে একটি translation unit. - ধাপ ৩ — Compile. Compiler পুরো translation unit পড়ে, ব্যাকরণ আর টাইপ যাচাই করে, তারপর assembly বানায়। খেয়াল করুন
std::coutহয়ে গেছে_ZSt4cout— C++-এ নামগুলো এভাবে “সংকেত” হয়ে যায়। কেন, তা অংশ ৬-এ দেখবেন। - ধাপ ৪ — Assemble. Assembly থেকে আসল বাইট তৈরি হলো, ফাইলটি object file (
.o). কিন্তুstd::coutআরoperator<<আপনি লেখেননি — তারা আছে library-তে। তাই তাদের ঠিকানার জায়গা ফাঁকা (??). - ধাপ ৫ — Link. Linker আপনার
.o-র সাথে C++ standard library-র compile করা কোড (GCC-তে libstdc++) জুড়ে ফাঁকা ঘরগুলো পূরণ করে। তৈরি হলো executable. একটিg++ main.cpp -o mainকমান্ডই এই চারটি ধাপ পরপর চালিয়ে দিয়েছে।
অনুবাদ হয় ফাইল ধরে ধরে, জোড়া লাগে একদম শেষে
পাইপলাইন হুবহু এক — preprocessor, compiler, assembler, linker; -E, -S, -c-ও একই কাজ করে। পার্থক্য তিনটি: কমান্ড gcc-এর বদলে g++ (যা নিজে থেকেই C++ standard library জোড়ে), header অনেক বেশি ভারী (template-এ ভরা), আর ফাংশনের নাম mangle হয়ে object file-এ ঢোকে। চার ধাপের প্রতিটির গভীর ব্যাখ্যা, static/dynamic linking আর debugger দেখতে C কোর্সের পর্ব ০৩ · টুলচেইন পড়ুন।
২ · C++ কেন compile হতে বেশি সময় নেয়
যাঁরা C থেকে আসছেন, তাঁরা প্রথমেই খেয়াল করেন — একই আকারের প্রোগ্রাম C++-এ compile হতে বেশি সময় লাগে। বড় প্রজেক্টে এই পার্থক্য মিনিট থেকে ঘণ্টায় গিয়ে দাঁড়ায়। কারণগুলো বুঝলে পরে build দ্রুত করার কৌশলগুলোও সহজে বুঝবেন।
ধরুন ঢাকার একটি গার্মেন্টস কারখানায় ২০টি সেলাই লাইন। প্রতিটি লাইনের সুপারভাইজারকে কাজ শুরুর আগে একই ৫০০ পাতার নিয়মের বই পুরোটা পড়তে হয় — কারণ নিয়ম আছে, “বই সামনে না রেখে কাজ শুরু করা যাবে না।” ২০ জন সুপারভাইজার মিলে একই বই ২০ বার পড়ছেন! C++-এ প্রতিটি .cpp ফাইল ঠিক এভাবে নিজের জন্য <iostream>, <vector>, <string> নতুন করে পড়ে ও বিশ্লেষণ করে।
📚 ভারী header
Standard library-র বেশিরভাগ অংশ template, আর template-এর পুরো কোড header-এই থাকে। তাই একটি #include মানে হাজার হাজার লাইন বাড়তি লেখা — প্রতিটি translation unit-এ আলাদা করে।
🧩 Template তৈরি
std::vector<int> আর std::vector<std::string> — compiler প্রতিটি টাইপের জন্য আলাদা কোড বানায়। এটি চলার সময়ে দ্রুততা দেয়, কিন্তু compile-এর সময়ে খরচ বাড়ায়।
🧠 কঠিন ব্যাকরণ
Overload কোনটি ডাকা হবে, কোন টাইপ কোথায় মিলবে, constexpr হিসাব — C++ compiler-কে C-র চেয়ে অনেক বেশি সিদ্ধান্ত নিতে হয়।
⚡ বেশি optimization
অনেক ছোট ছোট ফাংশন আর template-কে একসাথে মিশিয়ে (inline করে) দ্রুত কোড বানাতে -O2-এ optimizer-কে প্রচুর কাজ করতে হয়।
যেকোনো ধাপে থামিয়ে মাঝের ফাইল দেখা যায় — C-র মতোই, শুধু কমান্ড g++:
| কোথায় থামতে চান | কমান্ড | যা পাবেন |
|---|---|---|
| Preprocess-এর পরে | g++ -E main.cpp -o main.ii | header বসানো পুরো translation unit |
| Compile-এর পরে | g++ -S main.cpp | assembly ফাইল main.s |
| Assemble-এর পরে | g++ -c main.cpp | object file main.o |
| সব মাঝের ফাইল রেখে দিতে | g++ -save-temps main.cpp | .ii, .s, .o তিনটিই |
| পুরো পথ | g++ main.cpp -o main | চালানো যায় এমন প্রোগ্রাম |
নিজে মেপে দেখতে চাইলে Linux বা MSYS2-এ লিখুন g++ -E main.cpp | wc -l — preprocess-এর পর কত লাইন হলো, গুনে দেবে। সংখ্যাটি compiler-এর version অনুযায়ী বদলায়, তবে ৬ লাইনের ফাইলের জন্য সেটি যে হাজার হাজার, তা দেখে অবাক হবেন। এখানেই লুকিয়ে আছে C++ build-এর ধীরগতির বড় কারণ।
প্রথম C++ compiler Cfront (১৯৮০-র দশকে Bjarne Stroustrup-এর তৈরি) আসলে C++ কোডকে C কোডে অনুবাদ করত, তারপর সাধারণ C compiler দিয়ে সেটি compile হতো। আজকের compiler সরাসরি machine code বানায়, কিন্তু সেই উত্তরাধিকার রয়ে গেছে — C-র মতোই #include-নির্ভর header ব্যবস্থা। C++20-এ এসে modules এই পুরোনো ব্যবস্থার বিকল্প হিসেবে যোগ হয়েছে; পর্ব ৪২-এ তার পরিচয় পাবেন।
৩ · তিন বড় compiler — আর আপনার কম্পিউটারে একটি বসানো
C++ কোনো একটি কোম্পানির পণ্য নয়। ভাষার নিয়ম লেখা আছে ISO-র একটি আনুষ্ঠানিক দলিলে — C++ standard. সেই নিয়ম মেনে আলাদা আলাদা দল compiler বানায়। আজ তিনটি compiler সবচেয়ে বেশি ব্যবহৃত:
g++ (GCC)
GNU প্রজেক্টের ফ্রি compiler. Linux জগতের ডিফল্ট, আর Codeforces-সহ বেশিরভাগ অনলাইন জাজেও এটিই চলে। সাথে আসে libstdc++.
clang++ (LLVM)
Apple-এর উদ্যোগে শুরু, এখন বিশাল open-source প্রজেক্ট। macOS-এর ডিফল্ট; Android NDK-ও এটি ব্যবহার করে। পরিষ্কার error message-এর জন্য বিখ্যাত। macOS-এ সাথে থাকে libc++.
MSVC
Microsoft-এর compiler, Visual Studio-র ভেতরে। Windows ডেস্কটপ সফটওয়্যার আর অনেক গেম স্টুডিওর প্রধান হাতিয়ার। Flag-এর ধরন একদম আলাদা: /W4, /std:c++20.
এই কোর্সে আমরা মূলত g++ ব্যবহার করব, কারণ এটি সব OS-এ পাওয়া যায় আর পাতার কোড-রানারও g++ দিয়েই চলে। তবে clang++-এর কমান্ড প্রায় হুবহু এক — g++-এর জায়গায় clang++ লিখলেই হয়। শুধু MSVC আলাদা নিয়মে চলে।
শুরুতে কিছু ইনস্টল না করলেও চলবে — এই কোর্সের কোড পাতাতেই চালানো যায়। তবে নিজের কম্পিউটারে সেটআপ করলে আসল কাজের অভিজ্ঞতা পাবেন। আপনার OS-এর ট্যাবটি বেছে নিন (ধাপগুলো উঁচু স্তরের; ইনস্টলারের পর্দা সময়ের সাথে একটু বদলাতে পারে):
পথ ক — MSYS2 (g++ পাবেন):
- msys2.org থেকে ইনস্টলার নামিয়ে ইনস্টল করুন।
- Start মেনু থেকে “MSYS2 UCRT64” খুলে নিচের কমান্ডটি দিন।
- MSYS2-এর
ucrt64\binফোল্ডারটি Windows-এর PATH-এ যোগ করলে সাধারণ Command Prompt আর VS Code থেকেওg++পাওয়া যাবে। g++ --versionলিখে দেখুন — একটি version নম্বর এলেই কাজ শেষ।
pacman -S mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-gdbপথ খ — Visual Studio বা “Build Tools for Visual Studio” (MSVC পাবেন): ইনস্টলারে “Desktop development with C++” বেছে নিন। তারপর Start মেনুর “Developer Command Prompt” থেকে cl কমান্ড চলবে।
Ubuntu বা Debian হলে terminal-এ একটি লাইনই যথেষ্ট (build-essential-এর ভেতরে g++ আর make দুটোই আছে):
sudo apt install build-essential gdb
g++ --versionFedora-তে sudo dnf install gcc-c++. Windows ব্যবহারকারীরা চাইলে WSL দিয়ে Windows-এর ভেতরেই Ubuntu চালিয়ে এই পথ নিতে পারেন।
Terminal খুলে Xcode Command Line Tools বসান:
xcode-select --install
clang++ --versionমজার ব্যাপার: Mac-এ g++ লিখলেও আসলে Apple-এর clang++ চলে। আসল GCC চাইলে Homebrew দিয়ে আলাদা বসাতে হয়।
- Compiler Explorer (godbolt.org) — নানা compiler আর version বেছে কোড চালানো যায়, পাশে assembly-ও দেখায়।
- Wandbox ও OnlineGDB — দ্রুত কোড চালানোর জন্য।
মোবাইল থেকেও চলে। তবে মনে রাখুন — অনলাইন টুলে compile flag আর standard আপনাকে নিজে বেছে নিতে হতে পারে।
অনেক বিশ্ববিদ্যালয়ের ল্যাবে আর পুরোনো টিউটোরিয়ালে Code::Blocks-এর সাথে বহু বছর আগের MinGW compiler থাকে, যা C++20/23-এর অনেক কিছু চেনে না। নতুন কোডে অদ্ভুত error এলে প্রথমেই g++ --version দেখুন — সমস্যাটি হয়তো আপনার কোডে নয়, compiler-এর বয়সে।
৪ · প্রতিদিনের কমান্ড — প্রতিটি flag ভেঙে দেখা
শুধু g++ main.cpp লিখলেও প্রোগ্রাম তৈরি হয়। কিন্তু পেশাদাররা প্রায় কখনো এভাবে লেখেন না। কয়েকটি ছোট flag যোগ করলেই compiler আপনার পাহারাদার হয়ে ওঠে। নিচের কমান্ডটির প্রতিটি অংশ ধাপে ধাপে দেখুন:
g++— GCC-র C++ “ম্যানেজার”। এটি নিজেই preprocess, compile, assemble আর link চালায়, আর link-এর সময় libstdc++ জুড়ে দেয়। (gccদিয়ে C++ ফাইল link করলে এই শেষ কাজটি হয় না —std::নিয়ে একগাদা undefined reference আসে।)-std=c++23— “C++23 standard-এর নিয়ম মেনে চলো।” না দিলে compiler নিজের ডিফল্ট standard নেয়, যা version অনুযায়ী আলাদা (অনেক GCC version-এ সেটিgnu++17). তানিয়ারstd::printlnerror-এর কারণ ঠিক এটাই — C++23-এর জিনিস, অথচ standard বলা হয়নি বা compiler পুরোনো।-Wall— “গুরুত্বপূর্ণ সাধারণ সব warning দেখাও।” নাম “all” হলেও সত্যিই সব নয়। C++-এ এর ভেতরেই আছে signed/unsigned তুলনার সতর্কতা, যা পরের অংশে কাজে লাগবে।-Wextra— আরও কিছু বাড়তি সতর্কতা: ব্যবহার না হওয়া parameter, সন্দেহজনক তুলনা ইত্যাদি।-Wall -Wextraএকসাথে রাখাই নিয়ম। কঠোর হতে চাইলে-Wpedantic(standard-এর বাইরের extension ধরতে) আর-Werror(warning-কেই error বানাতে) যোগ করা হয়।-g— প্রোগ্রামের ভেতরে debug তথ্য রাখো: কোন machine code কোন লাইনের। এতে gdb বা VS Code-এর debugger লাইন ধরে থামতে পারে। এটি প্রোগ্রামের গতি কমায় না, শুধু ফাইল একটু বড় হয়।-O2— optimization-এর মাত্রা।-O0(ডিফল্ট) মানে প্রায় অনুবাদ-মাত্র, debug করা সবচেয়ে সহজ;-O2মানে দ্রুত প্রোগ্রাম। শেখার সময় বা debug-এর সময়-O0বা-Ogভালো; চূড়ান্ত প্রোগ্রামে-O2.-g -O2একসাথেও চলে, তবে তখন debugger-এ কিছু ভ্যারিয়েবল “optimized out” দেখাতে পারে।main.cpp— কোন source ফাইল। একাধিক ফাইল থাকলে এখানে পাশাপাশি লেখা যায়:main.cpp wallet.cpp.-o main— তৈরি প্রোগ্রামের নাম। না দিলে নাম হয়a.out(Linux/Mac) বাa.exe(Windows). এরপর./mainলিখে চালান।
C++-এর সংস্করণগুলো — -std=-এ কী লিখবেন
C++ standard মোটামুটি প্রতি তিন বছরে নতুন সংস্করণ পায়। প্রতিটি সংস্করণের জন্য compiler __cplusplus নামের একটি ম্যাক্রোতে আলাদা সংখ্যা বসায় — সংখ্যাটি আসলে বছর আর মাস (yyyymm):
| সংস্করণ | g++ flag | __cplusplus | একটি পরিচিত সংযোজন |
|---|---|---|---|
| C++98 / C++03 | -std=c++98 | 199711L | প্রথম ISO standard, STL |
| C++11 | -std=c++11 | 201103L | auto, lambda, move, smart pointer |
| C++14 | -std=c++14 | 201402L | std::make_unique |
| C++17 | -std=c++17 | 201703L | std::optional, structured bindings |
| C++20 | -std=c++20 | 202002L | concepts, ranges, modules, std::format |
| C++23 | -std=c++23 (পুরোনো compiler-এ -std=c++2b) | 202302L | std::print, std::expected |
আপনার compiler আসলে কোন standard মেনে চলছে, প্রোগ্রাম নিজেই বলে দিতে পারে। নিচের কোডটি চালান — এই পাতার রানার C++23 মোডে compile করে:
#include <iostream>
// __cplusplus-এর মান দেখে standard-এর নাম বের করা
const char* standard_name(long v) {
if (v >= 202302L) return "C++23";
if (v >= 202002L) return "C++20";
if (v >= 201703L) return "C++17";
if (v >= 201402L) return "C++14";
if (v >= 201103L) return "C++11";
return "C++98/03";
}
int main() {
std::cout << "__cplusplus = " << __cplusplus << '\n';
std::cout << "Standard = " << standard_name(__cplusplus) << '\n';
}__cplusplus আপনি কোথাও সংজ্ঞায়িত করেননি — compiler নিজেই বসিয়ে দেয়। এখানে আউটপুট আসবে 202302 আর C++23. নিজের কম্পিউটারে -std=c++17 দিয়ে চালালে দেখবেন 201703. ইচ্ছা করেই <print> নয়, <iostream> ব্যবহার করা হয়েছে, যাতে পুরোনো standard-এও কোডটি compile হয়। একটি সতর্কতা: MSVC ডিফল্টভাবে এখানে 199711 দেখায়, সঠিক মান পেতে /Zc:__cplusplus flag লাগে — compiler-ভেদে এমন খুঁটিনাটি থাকে বলেই নির্দেশনা যাচাই জরুরি।
এক নজরে আগামীর একটি flag — sanitizer
আরেক জোড়া flag মনে রাখুন: -fsanitize=address,undefined. এটি দিলে compiler প্রোগ্রামে বাড়তি পাহারা-কোড বসায়। ফলে প্রোগ্রাম চলার সময় vector-এর সীমার বাইরে পড়া বা অন্য undefined behavior ঘটলেই সঙ্গে সঙ্গে লাইন নম্বরসহ রিপোর্ট দেয়। এই sanitizer Linux, WSL আর macOS-এ সহজে চলে; Windows-এর MinGW-তে সাধারণত চলে না (MSVC-র নিজস্ব /fsanitize=address আছে)। বিস্তারিত দেখবেন পর্ব ৪৩ আর পর্ব ৪৮-এ।
# শেখা ও debug-এর সময়
g++ -std=c++23 -Wall -Wextra -g -fsanitize=address,undefined main.cpp -o main
# চূড়ান্ত, দ্রুত প্রোগ্রাম
g++ -std=c++23 -Wall -Wextra -O2 main.cpp -o main৫ · Warning — compiler-এর বন্ধুসুলভ পাহারা
Warning মানে “প্রোগ্রাম বানালাম, কিন্তু এখানে কিছু গোলমেলে লাগছে।” নতুনরা প্রায়ই উপেক্ষা করেন — “চলছে তো!” অথচ বেশিরভাগ আসল বাগ প্রথমে warning হয়েই আসে। C++-এ সবচেয়ে পরিচিত উদাহরণ signed আর unsigned সংখ্যার মিশ্রণ।
একটি তালিকায় পাশাপাশি দুটি নম্বরের পার্থক্য বের করতে অনেকেই (এবং অনেক AI-ও) এভাবে লেখেন:
void print_steps(const std::vector<int>& marks) {
for (int i = 0; i < marks.size() - 1; ++i) { // বিপদ!
std::cout << marks[i + 1] - marks[i] << ' ';
}
}-Wall চালু থাকলে g++ সঙ্গে সঙ্গে বলে:
warning: comparison of integer expressions of different signedness: 'int' and 'std::vector<int>::size_type' {aka 'long unsigned int'} [-Wsign-compare]
মানে কী? marks.size() ফেরত দেয় একটি unsigned সংখ্যা — যা কখনো ঋণাত্মক হতে পারে না। তালিকা খালি হলে 0 - 1 ঋণাত্মক −১ হয় না, বরং ঘুরে গিয়ে হয় বিশাল একটি সংখ্যা (64-bit কম্পিউটারে প্রায় ১৮ কুইন্টিলিয়ন)। তখন লুপ থামে না, আর খালি vector-এর বাইরে পড়তে থাকে — undefined behavior. ঠিক যেন মিটার রিডিং ০০০০ থেকে এক ঘর পেছালে ৯৯৯৯ হয়ে যাওয়া। আধুনিক সমাধান নিচে:
#include <iostream>
#include <vector>
// পাশাপাশি দুটি নম্বরের পার্থক্য ছাপে
void print_steps(const std::vector<int>& marks) {
// std::ssize: আকারটি signed সংখ্যা হিসেবে দেয় (C++20 থেকে)
for (int i = 0; i + 1 < std::ssize(marks); ++i) {
std::cout << marks[i + 1] - marks[i] << ' ';
}
std::cout << "(done)\n";
}
int main() {
print_steps({70, 85, 92});
print_steps({}); // খালি তালিকা: লুপ একবারও চলবে না
}দুটি পরিবর্তন: std::ssize আকারটি signed সংখ্যা হিসেবে দেয়, তাই int-এর সাথে তুলনায় আর মিশ্রণ নেই; আর i < size - 1-এর বদলে i + 1 < size লেখায় বিয়োগই হয় না। আউটপুট: প্রথম লাইনে 15 7 (done), দ্বিতীয় লাইনে শুধু (done) — খালি তালিকায় প্রোগ্রাম নিরাপদ। কোডটি -Wall -Wextra-এ একটিও warning দেয় না। যেখানে ইনডেক্স দরকারই নেই, সেখানে for (int m : marks)-এর মতো range-for আরও সহজ — পর্ব ০৮-এ দেখবেন।
লক্ষ্য হলো শূন্য warning. Warning “চুপ” করানোর জন্য flag সরিয়ে দেওয়া মানে ধোঁয়ার অ্যালার্মের ব্যাটারি খুলে রাখা — আগুন থেকেই যায়। বড় কোম্পানিগুলোর অনেক প্রজেক্টে -Werror দিয়ে নিয়ম করা থাকে: একটি warning থাকলেও কোড জমা হবে না।
৬ · একাধিক ফাইল — আলাদা compile, শেষে link
বাস্তবের প্রোগ্রাম একটি ফাইলে থাকে না। ধরুন একটি ছোট মোবাইল ওয়ালেট অ্যাপ — টাকা জমার হিসাবের কোড এক ফাইলে, মূল প্রোগ্রাম আরেক ফাইলে। C++-এ এর প্রচলিত গঠন তিনটি ফাইল:
wallet.h— ঘোষণা (declaration): “depositনামে একটি ফাংশন আছে, দুটিintনেয়,intফেরত দেয়।” যেন রেস্টুরেন্টের মেনু — কী পাওয়া যায় বলে, রান্না করে না।wallet.cpp— সংজ্ঞা (definition): ফাংশনের আসল দেহ। যেন রান্নাঘর।main.cpp— ফাংশনটি ব্যবহার করে;#include "wallet.h"দিয়ে শুধু মেনুটি দেখে।
এই declaration আর definition-এর ভাগটাই পুরো ব্যবস্থার চাবি। Compiler-এর কাছে মেনু থাকলেই যথেষ্ট; রান্নাঘর খুঁজে বের করার দায় linker-এর। নিচের ডায়াগ্রামে দেখুন:
- Header
wallet.hশুধু ঘোষণা - সংজ্ঞা
wallet.odeposit-এর কোড আছে - ব্যবহার
main.odeposit চাই, কিন্তু নেই - Link
wallet.exeদুটো জোড়া লাগল
// wallet.h int deposit(int balance, int amount); // ঘোষণা // wallet.cpp #include "wallet.h" int deposit(int balance, int amount) { // সংজ্ঞা return balance + amount; } // main.cpp #include <iostream> #include "wallet.h" int main() { std::cout << "Balance: " << deposit(500, 250) << '\n'; }
$ g++ -std=c++23 -Wall -Wextra -c wallet.cpp $ nm wallet.o 0000000000000000 T _Z7depositii ; T = সংজ্ঞা এখানে আছে $ nm -C wallet.o 0000000000000000 T deposit(int, int)
$ g++ -std=c++23 -Wall -Wextra -c main.cpp
$ nm -C main.o | grep deposit
U deposit(int, int) ; U = undefined: অন্য কোথাও খুঁজতে হবে
$ g++ main.o wallet.o -o wallet
$ ./wallet
Balance: 750
$ g++ main.o -o wallet
/usr/bin/ld: main.o: in function `main':
main.cpp:(.text+0x1e): undefined reference to `deposit(int, int)'
collect2: error: ld returned 1 exit status
- তিনটি ফাইল।
wallet.h-এ শুধু ঘোষণা,wallet.cpp-এ আসল কোড,main.cppব্যবহার করছে। দুটি.cpp-ইwallet.hinclude করে — তাই দুজনেই একই “মেনু” দেখে, ভুল বোঝাবুঝির সুযোগ কম।<iostream>-এর মতো কোণ-বন্ধনী মানে system-এর header;"wallet.h"-এর মতো উদ্ধৃতি মানে আগে নিজের ফোল্ডারে খোঁজো। wallet.cpp→wallet.o.-cflag বলে “link কোরো না, শুধু object file বানাও।”nmটুল দিয়ে object file-এর ভেতরের symbol-গুলো দেখা যায়।Tমানে এই ফাইলে সংজ্ঞা আছে। নামটা খেয়াল করুন:_Z7depositii—-Cদিলে পড়ার মতোdeposit(int, int)দেখায়।main.cpp→main.o. Compiler-এর কাছে ঘোষণা আছে, তাই সে খুশি — টাইপ মিলছে, ডাকার নিয়ম জানা। কিন্তুmain.o-তেdeposit-এর পাশেU— undefined. “এটা অন্য কোথাও আছে, linker খুঁজে নেবে।” দুটি ফাইল কেউ কারও কথা জানে না; চাইলে দুই প্রোগ্রামার দুটি একসাথে লিখতে পারেন।- Link.
g++ main.o wallet.o -o wallet— linkermain.o-র ফাঁকাU-কেwallet.o-রT-এর সাথে নাম মিলিয়ে জুড়ে দিল, সাথে libstdc++. প্রোগ্রাম চলল। এরপর শুধুwallet.cppবদলালে কেবল সেটিই আবার compile করে link করলেই হবে — বড় প্রজেক্টে এভাবেই সময় বাঁচে। - ভুলে
wallet.oবাদ দিলে। Compile তো আগেই সফল হয়েছিল, তাই compiler কিছু বলেনি। এবার linker (ld) মেনুতে লেখা খাবারের রান্নাঘর খুঁজে পেল না:undefined reference to `deposit(int, int)'. এটি একটি linker error — কোডের ব্যাকরণে ভুল নেই, build কমান্ডে একটি টুকরো অনুপস্থিত। একই error আসে যদি সংজ্ঞায় টাইপ আলাদা হয়, যেমনdeposit(long, int).
ছোট প্রজেক্টে এক লাইনেই সব করা যায় — g++ -std=c++23 -Wall -Wextra main.cpp wallet.cpp -o wallet. g++ ভেতরে ভেতরে ঠিক উপরের ধাপগুলোই করে। কখনো g++ main.cpp wallet.h লিখবেন না — header আলাদাভাবে compile করার জিনিস নয়, সে .cpp-এর ভেতরে include হয়েই কাজ করে।
নামগুলো এমন অদ্ভুত কেন — name mangling
C-তে deposit ফাংশনের symbol সাধারণত শুধু deposit. C++-এ সেটি _Z7depositii. কারণ C++-এ একই নামে একাধিক ফাংশন থাকতে পারে (overloading, পর্ব ০৯) — add(int, int) আর add(double, double). Linker শুধু নাম মেলায়, টাইপ বোঝে না। তাই compiler নামের ভেতরেই টাইপ লিখে দেয়। একে বলে name mangling.
| C++ ফাংশন | g++/clang++ symbol | কীভাবে পড়বেন |
|---|---|---|
int add(int, int) | _Z3addii | _Z = C++ নাম, 3add = ৩ অক্ষরের নাম “add”, ii = দুটি int |
double add(double, double) | _Z3adddd | dd = দুটি double — তাই দুটি আলাদা symbol |
int deposit(int, int) | _Z7depositii | ৭ অক্ষরের “deposit”, দুটি int |
int add(int, int) (MSVC) | ?add@@YAHHH@Z | MSVC-র নিয়ম একদম আলাদা |
খেয়াল করুন, return টাইপ সাধারণ ফাংশনের symbol-এ থাকে না — শুধু parameter. Mangled নাম হাতে পেলে c++filt _Z3addii লিখলে পড়ার মতো রূপ দেখায়। আর C-তে লেখা কোনো library-র ফাংশন C++ থেকে ডাকতে হলে ঘোষণার আগে extern "C" লিখতে হয় — তখন compiler নামটি mangle করে না, C-র নিয়মে রাখে। C আর C++ একসাথে জোড়ার এই খুঁটিনাটি দেখতে C কোর্সের মডুলার প্রোগ্রাম পর্বটিও কাজে লাগবে; header আর namespace দিয়ে বড় C++ প্রোগ্রাম সাজানো দেখবেন পর্ব ১১-এ।
৭ · Error পড়তে শেখা — compile, link না runtime?
এই পর্বের সবচেয়ে কাজের দক্ষতা এটি। প্রতিটি error মেসেজের ভেতরেই লেখা থাকে সে কোন ধাপ থেকে এসেছে। ধাপ চিনলে কোথায় খুঁজবেন তাও বুঝে যাবেন।
| ধাপ | মেসেজে যা খুঁজবেন | সাধারণ কারণ |
|---|---|---|
| 📦 Preprocess | fatal error: wallet.h: No such file or directory | header-এর নাম বা ফোল্ডার ভুল |
| 📐 Compile | main.cpp:5:33: error: expected ';' | ব্যাকরণ ভুল, সেমিকোলন বা বন্ধনী বাদ |
| 📐 Compile | error: 'cout' is not a member of 'std' | #include <iostream> নেই |
| 📐 Compile | error: conversion from 'int' to non-scalar type 'std::string' … | টাইপ মেলেনি |
| 🧵 Link | undefined reference to `deposit(int, int)', ld returned 1 | সংজ্ঞা নেই, বা .cpp/.o কমান্ডে নেই |
| 🧵 Link | multiple definition of `deposit(int, int)' | header-এ ফাংশনের পুরো দেহ লিখে দুটি .cpp-তে include |
| 💥 Runtime | Segmentation fault, terminate called after throwing …, বা ভুল ফল | ভুল মেমরি, অধরা exception, যুক্তির ভুল |
একটি সহজ সূত্র: মেসেজের শুরুতে ফাইল.cpp:লাইন:কলাম: error: থাকলে সেটি compiler; ld, collect2 বা undefined reference থাকলে linker. এবার একটি আসল compile error লাইন ধরে ধরে পড়ি। কোডে std::cout << "Hello" << '\n'-এর শেষে সেমিকোলন বাদ পড়েছে:
1main.cpp: In function 'int main()':2main.cpp:5:33: error: expected ';' before 'return'3 5 | std::cout << "Hello" << '\n'4 | ^5 | ;6 6 | return 0;7 | ~~~~~~
| ফাইল | |
| ফাংশন | |
| লাইন:কলাম | |
| ধাপ | |
| সমাধান |
- প্রেক্ষাপট। প্রথম লাইন বলছে ভুলটি কোন ফাংশনের ভেতরে —
int main(). বড় ফাইলে এটুকুই খোঁজার জায়গা অনেক ছোট করে দেয়। - আসল লাইন —
ফাইল:লাইন:কলাম: error:main.cpp-এর ৫ নং লাইন, ৩৩ নং কলাম। শব্দটিerror(warning নয়), তাই প্রোগ্রাম তৈরি হয়নি। বার্তা: “return-এর আগে একটি;আশা করেছিলাম।” - কোডের ছবি আর তীর। g++ নিজেই লাইনটি দেখিয়ে
^দিয়ে ঠিক জায়গা চিনিয়ে দিচ্ছে, আর নিচে;লিখে পরামর্শও দিচ্ছে (একে বলে fix-it hint). কলাম ৩৩ হলো লাইনের শেষ অক্ষরের ঠিক পরের ঘর। - কেন লাইন ৬-এর কথা আসছে? Compiler ৫ নং লাইনের শেষে থামেনি, কারণ C++-এ একটি statement পরের লাইনেও চলতে পারে। সে পরের শব্দ
returnপড়ে তবেই বুঝল কিছু একটা বাদ পড়েছে। তাই মনে রাখুন: error-এর লাইনে ভুল না পেলে ঠিক আগের লাইনটি দেখুন। - ঠিক করে আবার চালান।
;বসিয়ে আবার compile করুন। একটি ভুল প্রায়ই পরে আরও কয়েকটি “ভুয়া” error তৈরি করে, তাই নিয়ম হলো — সবসময় প্রথম error আগে ঠিক করুন, তারপর আবার compile. প্রায়ই বাকিগুলো নিজে থেকেই চলে যায়।
Template error — দুই পাতার মেসেজ, এক লাইনের সমস্যা
C++-এর কুখ্যাত দিক হলো template-এর ভেতরের error. ধরুন std::list-কে std::sort দিয়ে সাজাতে চাইলেন (যা চলে না, কারণ std::sort-এর দরকার এমন iterator যা সরাসরি লাফ দিতে পারে)। g++ কয়েক ডজন লাইনের মেসেজ দেবে, যার বড় অংশ standard library-র ভেতরের ফাইল থেকে:
In file included from /usr/include/c++/…/algorithm:61,
from main.cpp:1:
/usr/include/c++/…/bits/stl_algo.h: In instantiation of 'void std::__sort(…) [with _RandomAccessIterator = std::_List_iterator<int>; …]':
main.cpp:6:14: required from here
/usr/include/c++/…/bits/stl_algo.h: error: no match for 'operator-' (operand types are 'std::_List_iterator<int>' and 'std::_List_iterator<int>')
… আরও অনেক “note: candidate …” লাইন …
পড়ার কৌশল তিনটি: (১) প্রথম error: লাইনটি খুঁজুন — এখানে “দুটি list iterator বিয়োগ করা যায় না”; (২) required from here লেখা যে লাইনে আপনার নিজের ফাইলের নাম (main.cpp:6) আছে, সেখানে যান — ভুলটা আপনার ডাকে, library-তে নয়; (৩) note: candidate-এর লম্বা তালিকা প্রথমবার এড়িয়ে যান। C++20-এর concepts (পর্ব ৪০) ঠিক এই সমস্যা কমাতেই এসেছে — তখন মেসেজ অনেক ছোট ও স্পষ্ট হয়, যেমন “constraint not satisfied”.
- Compile error আর linker error-এর পার্থক্য কী? — Compile error একটি ফাইলের ব্যাকরণ বা টাইপের ভুল; linker error সব ফাইল জোড়ার সময় কোনো সংজ্ঞা না পাওয়া বা দুবার পাওয়া।
- Translation unit কী? — একটি
.cppফাইল আর preprocess-এর পর তাতে ঢোকা সব header মিলে যা compiler একবারে compile করে। #include <x>আর#include "x"-এর পার্থক্য? — কোণ-বন্ধনী system-এর include ফোল্ডারে খোঁজে; উদ্ধৃতি আগে নিজের ফোল্ডারে, না পেলে system-এ।- C++-এ name mangling কেন লাগে? — Overloading আর namespace-এর কারণে একই নামের অনেক ফাংশন থাকে; mangling নামের ভেতরে টাইপ বসিয়ে linker-এর জন্য প্রতিটিকে অনন্য করে।
-Wallকি সত্যিই সব warning চালু করে? — না; প্রচলিত গুরুত্বপূর্ণগুলো করে, বাড়তি জন্য-Wextraলাগে।
৮ · IDE, অনলাইন টুল আর build system
নতুনরা প্রায়ই ভাবেন “Code::Blocks-ই তো compiler.” আসলে না। IDE হলো কোড লেখার খাতা আর বোতামের প্যানেল; “Build” চাপলে ভেতরে সে g++, clang++ বা MSVC-কে ডাকে — ঠিক এই পর্বে শেখা কমান্ডগুলোই। IDE বদলালেও toolchain-এর ধারণা বদলায় না।
VS Code
হালকা, ফ্রি editor. Microsoft-এর C/C++ extension অথবা clangd extension যোগ করলে কোডে ভুলের দাগ, auto-complete আর debugger পাবেন। Compiler আলাদা করে বসাতে হয় (MSYS2, Linux বা Xcode-এর টুল)।
শেখা ও পেশাদার, দুই কাজেইVisual Studio
Windows-এর পূর্ণাঙ্গ IDE, ভেতরে MSVC. শক্তিশালী debugger; Community সংস্করণ ব্যক্তিগত ও শিক্ষার কাজে ফ্রি।
Windows অ্যাপ, গেমCLion
JetBrains-এর C++ IDE, CMake-কেন্দ্রিক। বড় প্রজেক্টে কোড খোঁজা ও refactor-এ সুবিধা। শিক্ষার্থীদের জন্য ফ্রি লাইসেন্সের সুযোগ আছে।
বড় প্রজেক্টCode::Blocks
দেশের অনেক বিশ্ববিদ্যালয়ের ল্যাবে পরিচিত। ব্যবহার সহজ, তবে সাথে আসা MinGW পুরোনো হতে পারে — Settings → Compiler-এ -std=c++20 বা নতুন standard চালু আছে কি না দেখে নিন।
Compiler Explorer — compiler-এর মনের ভেতর উঁকি
Compiler Explorer (godbolt.org, Matt Godbolt-এর তৈরি) বাঁ পাশে C++ কোড আর ডান পাশে তার assembly দেখায় — একসাথে কয়েকটি compiler ও version পাশাপাশি রেখে তুলনাও করা যায়। -O0 আর -O2-এ একই কোডের assembly কত আলাদা, বা range-for আর ইনডেক্স-লুপ শেষ পর্যন্ত একই কোড হয় কি না — এসব নিজের চোখে দেখার সবচেয়ে সহজ উপায়। Codeforces, AtCoder বা অন্য অনলাইন জাজে কোড জমা দেওয়ার সময়ও খেয়াল রাখুন কোন compiler আর কোন standard বাছছেন — কনটেস্টের দিক পর্ব ৫০-এ আলাদা করে দেখবেন।
Build system — হাতের কমান্ড যখন আর চলে না
দুই-তিনটি ফাইলে এক লাইনের g++ কমান্ডই যথেষ্ট। কিন্তু ২০০ ফাইল, বাইরের library আর Windows-Linux দুই জায়গায় build — তখন হাতে কমান্ড লেখা অসম্ভব। Build system হিসাব রাখে কোন ফাইল বদলেছে, কোনটি কোন flag-এ compile হবে, আর কী link হবে। C++ জগতে প্রায় মানদণ্ড হয়ে গেছে CMake — সে নিজে compile করে না, বরং প্রতিটি প্ল্যাটফর্মের জন্য Make, Ninja বা Visual Studio-র build ফাইল বানিয়ে দেয়। এক ঝলক দেখুন; পূর্ণ ব্যাখ্যা পর্ব ৪৭-এ:
# CMakeLists.txt (তিন লাইনের মূল অংশ)
# project(wallet LANGUAGES CXX)
# set(CMAKE_CXX_STANDARD 23)
# add_executable(wallet main.cpp wallet.cpp)
cmake -S . -B build # build ফাইল তৈরি
cmake --build build # compile + link৯ · AI-এর যুগে — build নির্দেশনাও যাচাই করুন
AI এখন কোডের সাথে build কমান্ডও লিখে দেয়। সমস্যা হলো, AI আপনার কম্পিউটার দেখতে পায় না — জানে না আপনার OS কোনটি, compiler কোন version, কোন terminal খোলা। ইন্টারনেটের পুরোনো লেখা থেকে শেখা বলে সে প্রায়ই বহু বছর আগের নিয়ম দেয়। কোড যাচাইয়ের মতোই build নির্দেশনা যাচাই করা এখন একটি আলাদা দক্ষতা।
AI সাধারণত যেসব build-ভুল করে
| AI-এর ভুল | আপনি যা দেখবেন | ঠিক উপায় |
|---|---|---|
-std= দিতে ভুলে গেছে, অথচ কোডে C++20/23-এর জিনিস | 'println' is not a member of 'std', 'ssize' is not a member of 'std' | কোড যে standard চায় তা -std=-এ লিখুন, compiler-এর version দেখুন |
| MSVC আর g++-এর flag মিশিয়েছে | g++: error: /W4: No such file or directory | g++/clang++-এ -Wall, MSVC-তে /W4 — এক কমান্ডে একটিই ধরন |
C++ কোড gcc বা clang দিয়ে link করতে বলেছে | undefined reference to `std::…' জাতীয় অসংখ্য লাইন | g++ বা clang++ ব্যবহার করুন |
পুরোনো flag দিয়েছে (-std=c++0x, -std=c++1z) | চলতে পারে, কিন্তু পুরোনো standard বেছে নেওয়া হয় | আধুনিক নাম: c++17, c++20, c++23 |
build কমান্ডে একটি .cpp বাদ রেখেছে | undefined reference to … | সব .cpp কমান্ডে বা CMake-এর তালিকায় আছে কি না দেখুন |
| Windows-এ sanitizer বা Linux-এর header ধরে নিয়েছে | link error বা No such file | নিজের OS আর compiler AI-কে স্পষ্ট বলুন |
AI-কে build বা error নিয়ে প্রশ্ন করার সময় তিনটি জিনিস সবসময় দিন — OS, compiler-এর version (g++ --version-এর আউটপুট), আর হুবহু কমান্ড। নিচের কাঠামোটি কপি করে ব্যবহার করুন:
AI-এর দেওয়া build নির্দেশনা হাতে পেলে — চেকলিস্ট
যিনি toolchain বোঝেন, AI-এর ভুলও তিনিই আগে ধরেন।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলো compiler-এর বোতাম চেনা থেকে toolchain-এর নকশা বোঝার দিকে নিয়ে যাবে।
প্র ০১বড় কোম্পানিগুলোর কাছে C++-এর build সময় কেন এত বড় সমস্যা, আর modules, precompiled header বা ccache কীভাবে সাহায্য করে?
একটি ছোট প্রোগ্রামে compile হতে এক সেকেন্ড লাগলে কেউ মাথা ঘামায় না। কিন্তু Chromium ব্রাউজার, বড় গেম ইঞ্জিন বা ডেটাবেসের মতো প্রজেক্টে হাজার হাজার .cpp ফাইল, আর প্রতিটি ফাইল নিজের জন্য একই ভারী header নতুন করে পড়ে। সাধারণ ল্যাপটপে এমন প্রজেক্টের পূর্ণ build ঘণ্টার হিসাবে লাগতে পারে। শত শত প্রকৌশলী দিনে বহুবার build করলে সেই অপেক্ষা মানে বিপুল বেতন আর মনোযোগের অপচয় — প্রতিবার অপেক্ষায় মনোযোগ ভেঙে যায়, আবার কাজে ফিরতে সময় লাগে।
সমস্যার মূল আগেই দেখেছেন: #include হলো লেখা কপি করা। তাই সমাধানগুলোও মূলত “একই কাজ বারবার না করা”:
- Precompiled header (PCH): প্রায় সব ফাইলে লাগে এমন header (যেমন
<vector>,<string>) একবার বিশ্লেষণ করে compiler-এর নিজস্ব রূপে জমিয়ে রাখা, পরে সেটাই সরাসরি ব্যবহার। - ccache: একই ইনপুট আর একই flag-এ আগে যে object file তৈরি হয়েছিল, সেটি cache থেকে তুলে দেওয়া — compile না করেই।
- Distributed build: distcc বা বড় কোম্পানির নিজস্ব ব্যবস্থায় compile-এর কাজ বহু মেশিনে ভাগ করা — যেহেতু প্রতিটি translation unit স্বাধীন, তাই ভাগ করা সহজ।
- Modules (C++20): সবচেয়ে মৌলিক সমাধান। Module একবার compile হয়ে একটি বাইনারি interface বানায়;
importকরলে লেখা কপি হয় না। C++23-এimport std;দিয়ে পুরো standard library একসাথে আনার ব্যবস্থাও এসেছে। তবে compiler আর build system-এর সমর্থন পরিপক্ব হতে কয়েক বছর লেগেছে, তাই অনেক পুরোনো প্রজেক্ট এখনো header-নির্ভর।
আরেকটি সস্তা কিন্তু কার্যকর কৌশল হলো header-এ যতটা সম্ভব কম #include রাখা — দরকার হলে শুধু forward declaration.
মূল উপলব্ধি: build সময় শুধু কারিগরি বিষয় নয়, এটি দলের উৎপাদনশীলতার বিষয়। C++-এর “চলার সময়ে দ্রুত” নকশার দাম দিতে হয় “compile-এর সময়ে ধীর” দিয়ে — আর পুরো একটি টুল-জগৎ সেই দাম কমানোর জন্যই তৈরি হয়েছে।
প্র ০২একই ভাষার জন্য তিনটি বড় compiler কেন, আর সবাই “একই” C++ মেনে চলছে কি না তা নিশ্চিত হয় কীভাবে?
C++-এর মালিক কোনো একটি কোম্পানি নয়। ভাষার নিয়ম ঠিক করে ISO-র একটি কমিটি — আনুষ্ঠানিক নাম WG21 — যেখানে বিভিন্ন কোম্পানি, বিশ্ববিদ্যালয় আর স্বাধীন বিশেষজ্ঞরা প্রস্তাব জমা দেন, বিতর্ক করেন, ভোট দেন। ফলাফল হলো একটি দীর্ঘ দলিল: standard. Compiler বানানো আলাদা কাজ — যে কেউ সেই দলিল মেনে বানাতে পারে।
তিনটি বড় compiler এসেছে তিন আলাদা প্রয়োজন থেকে। GCC হলো GNU প্রজেক্টের ফ্রি সফটওয়্যার আন্দোলনের অংশ, Linux-এর সাথে বেড়ে উঠেছে। Microsoft নিজের Windows আর Visual Studio-র জন্য MSVC বানিয়েছে। আর Apple চেয়েছিল এমন একটি compiler যার লাইসেন্স বেশি উদার এবং যাকে নিজের টুলে (যেমন Xcode-এর code-completion) সহজে জোড়া যায় — সেখান থেকে Clang. প্রতিযোগিতা সবাইকে ভালো করেছে: Clang-এর পরিষ্কার error message দেখে GCC নিজের মেসেজ অনেক উন্নত করেছে।
কিন্তু “একই C++” নিশ্চিত হয় কীভাবে? পুরোপুরি হয় না, আর সেটা জানা জরুরি:
- সময়ের ব্যবধান: standard প্রকাশের পরও প্রতিটি compiler নতুন ফিচার আলাদা গতিতে যোগ করে। cppreference.com-এর “compiler support” টেবিলে দেখা যায় কোন ফিচার কোন version থেকে আছে।
- Feature-test macro:
__cplusplus-এর মতো__cpp_lib_printজাতীয় ম্যাক্রো দিয়ে কোড নিজেই যাচাই করতে পারে কোনো ফিচার আছে কি না। - Extension: প্রতিটি compiler নিজস্ব বাড়তি জিনিস দেয় (যেমন GCC-র
gnu++17মোড)।-Wpedanticসেগুলো ধরিয়ে দেয়। - Implementation-defined আচরণ: standard কিছু বিষয় ইচ্ছা করেই compiler-এর ওপর ছেড়ে দিয়েছে, যেমন
intকত বাইট।
এজন্যই বড় open-source প্রজেক্ট সাধারণত নিজেদের কোড অন্তত দুটি compiler-এ নিয়মিত build করে — এক compiler যে ভুল ছেড়ে দেয়, অন্যটি তা ধরে ফেলে।
মূল উপলব্ধি: standard হলো চুক্তি, compiler হলো সেই চুক্তির বাস্তবায়ন। “আমার মেশিনে চলে” মানে এক বাস্তবায়নে চলে — বহনযোগ্য কোড মানে চুক্তি মেনে লেখা কোড।
প্র ০৩ABI compatibility বলতে কী বোঝায়? libstdc++-এর “C++11 ABI” পরিবর্তন থেকে কী শেখার আছে?
API হলো source code-এর স্তরের চুক্তি: কোন ফাংশন আছে, কী parameter নেয়। ABI (Application Binary Interface) হলো compile হওয়া বাইনারির স্তরের চুক্তি: symbol-এর নাম কীভাবে mangle হবে, argument কোন register-এ যাবে, একটি std::string object মেমরিতে কত বাইট নেবে আর ভেতরের ঘরগুলো কোন ক্রমে থাকবে। দুটি object file বা একটি প্রোগ্রাম আর একটি compile করা library একসাথে কাজ করতে হলে ABI মিলতে হয় — শুধু source মিললে হয় না।
একটি বাস্তব উদাহরণ: C++11 standard এমন নিয়ম আনে যে std::string-এর পুরোনো “copy-on-write” বাস্তবায়ন আর বৈধ থাকে না, আর std::list::size()-কে দ্রুত হতে হবে। libstdc++-কে তাই std::string আর std::list-এর ভেতরের গঠন বদলাতে হলো। কিন্তু তাতে পুরোনো library-গুলোর সাথে বাইনারি মিল ভেঙে যাবে! সমাধান হিসেবে GCC 5 থেকে libstdc++ দুই রকম ABI একসাথে রাখে — নতুনগুলো থাকে std::__cxx11 নামের ভেতরের namespace-এ, আর _GLIBCXX_USE_CXX11_ABI ম্যাক্রো দিয়ে কোনটি ব্যবহার হবে বেছে নেওয়া যায়। এজন্যই g++-এর error-এ মাঝে মাঝে std::__cxx11::basic_string দেখবেন। Linux distribution-গুলোকে এই রূপান্তর সামলাতে সব C++ প্যাকেজ নতুন করে build করতে হয়েছিল।
Microsoft-এর গল্পটাও শিক্ষণীয়। আগে প্রায় প্রতিটি Visual Studio সংস্করণে MSVC-র ABI বদলাত, ফলে library-কে প্রতি সংস্করণের জন্য আলাদা build করতে হতো। Visual Studio 2015 থেকে Microsoft ABI স্থির রাখার প্রতিশ্রুতি দেয় — পরের কয়েকটি সংস্করণ একে অপরের বাইনারির সাথে মেলে। এর দামও আছে: standard library-র কিছু ভেতরের উন্নতি তারা করতে পারে না, কারণ তাতে ABI ভাঙবে। C++ কমিটিতেও “ABI ভাঙা উচিত কি না” নিয়ে বছরের পর বছর তীব্র বিতর্ক চলেছে।
মূল উপলব্ধি: একবার বাইনারি ছড়িয়ে পড়লে তার গঠন বদলানো কঠিন। এজন্যই অনেক library বাইরের দিকে সরল C ইন্টারফেস (extern "C") দেয়, আর বাইনারি ভাগাভাগির সময় compiler-এর version মেলানো জরুরি।
প্র ০৪অনেক জনপ্রিয় C++ library “header-only” কেন? এর সুবিধা কী, আর দাম কী?
Header-only library মানে library-র সব কোড header ফাইলে — আলাদা .cpp compile করা বা .a/.lib ফাইল link করার ঝামেলা নেই। ফাইলটি প্রজেক্টে রেখে #include দিলেই কাজ শেষ। JSON পড়ার জনপ্রিয় library nlohmann/json, গাণিতিক matrix library Eigen, আর Boost-এর বড় অংশ এভাবেই তৈরি।
কেন এমন? প্রথম কারণ template. std::vector<T>-এর মতো template-এর কোড compiler তখনই বানাতে পারে যখন সে জানে T কী — আর সেটা জানা যায় শুধু ব্যবহারকারীর ফাইলে। তাই template-এর পুরো সংজ্ঞা ব্যবহারকারীর translation unit-এ দেখা যেতে হয়, অর্থাৎ header-এ। Standard library-র বড় অংশও এ কারণে header-এ লেখা। দ্বিতীয় কারণ বিতরণ সহজ করা: C++-এর কোনো একক, সর্বজনীন প্যাকেজ ম্যানেজার দীর্ঘদিন ছিল না (Python-এর pip-এর মতো)। ভিন্ন compiler, ভিন্ন ABI, ভিন্ন OS-এর জন্য compile করা library দেওয়া ঝামেলার — header-only হলে সেই সমস্যাই থাকে না, কারণ ব্যবহারকারী নিজের compiler দিয়েই সব compile করেন।
দাম কী? সবচেয়ে বড় দাম compile সময়। যে ফাইলই header-টি include করে, সবাই পুরো library নতুন করে বিশ্লেষণ করে — প্রশ্ন ০১-এর সমস্যাটা আরও বাড়ে। আরেকটি সূক্ষ্ম বিষয় হলো One Definition Rule: header-এ সাধারণ (non-template) ফাংশনের পুরো দেহ লিখে দুটি .cpp-তে include করলে linker “multiple definition” error দেয়। তাই header-only library-তে এমন ফাংশন inline হিসেবে চিহ্নিত থাকে, আর C++17 থেকে ভ্যারিয়েবলও inline করা যায়। কিছু library দুই পথই খোলা রাখে — চাইলে header-only, চাইলে আলাদা compile করা সংস্করণ, যাতে বড় প্রজেক্ট compile সময় বাঁচাতে পারে।
মূল উপলব্ধি: header-only হলো “ব্যবহার সহজ” বনাম “build দ্রুত”-এর মধ্যে একটি সচেতন trade-off. Modules (C++20) এই দুইয়ের মধ্যে সেতু হওয়ার প্রতিশ্রুতি দেয় — সহজ বিতরণ, কিন্তু বারবার বিশ্লেষণ ছাড়া।
প্র ০৫Compiler Explorer-এ assembly দেখে C++-এর “zero-overhead” নীতি সম্পর্কে কী শেখা যায়?
C++-এর স্রষ্টা Bjarne Stroustrup ভাষাটির একটি মূল নীতি এভাবে বলেছেন: “What you don't use, you don't pay for. And further: What you do use, you couldn't hand code any better.” অর্থাৎ যে ফিচার ব্যবহার করছেন না তার কোনো খরচ নেই, আর যা ব্যবহার করছেন তা হাতে লেখা নিচু-স্তরের কোডের চেয়ে খারাপ হবে না। একে বলে zero-overhead principle.
কথাটা বিশ্বাস করবেন কেন? Compiler Explorer-এ নিজে যাচাই করা যায়। কয়েকটি পরীক্ষা:
- একটি
std::vector<int>-এর যোগফল একবার range-for দিয়ে, একবার ইনডেক্স দিয়ে, একবারstd::accumulateদিয়ে লিখুন।-O2-এ তিনটির assembly সাধারণত প্রায় একই হয় — বিমূর্ত রূপের জন্য বাড়তি দাম নেই। - একটি ছোট struct-এর getter ফাংশন
-O2-এ পুরোপুরি মিলিয়ে যায় — inline হয়ে সরাসরি মেমরি থেকে পড়া। - কিন্তু
-O0-এ একই কোডে প্রতিটি ফাংশন ডাক আলাদাcallহয়ে থাকে। তাই “C++ ধীর” এমন মাপজোখ প্রায়ই আসলে optimization ছাড়া মাপা।
তবে সতর্ক থাকুন — নীতিটি “সব কিছু বিনা খরচে” নয়। virtual ফাংশন ডাকে একটি পরোক্ষ লাফ থাকে, exception-এর জন্য বাড়তি টেবিল লাগে, আর -O0 বা debug build-এ বিমূর্ততার খরচ স্পষ্টই চোখে পড়ে। নীতিটির আসল অর্থ: ফিচারটি যে কাজ করে, সেই কাজ হাতে করতে গেলেও অন্তত ততটাই খরচ হতো। Compiler Explorer এই দাবিগুলো যাচাই করার আয়না — অনুমানের বদলে প্রমাণ।
এই অভ্যাস পারফরম্যান্সের কাজেও লাগে (পর্ব ৪৯) — “এই কোড কি দ্রুত?” প্রশ্নের উত্তর তর্কে নয়, assembly আর মাপজোখে।
মূল উপলব্ধি: আধুনিক C++-এর পরিষ্কার, উঁচু-স্তরের কোড লেখা মানে গতি ছেড়ে দেওয়া নয় — optimizer-কে কাজ করতে দিলে সে বিমূর্ততার খোলস খুলে ফেলে। আর কোনো দাবি সন্দেহ হলে, assembly দেখে নিজেই যাচাই করুন।
অনুশীলন
বেশিরভাগ কাজে কোড লিখতে হবে না — চালিয়ে দেখা, পড়া আর ভাবাই আসল কাজ।
-
Header-এর ওজন মাপুন: দুটি ফাইল বানান — একটিতে শুধু
#include <cstdio>আর ফাঁকাmain, অন্যটিতে#include <iostream>. প্রতিটির জন্যg++ -E file.cpp | wc -lচালান (বা Compiler Explorer-এ-Eদিন)। কোনটিতে কত লাইন, আর কেন পার্থক্য?<iostream>-এর ফাইল অনেক গুণ বড় হবে — সাধারণত কয়েক হাজার থেকে কয়েক দশ হাজার লাইন, compiler-এর version অনুযায়ী। কারণ<cstdio>মূলত C-রprintfজাতীয় ফাংশনের ঘোষণা আনে, আর<iostream>আনে stream-এর পুরো template-কাঠামো, যা নিজে<string>, locale ও আরও অনেক header টানে। এজন্য একটি header যোগ করাও বড় প্রজেক্টে build সময়ে প্রভাব ফেলে। -
Error শিকারি: নিচের প্রতিটি মেসেজ কোন ধাপের, আর সম্ভাব্য কারণ কী — (ক)
fatal error: wallet.h: No such file or directory(খ)error: 'cout' is not a member of 'std'(গ)undefined reference to `deposit(int, int)'(ঘ)g++: error: /EHsc: No such file or directory(ক) Preprocess — header পাওয়া যায়নি; ফাইলের নাম, বানান বা ফোল্ডার ভুল (বা
-Iদিয়ে ফোল্ডার বলা হয়নি)।(খ) Compile —
#include <iostream>দেওয়া হয়নি; g++ সাধারণত একটি note-এ নিজেই এই পরামর্শ দেয়।(গ) Link —
depositঘোষিত হয়েছে কিন্তু সংজ্ঞা link-এ পৌঁছায়নি:wallet.cppকমান্ডে নেই, বা সংজ্ঞার parameter টাইপ ঘোষণার সাথে মেলে না।(ঘ) কমান্ডের ভুল —
/EHscহলো MSVC-র flag; g++ এটাকে একটি ফাইলের নাম ভেবেছে। AI প্রায়ই এমন মিশ্রণ করে। -
Standard বদলে দেখুন: অংশ ৪-এর
which_standard.cppনিজের কম্পিউটারে বা Compiler Explorer-এ তিনবার চালান —-std=c++17,-std=c++20আর কোনো-stdছাড়া। আউটপুট লিখে রাখুন।-stdছাড়া যা এলো, তা থেকে কী বুঝলেন?-std=c++17-এ201703,-std=c++20-এ202002.-stdছাড়া যা আসে, সেটাই আপনার compiler-এর ডিফল্ট standard — অনেক GCC version-এ C++17 (201703), তবে version ভেদে আলাদা হতে পারে। শিক্ষা: ডিফল্টের ওপর ভরসা না করে সবসময়-std=স্পষ্ট লিখুন, যাতে অন্য কম্পিউটারেও একই আচরণ পান। -
AI অডিট: যেকোনো AI-কে বলুন: “Windows-এ main.cpp, wallet.cpp আর wallet.h দিয়ে তৈরি একটি C++20 প্রজেক্ট কীভাবে compile করব?” উত্তরটি অংশ ৯-এর চেকলিস্ট দিয়ে যাচাই করুন। কী কী সমস্যা পেলেন?
প্রায়ই যা দেখা যায়:
- AI জিজ্ঞেস না করেই ধরে নেয় আপনার কাছে MSVC, নাকি MinGW — অথচ দুটির কমান্ড একদম আলাদা।
- কখনো
g++ main.cpp wallet.hলেখে, অর্থাৎ header-কেও compile করতে দেয়। -std=c++20বাদ পড়ে যায়, বা-Wall -Wextraথাকে না।- পুরোনো Code::Blocks-এর মেনু ধরে নির্দেশনা দেয়, যা আপনার সংস্করণে নেই।
ভালো উত্তর আগে আপনার compiler জানতে চাইবে, তারপর এক ধরনের flag দিয়েই সম্পূর্ণ কমান্ড দেবে। না দিলে উপরের prompt কাঠামো দিয়ে আবার জিজ্ঞেস করুন।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমএকটি C++ প্রোগ্রাম তৈরির ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- ✍️
.cppআর.hফাইলে কোড লেখা - 📦 Preprocess —
#include-এর লেখা এনে translation unit বানানো - 📐 Compile — translation unit থেকে assembly
- ✂️ Assemble — assembly থেকে object file (
.o) - 🧵 Link — সব
.oআর libstdc++ জুড়ে executable
g++ দিয়ে build করার সময় undefined reference to `deposit(int, int)' মেসেজটি কোন ধাপ থেকে আসে?
- Preprocessor
- Compiler
- Linker
- প্রোগ্রাম চলার সময়
g++-এর কমান্ডে -std=c++23 লেখার কাজ কী?
- ২০২৩ সালের compiler ডাউনলোড করে নেয়
- কোন C++ standard মেনে compile হবে, তা ঠিক করে দেয়
- সব warning চালু করে
- প্রোগ্রামকে দ্রুত করার optimization চালু করে
C++-এ “translation unit” বলতে কী বোঝায়?
- একটি .cpp ফাইল আর preprocess-এর পর তাতে ঢোকা সব header মিলে যে লেখা
- শুধু একটি .h ফাইল
- Link শেষে তৈরি .exe ফাইল
- কোডকে ইংরেজি থেকে বাংলায় অনুবাদের টুল
g++-এর object file-এ int add(int, int) ফাংশনের নাম _Z3addii হয়ে যায় কেন?
- কোড চুরি ঠেকাতে নাম গোপন করা হয়
- ফাইলের আকার কমাতে নাম সংক্ষেপ করা হয়
- Compiler-এর একটি বাগের কারণে
- Overload করা একই নামের ফাংশনগুলো আলাদা করতে নামের ভেতরে টাইপ লেখা হয় (name mangling)
একটি template-সংক্রান্ত C++ error-এ কয়েক ডজন লাইন এসেছে। প্রথমে কী দেখা উচিত?
- একদম শেষ লাইন
- প্রথম “error:” লাইন আর নিজের ফাইলের যে লাইন থেকে ডাকটি এসেছে
- Standard library-র header ফাইল খুলে ঠিক করা
- -w flag দিয়ে মেসেজ লুকিয়ে ফেলা
g++-এর কোন কমান্ড একটি ফাইলকে শুধু object file পর্যন্ত compile করে, link করে না?
- g++ -E wallet.cpp
- g++ -S wallet.cpp
- g++ -c wallet.cpp
- g++ wallet.cpp -o wallet
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- C++ build-এর ধাপ: preprocess → প্রতিটি
.cppআলাদা compile → link;g++link-এর সময় নিজেই libstdc++ জোড়ে। - ভারী header আর template-এর কারণে C++ compile-এ বেশি সময় লাগে; প্রতিটি translation unit একই header নতুন করে পড়ে।
- প্রতিদিনের কমান্ড:
g++ -std=c++23 -Wall -Wextra -g; চূড়ান্ত প্রোগ্রামে-O2; লক্ষ্য শূন্য warning. - Compiler-এর দরকার ঘোষণা, linker-এর দরকার সংজ্ঞা; না পেলে
undefined reference. নামগুলো mangle হয় overload আলাদা রাখতে। - Error-এ আগে ধাপ চিনুন, তারপর প্রথম error ঠিক করুন; AI-এর build কমান্ডে
-stdআর flag-এর ধরন যাচাই করুন।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ০৪ · প্রথম প্রোগ্রাম — main, iostream আর namespaceপরবর্তী পর্বপ্রথম C++ প্রোগ্রামের প্রতিটি লাইনের অর্থ ও উদ্দেশ্য।
- C++ প্রোগ্রামিং · কোর্স হোম৫৫টি পর্বপুরো কোর্সের রোডম্যাপ — কোন পর্ব কখন।
- C প্রোগ্রামিং · পর্ব ০৩ · টুলচেইনগভীরে যানচার ধাপের বিস্তারিত, static ও dynamic linking, আর debugger হাতে-কলমে।
- Programming Languages & Compiler Designপাশের ট্র্যাকCompiler-এর ভেতরে parsing, IR আর code generation কীভাবে কাজ করে।