17-bo‘lim

Preprotsessor va ko'p faylli loyiha

define, makroslar va ularning tuzoqlari, shartli kompilyatsiya, sarlavha fayllari, include qo'riqchisi va Makefile.

🕑 18 daqiqa o‘qish 📄 776 so‘z 👁 3 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Preprotsessor nima qiladi?
  2. #define - oddiy almashtirish
  3. Makroslar - argumentli almashtirish
  4. Foydali makroslar
  5. Shartli kompilyatsiya
  6. Loyihani fayllarga bo'lish
  7. talaba.h - sarlavha fayli
  8. talaba.c - amalga oshirish
  9. main.c - dastur boshi
  10. Kompilyatsiya
  11. include qo'riqchisi
  12. Sarlavhaga nima yoziladi?
  13. static - faylga xos
  14. Makefile
  15. Loyiha tuzilishi
  16. Xulosa

Katta dasturni bitta faylda yozib bo'lmaydi. Bu bo'limda loyihani fayllarga bo'lishni o'rganamiz.

Preprotsessor nima qiladi? #

Preprotsessor - kompilyatsiyadan oldingi qadam dastur.c #include, #define Preprotsessor matnni almashtiradi Sof C kodi kompilyatorga beriladi Preprotsessor C tilini bilmaydi - u faqat matn bilan ishlaydi gcc -E dastur.c natijani ko'rish uchun
Barcha # bilan boshlanadigan qatorlar shu bosqichda ishlanadi

#define - oddiy almashtirish #

C
#include <stdio.h>

#define PI 3.14159
#define MAKS_TALABA 50
#define SALOM "Assalomu alaykum"

int main(void) {
    printf("%s!\n", SALOM);
    printf("PI = %.5f\n", PI);
    printf("Maksimal talaba: %d\n", MAKS_TALABA);
    printf("Aylana uzunligi: %.2f\n", 2 * PI * 5.0);

    return 0;
}
Natija
Assalomu alaykum!
PI = 3.14159
Maksimal talaba: 50
Aylana uzunligi: 31.42

Makroslar - argumentli almashtirish #

C
#include <stdio.h>

#define KVADRAT(x) ((x) * (x))
#define MAKS(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b)  ((a) < (b) ? (a) : (b))

int main(void) {
    printf("KVADRAT(5)      = %d\n", KVADRAT(5));
    printf("KVADRAT(2 + 3)  = %d\n", KVADRAT(2 + 3));
    printf("MAKS(10, 25)    = %d\n", MAKS(10, 25));
    printf("MIN(10, 25)     = %d\n", MIN(10, 25));

    return 0;
}
Natija
KVADRAT(5)      = 25
KVADRAT(2 + 3)  = 25
MAKS(10, 25)    = 25
MIN(10, 25)     = 10
Qavslarsiz makros - kutilmagan natija
C
#define KVADRAT_YOMON(x) x * x

KVADRAT_YOMON(2 + 3)

Preprotsessor buni matn sifatida almashtiradi:

C
2 + 3 * 2 + 32 + 6 + 3  =  11

25 emas, 11!

Shuning uchun makrosda har bir argumentni va butun ifodani qavsga oling:

C
#define KVADRAT(x) ((x) * (x))
Makrosga yon ta'sirli ifoda bermang
C
#define KVADRAT(x) ((x) * (x))

int i = 5;
int n = KVADRAT(i++);      /* ((i++) * (i++)) - i ikki marta oshadi! */

Bu aniqlanmagan xatti-harakat. Makros argumentni ikki marta hisoblaydi.

Zamonaviy C da bunday holatlarda makros o'rniga static inline funksiya yozing:

C
static inline int kvadrat(int x) { return x * x; }

U turni tekshiradi, argumentni bir marta hisoblaydi va nosozliklarni tuzatishda ko'rinadi.

Foydali makroslar #

C
#include <stdio.h>

#define ELEMENTLAR(m) (sizeof(m) / sizeof((m)[0]))

