Poprawne dzielenie programu

0

Witam

Dostałem od wykładowcy bojowe zadanie napisania jakiejś tam gry, ale niestety od razu pojawił się problem jak to podzielić?

Zgodnie z zaleceniami wykminiłem, że w głównym pliku z rozszerzeniem .cpp będzie główny kod programu, w pliku funkcje.h deklaracje zmiennych globalnych oraz funkcji, zaś w pliku funkcje.cpp definicje funkcji.
Niestety, nie moge nic zrobic bo za cholere nie chce sie to skompilować i wyskakuje błąd że "redefinition" albo że niby gdzieś tam zdeklarowane już itd. Próbowałem to zrobić za pomocą dyrektyw preprocesora #ifndef i #define , ale nie udało sie.

Ktoś ma jakiś pomysł?

Poniżej kod:

#include <stdio.h>
#include "Funkcje.cpp"
#include"definicje.h"
int main (){

	printf("Zdanie testowe :)\n");

	wypplan();

}
#include <stdio.h>

char plansza [20][20];
void wypplan();

#include"definicje.h"
#include<stdio.h>
void wypplan(){

	for(int i=0;i<2;i++){
		for(int j=0;j<2;j++){
		printf("%c", plansza[i][j]);
		
		}
		printf("\n");
	}

}
0

Przede wszystkim zmienne globalne to jeden z najfatalniejszych pomysłów. Ale jeśli już musisz się ich trzymać, to nie w nagłówku, bo przy każdym #include są definiowane, w związku z czym w każdej jednostce kompilacji znajduje się taka sama zmienna globalna i linker nie wie którą z nich wybrać.

Rozwiązanie to deklaracja zmiennych w nagłówku a definicja w pliku .cpp:

// header.h
#ifndef HEDAER_H_INCLUDED
#define HEADER_H_INCLUDED

extern int variable;

#endif

// header.cpp
int variable = 5;

EDIT Dopiero przyjrzałem się twojemu kodowi. Plików .cpp się nie includuje, tylko pliki nagłówkowe. W twoim przypadku powinno to wyglądać tak:

// main.cpp
#include <stdio.h>
#include "definicje.h"

int main() {
    printf("Zdanie testowe :)\n");
    wypplan();
}
// koniec main.cpp

// definicje.h
#ifndef DEFINICJE_H_INCLUDED
#define DEFINICJE_H_INCLUDED

extern char plansza[20][20];
void wypplan();

#endif
// koniec definicje.h

// definicje.cpp
#include <stdio.h>
#include "definicje.h"

char plansza[20][20];

void wypplan() {
    for (int i = 0; i < 20; i++) {
        for (int j = 0; j < 2; j++) {
            printf("%c", plansza[i][j]);
        }
        printf("\n");
    }
}
// koniec definicje.cpp
0

W sumie podejście prawidłowe poza zmiennymi globalnymi, ich nie należy stosować jeśli nie trzeba.
Reasumując:

 // funkcje.hpp - definicje funkcji
void func0();
int func1(int***** lol, int num_lol);
// funkcje.cpp - kod funkcji
void func0() { blablabla; }
int func1(int***** lol, int num_lol) { blablabla; }
// main.cpp - main
#include <ceplusplus>
#include "funkcje.hpp" // żeby main znał funkcje z funkcje.cpp

int main()
{
  func0();
blablabla...
}

Załóżmy, żeś się uparł i chcesz mieć te zmienne globalne - normalnie robisz je w pliku CPP (tylko jednym) main.cpp lub funkcje.cpp

// funkcje.cpp lub main.cpp
float global_variable; // normalnie deklarujesz zmienną globalną i ona istnieje tylko w tym pliku CPP

Ale chcesz jej używać w innym pliku CPP? Nie powinieneś, ale jeśliś się uparł to musisz użyć słówka extern

// inny plik CPP niż ten w którym zmienne istnieje
extern float global_variable; // ta zmienne jest z innego pliku CPP
0

