[C] Jak dobrze zorganizować kod?


(Manonim93) #1

Cześć, mam problem z organizacją kodu i zasadami jak to robić, większość tutoriali w internecie omawia podstawy tego zagadnienia. Ja potrzebuję dowiedzieć się jak podzielić kod na mniejsze pliki za pomocą dyrektywy include, i jak to pliki powinny się do siebie odnosić.

Mam np. program który wykorzystuje GTK+, mam więc funkcje:

  • budujące okna, przyciski itp.

  • funkcje przypięte odpowiadające na sygnały

  • funkcje realizujące zadania programu

Obecnie program podzielony jest na trzy pliki:

program.h - prototypy funkcji i struktur

program.c - ciała funkcji

#include"GNUmoku.h"

main.c - pętla main stąd kompiluję

#include"GNUmoku.h"

#include"GNUmoku.c"

A chciałbym mieć trzy pliki *.c np. gui.c, logika.c, sygnaly.c i czy do każdego musi być odpowiednik w postaci *.h? Ponadto chciałbym nie includować wszystkiego w main tylko jeszcze stworzyć dodatkowy nagłówek gdzie zaincluduje wszystkie pliki i ten jeden plik dam do maina. Jak porozmieszczać include żeby to wszystko łączyło się w jedną kupę i działało? Ponadto gdzie deklarować najlepiej zmienne globalne?


(Wojtekbogocki) #2

Zasada prosta i sprawdzająca się w większości przypadków (w przypadku czystego C zamiast na klasy dzielisz na kategorie np. video.h):

  • deklaracja każdej klasy i jej metod w jednym osobnym pliku np. MyClass.h

  • definicja metod każdej klasy w pliku odpowiadającym nazwą plikowi z daną klasą np. MyClass.cpp

  • includy, dane i ewentualnie funkcje potrzebne globalnie trzymamy w oddzielnym pliku np. stdinc.h/global.h

  • plików *.c/cpp się nie załącza do jasnej ****

To tyle. I hope it helped :slight_smile:


(Manonim93) #3

Podzieliłem kod tak:

gui.h

gui.c

konsola.h

konsola.c

teraz w pliku biblioteki.h

includuje gui.h i konsola.h

a w main.c

includuje biblioteki.h

Czy to poprawne? I jeżeli mam w pliku biblioteki.h zdefiniowana strukturę np:

struct POLE{

   int a;

   int b;

};

To czemu kompilator zgłasza błąd dla prototypu funkcji w pliku gui.h: warning: ‘struct POLE’ declared inside parameter list [enabled by default]

void button_clicked(GtkButton*, struct POLE*);

EDIT: Gdy nie miałem podziału na mniejsze biblioteki to te same funkcje się kompilowały


(etam) #4

Pomysł z plikiem nagłówkowym biblioteki.h moim zdaniem nie jest najlepszy.

Powinieneś zrobić tak:

globals.h zawiera struct POLE

w plikach gui.h i konsola.h jeżeli jest w którymś użyta stuct POLE, to powinno być #include "globals.h"

w main.c dołączasz gui.h i konsola.h

Dla ułatwienia na przyszłość: linijka #include "plik.h" powoduje wklejenie zawartości danego pliku w tym miejscu. Nagłówki trzeba dołączać tak, żeby przed wykorzystaniem funkcji, struktur, zmiennych globalnych, itp, znalazły się ich deklaracje. W twoim przypadku przed deklaracją button_clicked nie było deklaracji struct POLE.

Gdy to opanujesz, możesz się zainteresować używaniem Forward Declaration

I nie zapomnij o Include guard


(Manonim93) #5

Czy global.h powinno zawierać definicje struktur czy globalne zmienne strukturalne? Czy może oba?


(etam) #6

oba


(Manonim93) #7

Sytuacja wygląda tak (dla uproszczenia podaję jedną funkcję tylko):

main.c

#include 

#include 

#include "lib/gui.h"

int main(int argc, char* argv[])

{

	gtk_init(&argc, &argv);

        create_window();

        return 0;

}

gui.h

int create_window(void);

gui.c

#include "gui.h"


int create_window(void)

{

	Różnie rzeczy nie istotne teraz...

        return 0;

}

Gdy kompiluję plik main.c dostaje błąd linkera:

main.c:(.text+0x23): undefined reference to `create_window'

EDIT: pominąłem dla czytelności include guard w gui.h

EDIT2: A co jeżeli np funkcja create_window() korzysta z #include? To gdzie dać include w pliku gui.c?


(etam) #8

Pytanie jak kompilujesz? Powinno być tak:

$ gcc -c main.c `pkg-config gtk+-3.0 --cflags`

$ gcc -c gui.c `pkg-config gtk+-3.0 --cflags`

$ gcc main.o gui.o `pkg-config gtk+-3.0 --libs` -o main

Wyjaśnienie: dwie pierwsze komendy (ważna jest opcja "-c") kompilują pliki wejściowe do tzw. "object file", odpowiednio main.o i gui.o. Jest to nic innego jak przerobienie kodu do binarki. Ale w tym momencie ten kod nie nadaje się do uruchomienia. Ostatnia linijka to jest konsolidacja, zwana też z angielskiego linkowanie. W tym momencie pliki .o zostają połączone w plik wykonywalny oraz dołączane są informacje o wymaganych bibliotekach dynamicznych. Korzystałeś kiedyś z make? Spróbuj tego:

CC = gcc

CFLAGS = `pkg-config gtk+-3.0 --cflags`

LDLIBS = `pkg-config gtk+-3.0 --libs`

OBJS = gui.o main.o

EXE = main


all: $(EXE)

$(EXE): $(OBJS)


.PHONY: clean

clean: $(EXE)
rm -f $(EXE) $(OBJS)

Zapisz jako plik Makefile. Teraz wystarczy wpisać make i program zostanie automagicznie skompilowany.

Jeżeli nie znasz make, to zajrzyj na https://www.gnu.org/software/make/manual/make.html (na początek sam rozdział 2 wystarczy)

W każdym pliku .c, który korzysta z GTK, musi być #include.


(Manonim93) #9

Właśnie wcześniej kompilowałem źle teraz program się uruchamia. A czy includowanie GTK w plikach nagłówkowych poszczególnych części jest błędne?


(etam) #10

Nie. Includowanie nagłówków nie może być błędne. Może co najwyżej być nadmiarowe (hail include guards) lub niepotrzebne.

Przykład:

gui.h

#ifndef GUI_H

#define GUI_H


#include /* Musi być. Inaczej GtkWidget będzie niezdefiniowane */


GtkWidget* create_window(void);


#endif /* GUI_H */

gui.c

/* W tym miejscu można dołączyć gtk/gtk.h ale po co, skoro i tak będzie w gui.h? */

#include "gui.h"

/* W tym miejscu można dołączyć gtk/gtk.h ale po co, skoro i tak zostało w gui.h? */


GtkWidget* create_window(void) {

    GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

    gtk_window_set_title (GTK_WINDOW(window), "test");

    g_signal_connect(window, "destroy", G_CALLBACK (gtk_main_quit), NULL);

    return window;

}

main.c

#include /* Tutaj podobnie jak w gui.c nie jest to konieczne. */

#include "gui.h"


int main(int argc, char *argv[]) {

    GtkWidget *window;

    gtk_init (&argc, &argv);

    window = create_window();

    gtk_widget_show(window);

    gtk_main();

    return 0;

}

(Manonim93) #11

No właśnie czyli zadeklarowanie gtk.h raz w poszczególnych nagłówkach jest ok.