#define TEKSHIR(shart) \
    do { \
        if (!(shart)) { \
            fprintf(stderr, "Tekshiruv buzildi: %s (%s:%d)\n", \
                    #shart, __FILE__, __LINE__); \
        } \
    } while (0)

int main(void) {
    int ballar[] = {85, 92, 78, 95, 88};

    printf("Elementlar soni: %zu\n", ELEMENTLAR(ballar));

    int yosh = 25;
    TEKSHIR(yosh > 18);
    TEKSHIR(yosh > 100);

    return 0;
}
Natija
Elementlar soni: 5
Tekshiruv buzildi: yosh > 100 (makros.c:22)

Qator raqami sizning faylingizda boshqacha bo'ladi - __LINE__ aynan shu qatorning raqamini beradi. Xabar stderr ga yozilgani uchun uni alohida faylga yo'naltirish ham mumkin.

Makrosdagi maxsus belgilar
BelgiNima qiladi
\Makrosni keyingi qatorga davom ettiradi
#xArgumentni satrga aylantiradi
a##bIkki nomni birlashtiradi
__FILE__Joriy fayl nomi
__LINE__Joriy qator raqami
__DATE__Kompilyatsiya sanasi
__func__Joriy funksiya nomi

do { ... } while (0) naqshi ko'p qatorli makroslar uchun standart yechim - u makrosni bitta buyruqdek tutadi va if ichida to'g'ri ishlaydi.

Shartli kompilyatsiya #

C
#include <stdio.h>

#define NOSOZLIK 1

int main(void) {
#if NOSOZLIK
    printf("[nosozlik] Dastur boshlandi\n");
#endif

    printf("Asosiy ish bajarilmoqda\n");

#ifdef _WIN32
    printf("Windows tizimi\n");
#elif defined(__linux__)
    printf("Linux tizimi\n");
#elif defined(__APPLE__)
    printf("macOS tizimi\n");
#else
    printf("Noma'lum tizim\n");
#endif

#if NOSOZLIK
    printf("[nosozlik] Dastur tugadi\n");
#endif

    return 0;
}
Natija
[nosozlik] Dastur boshlandi
Asosiy ish bajarilmoqda
Linux tizimi
[nosozlik] Dastur tugadi
Nosozlik xabarlarini o'chirish
C
#ifdef NOSOZLIK
    #define JURNAL(...) fprintf(stderr, __VA_ARGS__)
#else
    #define JURNAL(...) ((void) 0)
#endif

JURNAL("Qiymat: %d\n", son);

Ishlab chiqishda:

Terminal
gcc -DNOSOZLIK dastur.c -o dastur

Ishlab chiqarishda -DNOSOZLIK siz kompilyatsiya qilinsa, barcha jurnal qatorlari butunlay yo'qoladi - hech qanday tezlik yo'qotilmaydi.

Loyihani fayllarga bo'lish #

Ko'p faylli loyihaning yig'ilishi talaba.h e'lonlar talaba.c amalga oshirish main.c dastur boshi #include talaba.o main.o dastur ishga tayyor Har bir .c fayl alohida kompilyatsiya qilinadi, keyin bog'lanadi
Faqat o'zgargan fayl qayta kompilyatsiya qilinadi - bu vaqtni tejaydi

talaba.h - sarlavha fayli #

C
#ifndef TALABA_H
#define TALABA_H

#define MAKS_ISM 50

typedef struct {
    char ism[MAKS_ISM];
    int yosh;
    double baho;
} Talaba;

/* Yangi talaba yaratadi. */
Talaba talaba_yarat(const char *ism, int yosh, double baho);

/* Talabani ekranga chiqaradi. */
void talaba_chiqar(const Talaba *t);

/* Guruh o'rtacha bahosini hisoblaydi. Guruh bo'sh bo'lsa 0 qaytaradi. */
double guruh_ortachasi(const Talaba guruh[], int soni);

#endif /* TALABA_H */

talaba.c - amalga oshirish #

C
#include <stdio.h>
#include <string.h>
#include "talaba.h"

Talaba talaba_yarat(const char *ism, int yosh, double baho) {
    Talaba yangi;

    strncpy(yangi.ism, ism, MAKS_ISM - 1);
    yangi.ism[MAKS_ISM - 1] = '\0';
    yangi.yosh = yosh;
    yangi.baho = baho;

    return yangi;
}

void talaba_chiqar(const Talaba *t) {
    printf("%-20s %3d %6.2f\n", t->ism, t->yosh, t->baho);
}

double guruh_ortachasi(const Talaba guruh[], int soni) {
    if (soni <= 0) {
        return 0.0;
    }

    double yigindi = 0.0;

    for (int i = 0; i < soni; i++) {
        yigindi += guruh[i].baho;
    }

    return yigindi / soni;
}

main.c - dastur boshi #

C
#include <stdio.h>
#include "talaba.h"

int main(void) {
    Talaba guruh[3];

    guruh[0] = talaba_yarat("Husanboy Qodirov", 25, 4.60);
    guruh[1] = talaba_yarat("Malika Yusupova", 21, 4.90);
    guruh[2] = talaba_yarat("Nodira Sobirova", 24, 4.30);

    printf("%-20s %3s %6s\n", "Talaba", "Yosh", "Baho");
    printf("-------------------------------\n");

    for (int i = 0; i < 3; i++) {
        talaba_chiqar(&guruh[i]);
    }

    printf("-------------------------------\n");
    printf("O'rtacha: %.2f\n", guruh_ortachasi(guruh, 3));

    return 0;
}

Kompilyatsiya #

Terminal
gcc -std=c17 -Wall -Wextra -c talaba.c -o talaba.o
gcc -std=c17 -Wall -Wextra -c main.c   -o main.o
gcc talaba.o main.o -o dastur
./dastur
Natija
Talaba               Yosh   Baho
-------------------------------
Husanboy Qodirov      25   4.60
Malika Yusupova       21   4.90
Nodira Sobirova       24   4.30
-------------------------------
O'rtacha: 4.60

Yoki bir buyruqda:

Terminal
gcc -std=c17 -Wall -Wextra talaba.c main.c -o dastur

include qo'riqchisi #

C
#ifndef TALABA_H
#define TALABA_H

/* ... e'lonlar ... */

#endif
Qo'riqchisiz nima bo'ladi?

Ikkita fayl bir xil sarlavhani qo'shsa, uning mazmuni ikki marta kiritiladi:

Natija
error: redefinition of 'struct Talaba'

#ifndef qo'riqchisi buni to'xtatadi: birinchi kiritishda TALABA_H aniqlanadi, ikkinchisida esa butun blok o'tkazib yuboriladi.

Har bir sarlavha faylida qo'riqchi bo'lishi shart.

Zamonaviy kompilyatorlar #pragma once ni ham qo'llab-quvvatlaydi - u qisqaroq, lekin standart emas:

C
#pragma once

Sarlavhaga nima yoziladi? #

Sarlavha va manba fayli mazmuni
Sarlavhaga (.h)Manbaga (.c)
Funksiya prototiplariFunksiya tanasi
typedef va struct e'lonlariMahalliy yordamchi funksiyalar
#define doimiylaristatic o'zgaruvchilar
extern o'zgaruvchilarGlobal o'zgaruvchi ta'rifi

Sarlavhaga hech qachon quyidagilarni yozmang:

  • funksiya tanasi (static inline dan tashqari);
  • global o'zgaruvchi ta'rifi (faqat extern e'loni);
  • using yoki keraksiz #include lar.

static - faylga xos #

C
/* talaba.c ichida */

static int hisoblagich = 0;          /* faqat shu faylda ko'rinadi */

static void ichki_yordamchi(void) {  /* faqat shu faylda chaqiriladi */
    hisoblagich++;
}
static ni faol ishlating

Sarlavhada e'lon qilinmagan har bir funksiya va global o'zgaruvchini static qiling.

Foydasi:

  • boshqa fayllar bilan nom to'qnashuvi bo'lmaydi;
  • kompilyator ularni yaxshiroq optimallashtiradi;
  • kod o'quvchisiga «bu faqat ichki» deb aytadi.

Makefile #

MAKEFILE
CC = gcc
CFLAGS = -std=c17 -Wall -Wextra -g
MAQSAD = dastur
MANBALAR = main.c talaba.c
OBYEKTLAR = $(MANBALAR:.c=.o)

all: $(MAQSAD)

$(MAQSAD): $(OBYEKTLAR)
    $(CC) $(OBYEKTLAR) -o $@

%.o: %.c
    $(CC) $(CFLAGS) -c $< -o $@

main.o: main.c talaba.h
talaba.o: talaba.c talaba.h

tozala:
    rm -f $(OBYEKTLAR) $(MAQSAD)

ishga: $(MAQSAD)
    ./$(MAQSAD)

.PHONY: all tozala ishga
Terminal
make            # yig'adi
make ishga      # yig'adi va ishga tushiradi
make tozala     # oraliq fayllarni o'chiradi
Makefile da TAB majburiy

Buyruq qatorlari tabulyatsiya bilan boshlanishi kerak - probel bilan emas. Bu Makefile ning eng ko'p uchraydigan xatosi:

Natija
Makefile:8: *** missing separator.  Stop.

Tahrirchingizda Makefile uchun tabni probelga almashtirishni o'chiring.

make nima uchun tez?

make fayl sanalarini solishtiradi. Agar talaba.c o'zgarmagan bo'lsa, talaba.o qayta yaratilmaydi.

Yuzlab fayldan iborat loyihada bitta faylni o'zgartirsangiz, faqat o'sha bittasi qayta kompilyatsiya qilinadi. Bu daqiqalarni soniyalarga aylantiradi.

Shuning uchun bog'liqliklarni to'g'ri yozish muhim:

MAKEFILE
main.o: main.c talaba.h

Bu qator «talaba.h o'zgarsa, main.o ham qayta yig'ilsin» degani.

Loyiha tuzilishi #

Natija
loyiha/
├── Makefile
├── src/
│   ├── main.c
│   ├── talaba.c
│   └── hisobot.c
├── include/
│   ├── talaba.h
│   └── hisobot.h
├── tests/
│   └── talaba_test.c
└── build/
    └── (yig'ilgan fayllar)
Terminal
gcc -std=c17 -Wall -Iinclude src/*.c -o build/dastur

-Iinclude bayrog'i kompilyatorga sarlavhalarni qayerdan qidirishni aytadi.

Amaliy topshiriq
  1. #define bilan bir nechta doimiy yarating.
  2. KVADRAT makrosini qavssiz yozib, KVADRAT(2+3) natijasini ko'ring.
  3. Qavs qo'shib, natija to'g'rilanishini tasdiqlang.
  4. ELEMENTLAR makrosini yozib, massivda sinang.
  5. __FILE__ va __LINE__ ishlatuvchi jurnal makrosi yozing.
  6. #ifdef bilan nosozlik xabarlarini yoqing va o'chiring.
  7. Loyihani talaba.h, talaba.c va main.c ga bo'ling.
  8. include qo'riqchisini olib tashlab, xatoni ko'ring.
  9. Makefile yozing va make, make tozala ni sinang.
  10. Bitta faylni o'zgartirib, make faqat o'shani qayta yig'ishini kuzating.

Xulosa #

  • Preprotsessor matn bilan ishlaydi - u C tilini bilmaydi.
  • #define oddiy almashtirish qiladi; makrosda har bir argumentni qavsga oling.
  • Makros argumentni bir necha marta hisoblashi mumkin - yon ta'sirli ifoda bermang.
  • Zamonaviy C da murakkab makros o'rniga static inline funksiya afzal.
  • #ifdef bilan shartli kompilyatsiya - platformaga qarab kod tanlash.
  • Loyiha .h (e'lonlar) va .c (amalga oshirish) fayllariga bo'linadi.
  • Har bir sarlavhada #ifndef qo'riqchisi bo'lishi shart.
  • Faqat ichki funksiyalarni static qiling.
  • Makefile faqat o'zgargan fayllarni qayta yig'adi.
  • Makefile da buyruqlar tabulyatsiya bilan boshlanadi.

Keyingi bo'limda standart kutubxonaning eng foydali qismlarini ko'ramiz.

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.