2-bo‘lim

Muhitni sozlash va kompilyatsiya

Kompilyator o'rnatish, to'rt bosqichli kompilyatsiya, muhim bayroqlar va sanitayzerlar.

🕑 20 daqiqa o‘qish 📄 1 255 so‘z 👁 4 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Kompilyator o'rnatish
  2. Kompilyatsiya - to'rt bosqich
  3. Muhim bayroqlar
  4. Bayroqlar to'plami
  5. Optimallashtirish darajasi
  6. Sanitayzerlar
  7. Bir necha fayldan iborat loyiha
  8. Bog'lovchi xatosi
  9. Oddiy Makefile
  10. Nosozlik qidirish
  11. Xulosa

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 #

TizimBuyruq
Ubuntu, Debiansudo apt install g++ gdb
Fedorasudo dnf install gcc-c++ gdb
Archsudo pacman -S gcc gdb
macOSxcode-select --install (clang++)
WindowsWSL, MSYS2/MinGW yoki Visual Studio

Windows foydalanuvchilariga tavsiya - WSL: shunda Linux muhitida ishlaysiz va darslikdagi barcha buyruqlar to'g'ridan-to'g'ri ishlaydi.

Terminal
export LC_ALL=C
Terminal
# 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") }'
Natija
g++ versiyasi: 15.2
C++23 uchun yetarlimi: ha
Versiya talablari

Ushbu darslik C++23 dan foydalanadi. Kerakli minimal versiyalar:

KompilyatorMinimalstd::print uchun
GCC1214+
Clang1618+ (libc++ bilan)
MSVC19.3419.40+

std::println mavjud bo'lmasa, uni almashtirish oson:

KOD
// <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 #

Terminal
cat > salom.cpp <<'EOF'
#include <print>

int main()
{
    std::println("Salom, dunyo!");
    return 0;
}
EOF
Terminal
# 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
Natija
1. preprotsessordan keyin: 65808 qator
2. assembler kodi:         39798 qator
3. obyekt fayl:            464448 bayt
4. bajariladigan fayl:     244312 bayt
Salom, dunyo!
Olti qatorlik dastur 65 000 qatorga aylandi

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 beradiIzoh
Faqat kerakli sarlavhalarni qo'shish<bits/stdc++.h> ishlatmang
Oldindan kompilyatsiya qilingan sarlavhalar-include bilan
Sarlavhada #include ni kamaytirishOldindan 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 #

Terminal
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: /  /'
Natija
--- 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 qilmang

Bayroqsiz kompilyator jim turdi. Ikkita haqiqiy muammo e'tiborsiz qoldi:

MuammoNima uchun xavfli
Ishlatilmagan o'zgaruvchiKo'pincha yozuv xatosi belgisi
int va unsigned solishtirishManfiy son katta musbatga aylanadi

Ikkinchisini ko'rsatamiz:

C++
#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;
}
Natija
-1 < 1 ?  true
solishtirish o'zgartirilgach: false
chunki -1 unsigned da: 4294967295
-1 < 1 noto'g'ri javob berdi

int 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:

KOD
for (int i = 0; i < vektor.size(); ++i)   // size() unsigned!

To'g'ri variantlar:

YechimIzoh
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 #

Terminal
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
Natija
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 #

Terminal
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)"
Natija
-O0  hajm:  247624 bayt
-O2  hajm:  131016 bayt
natija ikkalasida bir xil:
  -O0: 500000500000
  -O2: 500000500000
