2-bo‘lim
Muhitni sozlash va kompilyatsiya
Kompilyator o'rnatish, to'rt bosqichli kompilyatsiya, muhim bayroqlar va sanitayzerlar.
Ushbu bo‘lim mundarijasi
C++ - kompilyatsiya qilinadigan til. Kod ishga tushishidan oldin mashina kodiga aylantiriladi.
Bu bo'limda kompilyatorni sozlaymiz va uning bayroqlarini o'rganamiz. Bayroqlar mavzusi zerikarli ko'rinadi, lekin ular xatolarning yarmini o'zi topib beradi.
Kompilyator o'rnatish #
| Tizim | Buyruq |
|---|---|
| Ubuntu, Debian | sudo apt install g++ gdb |
| Fedora | sudo dnf install gcc-c++ gdb |
| Arch | sudo pacman -S gcc gdb |
| macOS | xcode-select --install (clang++) |
| Windows | WSL, MSYS2/MinGW yoki Visual Studio |
Windows foydalanuvchilariga tavsiya - WSL: shunda Linux muhitida ishlaysiz va darslikdagi barcha buyruqlar to'g'ridan-to'g'ri ishlaydi.
export LC_ALL=C
# Faqat versiya raqami - distributiv nomi kerak emas
g++ -dumpfullversion 2>/dev/null | awk -F. '{ printf "g++ versiyasi: %s.%s\n", $1, $2 }'
g++ -dumpversion | awk -F. '{ print "C++23 uchun yetarlimi:", ($1 >= 14 ? "ha" : "yoq") }'
g++ versiyasi: 15.2
C++23 uchun yetarlimi: ha
Ushbu darslik C++23 dan foydalanadi. Kerakli minimal versiyalar:
| Kompilyator | Minimal | std::print uchun |
|---|---|---|
| GCC | 12 | 14+ |
| Clang | 16 | 18+ (libc++ bilan) |
| MSVC | 19.34 | 19.40+ |
std::println mavjud bo'lmasa, uni almashtirish oson:
// <print> o'rniga
#include <format>
#include <iostream>
template <typename... Args>
void println(std::format_string<Args...> f, Args&&... args) {
std::cout << std::format(f, std::forward<Args>(args)...) << '\n';
}
Bu darslikdagi barcha misollar shu bilan ham ishlaydi.
Kompilyatsiya - to'rt bosqich #
cat > salom.cpp <<'EOF'
#include <print>
int main()
{
std::println("Salom, dunyo!");
return 0;
}
EOF
# 1. Preprotsessor: #include larni ochadi, makroslarni almashtiradi
g++ -std=c++23 -E salom.cpp -o salom.i
printf "1. preprotsessordan keyin: %s qator\n" "$(wc -l < salom.i)"
# 2. Kompilyator: assembler kodiga aylantiradi
g++ -std=c++23 -S salom.cpp -o salom.s
printf "2. assembler kodi: %s qator\n" "$(wc -l < salom.s)"
# 3. Assembler: obyekt fayl (mashina kodi, hali bog'lanmagan)
g++ -std=c++23 -c salom.cpp -o salom.o
printf "3. obyekt fayl: %s bayt\n" "$(stat -c%s salom.o)"
# 4. Bog'lovchi (linker): kutubxonalar bilan birlashtiradi
g++ salom.o -o salom
printf "4. bajariladigan fayl: %s bayt\n" "$(stat -c%s salom)"
./salom
1. preprotsessordan keyin: 65808 qator
2. assembler kodi: 39798 qator
3. obyekt fayl: 464448 bayt
4. bajariladigan fayl: 244312 bayt
Salom, dunyo!
Birinchi raqamga e'tibor bering. Sabab - #include <print>
o'nlab boshqa sarlavhalarni o'ziga tortadi.
Aynan shu C++ ning sekin kompilyatsiya qilinishiga asosiy sabab.
| Nima yordam beradi | Izoh |
|---|---|
| Faqat kerakli sarlavhalarni qo'shish | <bits/stdc++.h> ishlatmang |
| Oldindan kompilyatsiya qilingan sarlavhalar | -include bilan |
Sarlavhada #include ni kamaytirish | Oldindan e'lon qilish |
| Modullar (C++20) | Kelajakdagi yechim, hali beqaror |
Aniq raqamlar sizning kompilyatoringizda boshqacha bo'ladi - muhimi tartib: o'n minglab qator.
Muhim bayroqlar #
cat > ogoh.cpp <<'EOF'
#include <print>
int main()
{
int ishlatilmagan;
int a = 5;
unsigned int b = 3;
if (a > b) { // int va unsigned solishtirish
std::println("a katta");
}
return 0;
}
EOF
echo "--- bayroqsiz:"
g++ -std=c++23 -o ogoh ogoh.cpp 2>&1 | grep -c . | xargs printf " %s ta xabar\n"
echo "--- -Wall -Wextra bilan:"
g++ -std=c++23 -Wall -Wextra -o ogoh ogoh.cpp 2>&1 \
| grep "warning:" | sed 's/^.*warning: / /'
--- bayroqsiz:
0 ta xabar
--- -Wall -Wextra bilan:
comparison of integer expressions of different signedness: 'int' and 'unsigned int' [-Wsign-compare]
unused variable 'ishlatilmagan' [-Wunused-variable]
-Wall -Wextra siz kompilyatsiya qilmangBayroqsiz kompilyator jim turdi. Ikkita haqiqiy muammo e'tiborsiz qoldi:
| Muammo | Nima uchun xavfli |
|---|---|
| Ishlatilmagan o'zgaruvchi | Ko'pincha yozuv xatosi belgisi |
int va unsigned solishtirish | Manfiy son katta musbatga aylanadi |
Ikkinchisini ko'rsatamiz:
#include <print>
int main()
{
int manfiy = -1;
unsigned int musbat = 1;
std::println("-1 < 1 ? {}", manfiy < static_cast<int>(musbat));
std::println("solishtirish o'zgartirilgach: {}",
manfiy < musbat);
std::println("chunki -1 unsigned da: {}",
static_cast<unsigned int>(manfiy));
return 0;
}
-1 < 1 ? true
solishtirish o'zgartirilgach: false
chunki -1 unsigned da: 4294967295
-1 < 1 noto'g'ri javob berdiint va unsigned int solishtirilganda, int
unsigned ga aylantiriladi. -1 esa unsigned da
4 294 967 295 bo'ladi.
Natijada -1 < 1 ifodasi false beradi.
Bu C++ dagi eng ko'p uchraydigan mantiqiy xatolardan biri va u odatda halqalarda paydo bo'ladi:
for (int i = 0; i < vektor.size(); ++i) // size() unsigned!
To'g'ri variantlar:
| Yechim | Izoh |
|---|---|
for (std::size_t i = 0; ...) | Turlar mos |
for (int x : vektor) | Indeks umuman yo'q |
std::ssize(vektor) (C++20) | Belgili qiymat qaytaradi |
Ikkinchisi eng yaxshi - u indeks bilan bog'liq barcha xatolarni yo'q qiladi.
-Wall -Wextra bu holatni ogohlantirish bilan aytadi.
Shuning uchun ular majburiy.
Bayroqlar to'plami #
cat > bayroq.cpp <<'EOF'
#include <print>
int main() { std::println("ishladi"); return 0; }
EOF
printf "%-34s %s\n" "BAYROQ" "NIMA QILADI"
printf "%-34s %s\n" "----------------------------------" "------------------------"
printf "%-34s %s\n" "-std=c++23" "til standarti"
printf "%-34s %s\n" "-Wall -Wextra" "ogohlantirishlar"
printf "%-34s %s\n" "-Wpedantic" "standartdan chetlanish"
printf "%-34s %s\n" "-Werror" "ogohlantirish = xato"
printf "%-34s %s\n" "-O0 / -O2 / -O3" "optimallashtirish darajasi"
printf "%-34s %s\n" "-g" "nosozlik qidirish malumoti"
printf "%-34s %s\n" "-fsanitize=address" "xotira xatolarini topadi"
printf "%-34s %s\n" "-fsanitize=undefined" "aniqlanmagan xulqni topadi"
echo
echo "Kundalik ish uchun:"
echo " g++ -std=c++23 -Wall -Wextra -g -fsanitize=address,undefined"
echo "Reliz uchun:"
echo " g++ -std=c++23 -Wall -Wextra -O2 -DNDEBUG"
g++ -std=c++23 -Wall -Wextra -O2 -o bayroq bayroq.cpp && ./bayroq
BAYROQ NIMA QILADI
---------------------------------- ------------------------
-std=c++23 til standarti
-Wall -Wextra ogohlantirishlar
-Wpedantic standartdan chetlanish
-Werror ogohlantirish = xato
-O0 / -O2 / -O3 optimallashtirish darajasi
-g nosozlik qidirish malumoti
-fsanitize=address xotira xatolarini topadi
-fsanitize=undefined aniqlanmagan xulqni topadi
Kundalik ish uchun:
g++ -std=c++23 -Wall -Wextra -g -fsanitize=address,undefined
Reliz uchun:
g++ -std=c++23 -Wall -Wextra -O2 -DNDEBUG
ishladi
Optimallashtirish darajasi #
cat > tez.cpp <<'EOF'
#include <print>
#include <vector>
#include <numeric>
int main()
{
std::vector<int> v(1000000);
std::iota(v.begin(), v.end(), 1);
long long yigindi = 0;
for (int x : v) {
yigindi += x;
}
std::println("{}", yigindi);
return 0;
}
EOF
for daraja in O0 O2; do
g++ -std=c++23 -$daraja -o tez_$daraja tez.cpp
printf "%-4s hajm: %7s bayt\n" "-$daraja" "$(stat -c%s tez_$daraja)"
done
echo "natija ikkalasida bir xil:"
printf " -O0: %s\n" "$(./tez_O0)"
printf " -O2: %s\n" "$(./tez_O2)"
-O0 hajm: 247624 bayt
-O2 hajm: 131016 bayt
natija ikkalasida bir xil:
-O0: 500000500000
-O2: 500000500000
| Ishlab chiqish | Reliz | |
|---|---|---|
| Optimallashtirish | -O0 yoki -Og | -O2 |
| Nosozlik ma'lumoti | -g | Ba'zan -g (alohida faylga) |
| Sanitayzerlar | Yoqilgan | O'chirilgan |
assert | Ishlaydi | -DNDEBUG bilan o'chadi |
| Kompilyatsiya tezligi | Tez | Sekin |
| Ish tezligi | Sekin (5-20 marta) | Tez |
Sanitayzerlar dasturni 2-3 marta sekinlashtiradi va xotirani ko'p ishlatadi. Shuning uchun ular relizga qo'shilmaydi.
Lekin ishlab chiqish va testlarda ular doim yoqilgan bo'lishi kerak - ular topadigan xatolarni boshqa hech narsa topmaydi.
Sanitayzerlar #
cat > xato.cpp <<'EOF'
#include <print>
#include <climits>
int main()
{
int a = INT_MAX;
int b = a + 1; // butun son toshib ketishi - aniqlanmagan xulq
std::println("{}", b);
return 0;
}
EOF
echo "--- sanitayzersiz (jimgina noto'g'ri natija):"
g++ -std=c++23 -O0 -o xato xato.cpp && ./xato
echo "--- UBSan bilan:"
g++ -std=c++23 -fsanitize=undefined -g -o xato_ub xato.cpp
./xato_ub 2>&1 | sed 's/^.*xato\.cpp/xato.cpp/'
--- sanitayzersiz (jimgina noto'g'ri natija):
-2147483648
--- UBSan bilan:
xato.cpp:7:15: runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type 'int'
-2147483648
INT_MAX + 1 - bu aniqlanmagan xulq-atvor
(undefined behaviour). Standart bo'yicha kompilyator
xohlagan narsani qilishi mumkin.
Amalda u jimgina noto'g'ri natija berdi. Bu eng yomon variant: dastur ishlayotgandek ko'rinadi.
UBSan esa aniq aytdi: qaysi faylda, qaysi qatorda, nima bo'ldi.
Aniqlanmagan xulq-atvor 19-bo'limning to'liq mavzusi. Hozir esa bitta qoidani eslab qoling:
Ishlab chiqish va testlarda sanitayzerlarni doim yoqing.
Ular topadigan xatolarni na kompilyator, na testlar, na kod ko'rib chiqish topadi.
Bir necha fayldan iborat loyiha #
mkdir -p loyiha && cd loyiha
cat > hisob.hpp <<'EOF'
#pragma once // sarlavha bir marta qo'shilishini kafolatlaydi
#include <vector>
// E'LON - tanasi yo'q
int yigindi(const std::vector<int>& sonlar);
double ortacha(const std::vector<int>& sonlar);
EOF
cat > hisob.cpp <<'EOF'
#include "hisob.hpp"
#include <numeric>
// TA'RIF - tanasi shu yerda
int yigindi(const std::vector<int>& sonlar)
{
return std::accumulate(sonlar.begin(), sonlar.end(), 0);
}
double ortacha(const std::vector<int>& sonlar)
{
if (sonlar.empty()) {
return 0.0;
}
return static_cast<double>(yigindi(sonlar)) / sonlar.size();
}
EOF
cat > main.cpp <<'EOF'
#include "hisob.hpp"
#include <print>
int main()
{
std::vector<int> baholar{85, 92, 78, 95, 88};
std::println("yigindi: {}", yigindi(baholar));
std::println("ortacha: {:.2f}", ortacha(baholar));
return 0;
}
EOF
cd ..
cd loyiha
# Har faylni alohida kompilyatsiya qilamiz
g++ -std=c++23 -Wall -Wextra -c hisob.cpp -o hisob.o
g++ -std=c++23 -Wall -Wextra -c main.cpp -o main.o
# Keyin bog'laymiz
g++ hisob.o main.o -o dastur
./dastur
yigindi: 438
ortacha: 87.60
| Fayl | Nima bor | Kim ko'radi |
|---|---|---|
hisob.hpp | E'lonlar - funksiya imzosi | Har bir foydalanuvchi |
hisob.cpp | Ta'riflar - funksiya tanasi | Faqat kompilyator |
Foydasi: hisob.cpp o'zgarsa, main.cpp qayta
kompilyatsiya qilinmaydi - faqat qayta bog'lanadi.
Katta loyihada bu farq minutlar bilan o'lchanadi.
#pragma once - sarlavha bir necha marta qo'shilib
qolishining oldini oladi. Eski muqobil:
#ifndef HISOB_HPP
#define HISOB_HPP
...
#endif
#pragma once standartda yo'q, lekin barcha jiddiy
kompilyatorlar uni qo'llab-quvvatlaydi va u qisqaroq.
Bog'lovchi xatosi #
cd loyiha
cat > yomon.cpp <<'EOF'
#include "hisob.hpp"
#include <print>
int koproq(int x); // e'lon qilindi, lekin hech qayerda TA'RIFLANMAGAN
int main()
{
std::println("{}", koproq(5));
return 0;
}
EOF
g++ -std=c++23 -c yomon.cpp -o yomon.o && echo "kompilyatsiya: OK"
g++ yomon.o hisob.o -o yomon 2>&1 | grep -c "undefined reference" \
| xargs printf "boglashda %s ta 'undefined reference' xatosi\n"
echo "boglash: YIQILDI"
kompilyatsiya: OK
boglashda 0 ta 'undefined reference' xatosi
boglash: YIQILDI
undefined reference - eng chalkash xatoBu xato kompilyatsiyada emas, bog'lashda chiqadi. Kompilyator e'lonni ko'rgani uchun rozi bo'ldi - tanani esa bog'lovchi qidirdi va topmadi.
Eng ko'p sabablari:
| Sabab | Yechim |
|---|---|
.cpp fayl bog'lashga qo'shilmagan | Uni ham g++ ga bering |
| Kutubxona ulanmagan | -lpthread, -lm va h.k. |
| Funksiya tanasi yozilmagan | Yozing |
| Imzo mos kelmaydi | const, parametr turlarini solishtiring |
| C kutubxonasi C++ dan chaqirilgan | extern "C" kerak |
To'rtinchi qator nozik: C++ da funksiya nomi imzosi bilan
birga kodlanadi (name mangling). int f(int) va
int f(double) - ikki boshqa belgi.
Oddiy Makefile #
cd loyiha
cat > Makefile <<'EOF'
CXX = g++
CXXFLAGS = -std=c++23 -Wall -Wextra -g
OBJS = main.o hisob.o
dastur: $(OBJS)
$(CXX) $(OBJS) -o dastur
%.o: %.cpp hisob.hpp
$(CXX) $(CXXFLAGS) -c $< -o $@
.PHONY: clean
clean:
rm -f $(OBJS) dastur
EOF
make clean >/dev/null 2>&1
make 2>&1 | sed 's/-o [a-z.]*$//'
./dastur
echo "--- faqat main.cpp ozgardi:"
touch main.cpp
make 2>&1 | grep -c "g++" | xargs printf " %s ta buyruq bajarildi (hammasi emas)\n"
g++ -std=c++23 -Wall -Wextra -g -c main.cpp
g++ -std=c++23 -Wall -Wextra -g -c hisob.cpp
g++ main.o hisob.o
yigindi: 438
ortacha: 87.60
--- faqat main.cpp ozgardi:
2 ta buyruq bajarildi (hammasi emas)
Yuqoridagi faylda buyruq qatorlari tabulyatsiya bilan
boshlanadi, bo'shliq bilan emas. Bu make ning eski va
noqulay talabi.
Xato bo'lsa shunday xabar chiqadi:
Makefile:6: *** missing separator. Stop.
Katta loyihalarda make o'rniga CMake ishlatiladi:
| Vosita | Qachon |
|---|---|
Qo'lda g++ | 1-3 fayl, o'rganish |
Makefile | Kichik loyiha, Unix |
| CMake | Jiddiy loyiha, ko'p platforma |
| Meson, Bazel | Katta tashkilotlar |
Ushbu darslikda misollar bitta fayldan iborat, shuning uchun qurish tizimi kerak emas.
Nosozlik qidirish #
cat > xato2.cpp <<'EOF'
#include <print>
#include <vector>
int main()
{
std::vector<int> v{1, 2, 3};
std::println("{}", v.at(10)); // chegaradan tashqari
return 0;
}
EOF
g++ -std=c++23 -g -o xato2 xato2.cpp
# Ichki qobiq: signal haqidagi xabar tashqariga chiqmasin
kod=$(bash -c './xato2 > chiqish.txt 2>&1; echo $?' 2>/dev/null)
head -2 chiqish.txt
echo "chiqish kodi: $kod (0 emas - dastur yiqildi)"
terminate called after throwing an instance of 'std::out_of_range'
what(): vector::_M_range_check: __n (which is 10) >= this->size() (which is 3)
chiqish kodi: 134 (0 emas - dastur yiqildi)
.at() va [] farqi| Usul | Chegara tekshiriladimi | Xato bo'lsa |
|---|---|---|
v[10] | Yo'q | Aniqlanmagan xulq |
v.at(10) | Ha | std::out_of_range istisnosi |
[] tezroq, lekin xato jimgina o'tadi. .at() sekinroq
(bir taqqoslash), lekin xatoni darhol ko'rsatadi.
Amaliy tavsiya:
| Vaziyat | Tanlov |
|---|---|
| Indeks ichki hisobdan | [] |
| Indeks tashqaridan (foydalanuvchi, fayl) | .at() |
| Ishlab chiqish bosqichi | .at() yoki ASan |
| Isbotlangan tezlik muhim joy | [] |
Eng yaxshisi esa - indeksni umuman ishlatmaslik (8-bo'lim).
- Kompilyatoringiz versiyasini tekshiring.
- "Salom" dasturini to'rt bosqichda alohida kompilyatsiya qiling.
- Preprotsessordan keyin nechta qator chiqqanini yozing.
-Wall -Wextrasiz va bilan kompilyatsiya qilib solishtiring.intvaunsignedsolishtirish xatosini takrorlang.-O0va-O2bilan qurilgan fayl hajmlarini solishtiring.- UBSan bilan butun son toshishini aniqlang.
- Ikki
.cppva bitta.hppdan iborat loyiha yarating. - Ataylab
undefined referencexatosini chiqaring. v[10]vav.at(10)farqini o'z mashinangizda ko'ring.
Xulosa #
- C++ kompilyatsiya qilinadi: preprotsessor → kompilyator → assembler → bog'lovchi.
- Olti qatorlik dastur preprotsessordan keyin o'n minglab qator bo'ladi.
-Wall -Wextrasiz kompilyatsiya qilmang - ular xatolarning katta qismini topadi.intvaunsignedsolishtirish jimgina noto'g'ri natija beradi.- Ishlab chiqish uchun:
-g -fsanitize=address,undefined; reliz uchun:-O2 -DNDEBUG. - Sanitayzerlar boshqa hech narsa topa olmaydigan xatolarni topadi.
- Sarlavha (
.hpp) e'lonlarni, manba (.cpp) ta'riflarni saqlaydi. #pragma oncesarlavhaning takror qo'shilishini oldini oladi.undefined reference- bog'lash bosqichidagi xato, kompilyatsiya emas.v[10]chegarani tekshirmaydi,v.at(10)esa istisno tashlaydi.
Keyingi bo'limda turlar va o'zgaruvchilar bilan tanishamiz.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
Xatolik topdingizmi?
Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.