Trochę mi się w tej chwili rozjaśniło, ale ciągle nie do końca rozumiem:

  1. Po co są w ogóle pliki nagłówkowe?
  2. Jak się dzieli kod, programując tego typu aplikacje? Na wykładzie nakazali mi jakoś tak to dzielić, ale czy nie byłoby w zgodzie ze "sztuką" załączenie wszystkiego w jednym pliku main.cpp?
0
aaquillus napisał(a):
  1. Po co są w ogóle pliki nagłówkowe?

Pozwalają na współdzielenie danych między jednostkami kompilacji. Ogólnie temat jest dość złożony, ale postaram się nieco przybliżyć działanie kompilacji i linkowania (w sporym uproszczeniu).

Na początek, czym różni się deklaracja od definicji.

  1. Deklaracja to określenie, że gdzieś w programie znajduje się taka zmienna bądź funkcja. Jej wartość jest nieznana, po prostu wiemy, że gdzieś jest.
  2. Definicja to zdefiniowanie w danym miejscu danej funkcji bądź zmiennej. Wszelkie odwołania do zmiennej piszą do tego konkretnego miejsca w pamięci. Wszelkie odwołania do funkcji będą wykonywały waśnie ten kod.
int foo(int x); //< to jest deklaracja funkcji – gdzies oczekujemy definicji
int foo(int x) { return x*x; } //< to jest definicja – ten kod bedzie wywolany przy kazdym wywolaniu tej funkcji gdzies w programie

Teraz fazy kompilacji:

Kompilacja
Kompilowane są osobno pliki .cpp jako tzw. jednostki kompilacji. GCC tworzy pliki .o, VC++ .obj. Pierwszą fazą kompilacji jest wykonanie dyrektyw preprocesora, m.in. #include. Ta dyrektywa to nic innego jak wklejenie tekstu z nagłówka w miejsce dyrektywy.

Następnie odbywa się właściwa kompilacja. Definicje funkcji i zmiennych globalnych są „wystawione” na zewnątrz jednostki kompilacji dla fazy linkowania całego programu. Jeżeli dana jednostka korzysta z deklaracji funkcji, to w miejsce jej wywołania wstawiany jest odwołanie do odpowiedniej definicji obiektu (funkcji lub zmiennej – od tej pory będę pisał obiekt mając na myśli funkcję bądź zmienną globalną). Odwołanie jest po prostu nieco „upiększoną” nazwą tego obiektu (tzw. name mangling).

Linkowanie
Kolejnym krokiem jest linkowanie, czyli składanie programu do kupy. Linker skanuje wszystkie jednostki kompilacji w poszukiwaniu znaczników (odwołań). Jeśli taki znajdzie, to skanuje wszystkie jednostki w poszukiwaniu definicji takiego obiektu i wstawia go w miejsce znacznika.

I tutaj właśnie napotkałeś swój problem, bo w dwóch jednostkach kompilacji miałeś definicję obiektu o takim samym identyfikatorze. W takim wypadku linker sygnalizuje błąd redefinicji, bo nie wie, której definicji ma użyć. Może się też zdarzyć błąd braku definicji, jeśli odwołujesz się do obiektu, który jest tylko zadeklarowany, ale nie ma nigdzie definicji.

Po przydługim wstępie właściwa odpowiedź na postawione pytanie: pliki nagłówkowe zawierają deklaracje funkcji i zmiennych globalnych. Bez deklaracji niemożliwe jest skompilowanie jednostki kompilacji, bo muszą zostać powstawiane odwołania do definicji. Takie odwołania są zamieniane na prawdziwe wywołania funkcji i zmiennych na etapie linkowania.

aaquillus napisał(a):
  1. Jak się dzieli kod, programując tego typu aplikacje? Na wykładzie nakazali mi jakoś tak to dzielić, ale czy nie byłoby w zgodzie ze "sztuką" załączenie wszystkiego w jednym pliku main.cpp?