Ishlab chiqish va reliz - ikki xil qurish
Ishlab chiqishReliz
Optimallashtirish-O0 yoki -Og-O2
Nosozlik ma'lumoti-gBa'zan -g (alohida faylga)
SanitayzerlarYoqilganO'chirilgan
assertIshlaydi-DNDEBUG bilan o'chadi
Kompilyatsiya tezligiTezSekin
Ish tezligiSekin (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.

Manba kodidan bajariladigan faylgacha salom.cpp 6 qator -E salom.i ~65 000 qator -S salom.s assembler -c salom.o mashina kodi ld salom bajariladigan Har bosqichda qanday xatolar chiqadi Preprotsessor: fayl topilmadi · Kompilyator: sintaksis, tur mosligi · Bog'lovchi: undefined reference Oxirgisi eng chalkash: kod to'g'ri, lekin funksiyaning tanasi topilmadi. Ishlab chiqish uchun -std=c++23 -Wall -Wextra -Wpedantic -g -fsanitize=address,undefined Sekin ishlaydi, lekin xatolarni ishga tushirish paytida topadi Reliz uchun -std=c++23 -Wall -Wextra -O2 -DNDEBUG Tez ishlaydi, lekin xatolar jimgina o'tib ketishi mumkin
Ikki xil qurish - ikki xil maqsad; ikkalasi ham kerak

Sanitayzerlar #

Terminal
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/'
Natija
--- 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
Birinchi holatda dastur hech narsa demadi

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 #

Terminal
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 ..
Terminal
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
Natija
yigindi: 438
ortacha: 87.60
Nima uchun sarlavha va manba ajratiladi
FaylNima borKim ko'radi
hisob.hppE'lonlar - funksiya imzosiHar bir foydalanuvchi
hisob.cppTa'riflar - funksiya tanasiFaqat 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:

KOD
#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 #

Terminal
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"
Natija
kompilyatsiya: OK
boglashda 0 ta 'undefined reference' xatosi
boglash: YIQILDI
undefined reference - eng chalkash xato

Bu 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:

SababYechim
.cpp fayl bog'lashga qo'shilmaganUni ham g++ ga bering
Kutubxona ulanmagan-lpthread, -lm va h.k.
Funksiya tanasi yozilmaganYozing
Imzo mos kelmaydiconst, parametr turlarini solishtiring
C kutubxonasi C++ dan chaqirilganextern "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 #

Terminal
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"
Natija
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)
Makefile da tabulyatsiya majburiy

Yuqoridagi faylda buyruq qatorlari tabulyatsiya bilan boshlanadi, bo'shliq bilan emas. Bu make ning eski va noqulay talabi.

Xato bo'lsa shunday xabar chiqadi:

KOD
Makefile:6: *** missing separator.  Stop.

Katta loyihalarda make o'rniga CMake ishlatiladi:

VositaQachon
Qo'lda g++1-3 fayl, o'rganish
MakefileKichik loyiha, Unix
CMakeJiddiy loyiha, ko'p platforma
Meson, BazelKatta tashkilotlar

Ushbu darslikda misollar bitta fayldan iborat, shuning uchun qurish tizimi kerak emas.

Nosozlik qidirish #

Terminal
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)"
Natija
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
UsulChegara tekshiriladimiXato bo'lsa
v[10]Yo'qAniqlanmagan xulq
v.at(10)Hastd::out_of_range istisnosi

[] tezroq, lekin xato jimgina o'tadi. .at() sekinroq (bir taqqoslash), lekin xatoni darhol ko'rsatadi.

Amaliy tavsiya:

VaziyatTanlov
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).

Amaliy topshiriq
  1. Kompilyatoringiz versiyasini tekshiring.
  2. "Salom" dasturini to'rt bosqichda alohida kompilyatsiya qiling.
  3. Preprotsessordan keyin nechta qator chiqqanini yozing.
  4. -Wall -Wextra siz va bilan kompilyatsiya qilib solishtiring.
  5. int va unsigned solishtirish xatosini takrorlang.
  6. -O0 va -O2 bilan qurilgan fayl hajmlarini solishtiring.
  7. UBSan bilan butun son toshishini aniqlang.
  8. Ikki .cpp va bitta .hpp dan iborat loyiha yarating.
  9. Ataylab undefined reference xatosini chiqaring.
  10. v[10] va v.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 -Wextra siz kompilyatsiya qilmang - ular xatolarning katta qismini topadi.
  • int va unsigned solishtirish 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 once sarlavhaning 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.

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.