Dzieli się tak, że w każdej jednostce kompilacji definiuje się zmienne globalne i funkcje odpowiadające jakiemuś logicznemu podziałowi programu. Na przykład funkcje operujące na tekście – taka mini-biblioteka – może zostać wydzielona do osobnego pliku .cpp. Natomiast w odpowiadającym mu pliku nagłówkowym wystawia się deklaracje zdefiniowanych w nim funkcji i zmiennych tak, aby inne jednostki kompilacji mogły powstawiać odpowiednie odwołania dla linkera.

Łączenie wszystkiego w jeden plik .cpp jest czymś zupełnie łamiącym „sztukę”. Wyobrażasz sobie jakiś większy program w jednym pliku? Ostatni projekt w C++ nad jakim pracowałem miał około 500000 (pół miliona) linii kodu. Taki plik miałby ~40 MB czystego tekstu. Niemożliwym jest pracować nad taką kobyłą.

Poza tym podział na jednostki kompilacji pozwala przyspieszyć proces kompilacji. Jeśli nie było żadnych zmian w pliku .cpp to nie ma potrzeby ponowna kompilacja, można wykorzystać istniejący już plik obiektowy (.o albo .obj) do samego linkowania. Zmiana tylko w jednym pliku nie powoduje pełnej rekompilacji a tylko kompilację tego zmienionego pliku, reszta już jest gotowa i czeka na zlinkowanie.

0

Co jak co, ale tagi w tym temacie są po prostu genialne.

0

To się nazywa wyczerpująca odpowiedź :)

Jeżeli w pliku nagłówkowym deklaruję zmienną globalną, to czy odwołując się do niej w innym pliku .cpp muszę ją koniecznie zadeklarować jeszcze raz używając słowa extern?
Podobnie z funkcjami, jeżeli w nagłówku zadeklarowałem funkcję void wypplan(); , która nie ma żadnych argumentów i nie zwraca żadnych wartości, czy do tego też muszę dodać extern?
W moim pliku wygląda to tak, że w nagłówku mam powyższą deklarację, ale jak w pliku wpisuję wypplan(); , i definicję funkcji to mi wywala błąd. Muszę tam wpisać void wypplan(); , czy to aby nie jest też podwójna deklaracja tej samej funkcji?

0

Jeżeli w pliku nagłówkowym deklaruję zmienną globalną, to czy odwołując się do niej w innym pliku .cpp muszę ją koniecznie zadeklarować jeszcze raz używając słowa extern?

Nie, wystarczy, że includujesz ten plik nagłówkowy do pliku .cpp. Jak już napisałem, preprocesor praktycznie wkleja zawartość pliku nagłówkowego w miejsce dyrektywy #include. Wszędzie gdzie chcesz korzystać z tej zmiennej globalnej wystarczy include pliku nagłówkowego z deklaracją/deklaracjami.

Podobnie z funkcjami, jeżeli w nagłówku zadeklarowałem funkcję void wypplan(); , która nie ma żadnych argumentów i nie zwraca żadnych wartości, czy do tego też muszę dodać extern?

Funkcje nie potrzebują extern, po prostu jeśli funkcja nie ma ciała jest deklaracją.

W moim pliku wygląda to tak, że w nagłówku mam powyższą deklarację, ale jak w pliku wpisuję wypplan(); , i definicję funkcji to mi wywala błąd. Muszę tam wpisać void wypplan(); , czy to aby nie jest też podwójna deklaracja tej samej funkcji?

Deklaracji może być dowolnie wiele, ważne jest to, żeby była tylko jedna definicja.

Może mały przykład:
foo.h

/* deklaracja funkcji */
int foo(int);

/* deklaracja zmiennej globalnej */
extern int bar;

foo.cpp

#include "foo.h"

/* definicja funkcji */
int foo(int x) {
    return x*x;
}

/* definicja zmiennej globalnej */
int bar = 2;

main.cpp

#include <stdio.h>
#include "foo.h"

int main() {
    printf("Zmienna globalna: %d\n", bar);

    /* wywolanie funkcji */
    bar = foo(bar);

    printf("Zmienna po wywolaniu funkcji: %d\n", bar);

    return 0;
}
0

Załapane i zrobione. Dziękuję za pomoc!!

1 użytkowników online, w tym zalogowanych: 0, gości: 1