Domanda Risolto Come organizzo gli headers?

Stato
Discussione chiusa ad ulteriori risposte.

zouth

Utente Silver
16 Dicembre 2020
82
36
4
61
Ciao a tutti, avrei una domanda.
Come organizzo gli headers definiti da me e i rispettivi file .c all'interno di un progetto?
Grazie ciao.
 
Io ho sempre creato una cartella chiamata include, dove vado ad inserire i vari file headers.
Quindi ad esempio folder/src/*.c e folder/include/*.h. Se hai tanti file e li devi organizzare in base alla funzione che svolgono (magari alcuni si occupano di determinate operazioni e altre di altre ancora) puoi dare nomi esplicativi alla cartelle; del tipo che invece di "src" la chiamerai "pippo" o "pluto".
 
Si come dice dispatch, io (linux/gcc) utilizzo una struttura del tipo

Codice:
- src
- include
- bin
- obj
makefile

CFLAGS += -Iinclude

nel tuo progetto in genere is usa

#include <header_libreria.h>
#include "header_tuo.h"

negli header usa sempre gli include guard:

Codice:
#ifndef tuoinclude_h
#define touinclude_h

.....

#endif /* tuoinclude_h */
 
  • Mi piace
Reazioni: DispatchCode
Si come dice dispatch, io (linux/gcc) utilizzo una struttura del tipo

Codice:
- src
- include
- bin
- obj
makefile

CFLAGS += -Iinclude

nel tuo progetto in genere is usa

#include <header_libreria.h>
#include "header_tuo.h"

negli header usa sempre gli include guard:

Codice:
#ifndef tuoinclude_h
#define touinclude_h

.....

#endif /* tuoinclude_h */
Cos'è il make file?
 
Il makefile contiene delle ricette di compilaizone, serve a automatizzare il processo di compilazione. Dalla tua domanda immagino tu sia in windows ed utilizzando qualche IDE, tipo visual C / compilatore microsoft. In quel caso, dimentica pure il makefile, gli IDE fanno tutto per conto loro senza farti interessare di come compilano, anche se in genere utilizzano makefile a te invisibili, o sistemi similari. Se invece vuoi capire come si compila allora lascia perdere gli IDE e compila da riga di comando. PS se mio post ti e' stato utile lascia pure un like :), non ho bisogno di medaglie ma puo essere un modo per ringraziare.
 
  • Mi piace
Reazioni: zouth
Uso visual studio code, però mi sembra che comunque ci siano tipo tasks.json e lunch.json, sono simili al make file?
 
Uso visual studio code, però mi sembra che comunque ci siano tipo tasks.json e lunch.json, sono simili al make file?
Diciamo di si, a volte i task sono creati on the fly e poi rimossi, dipende dall'IDE.
Io lavoro in linux e compilo sempre da riga di comando, linux, gcc. Voglio definire esattamente opzini di compilazione, ottimizzazioni, etc, per quello uso i makefile (vedi gnu-make).

Anche in windows, microsoft ha volendo i suoi makefile, un tempo l'ide tipo visual C li generava lui, roba in cui non ho messo mai mano. Poi abbandonato VisaulStudio e sempre usato mingw, ma ormai sono esclusivamente su linux da oltre 20 anni.
 
Si come dice dispatch, io (linux/gcc) utilizzo una struttura del tipo

Codice:
- src
- include
- bin
- obj
makefile

CFLAGS += -Iinclude

nel tuo progetto in genere is usa

#include <header_libreria.h>
#include "header_tuo.h"

negli header usa sempre gli include guard:

Codice:
#ifndef tuoinclude_h
#define touinclude_h

.....

#endif /* tuoinclude_h */
Rieccomi, sono tornato in citta' e ho organizzato i files cosi:

Codice:
include:
    test.h
src:
    main.c
    test.c

test.h
C:
int sum(int, int);

test.c
C:
#include "include/test.h"

int sum(int x, int y) {
    return x + y;
}

main.c
C:
#include <stdio.h>
#include "include/test.h"

int main() {
    int x = sum(6, 6);
    printf("%d", x);
    
    return 0;
}

Ho provato a compilare ma non funziona:
gcc src/main.c src/test.c -o main

come mai?
 
Rieccomi, sono tornato in citta' e ho organizzato i files cosi:

Codice:
include:
    test.h
src:
    main.c
    test.c

test.h
C:
int sum(int, int);

test.c
C:
#include "include/test.h"

int sum(int x, int y) {
    return x + y;
}

main.c
C:
#include <stdio.h>
#include "include/test.h"

int main() {
    int x = sum(6, 6);
    printf("%d", x);
   
    return 0;
}

Ho provato a compilare ma non funziona:
gcc src/main.c src/test.c -o main

come mai?
prova a compilare solo src/main.c e poi compila src/test.c
 
prova a compilare solo src/main.c e poi compila src/test.c
Da errore a entrambi, ho quindi provato cosi:

#include "..\include\test.h"

invece di:

#include "include\test.h"

e mi da un errore diverso:

undefined reference to `sum' quando compilo main.c

undefined reference to `WinMain@16' quando compilo test.c
 
Ultima modifica:
Da errore a entrambi, ho quindi provato cosi:

#include "..\include\test.h"

invece di:

#include "include\test.h"

e mi da un errore diverso:

undefined reference to `sum' quando compilo main.c

undefined reference to `WinMain@16' quando compilo test.c
Devi generare i soli file oggetto di tutti i moduli con estensione *.c, a questo punto colleghi i file oggetto e generi l'eseguibile:
Codice:
gcc -c file1.c file2.c file3.c ecc..
Genera i file ogetto

gcc -o Nome eseguibile file1.o file2.o ecc.
Genera l'eseguibile
Non c'è bisogno di generare i file ogetto per gli header (estensioni .h), in quanto contengono le sole dichiarazioni delle funzioni e delle macro, non istruzioni.

PS: è importante far si che il modulo anche se incluso più volte non generi errori di "simboli ridefiniti", in quanto includendo più volte lo stesso modulo ridefinirai più volte le stesse macro e le stesse funzioni, per evitare ciò esegui dei controlli sulle macro definite dal modulo tramite le direttive preprocessore, in questo modo se una macro è stato già definita il modulo verrà incluso ma il uso contenuto sarà nullo.
C:
#ifndef MACRO_MODULO
#define MACRO_MODULO

//dichiarazioni

#endif
Così le varie macro e funzioni verrano definite solamente se la macro definita dal modulo non è stata ancora definita.
 
Devi generare i soli file oggetto di tutti i moduli con estensione *.c, a questo punto colleghi i file oggetto e generi l'eseguibile:
Codice:
gcc -c file1.c file2.c file3.c ecc..
Genera i file ogetto

gcc -o Nome eseguibile file1.o file2.o ecc.
Genera l'eseguibile
Non c'è bisogno di generare i file ogetto per gli header (estensioni .h), in quanto contengono le sole dichiarazioni delle funzioni e delle macro, non istruzioni.

PS: è importante far si che il modulo anche se incluso più volte non generi errori di "simboli ridefiniti", in quanto includendo più volte lo stesso modulo ridefinirai più volte le stesse macro e le stesse funzioni, per evitare ciò esegui dei controlli sulle macro definite dal modulo tramite le direttive preprocessore, in questo modo se una macro è stato già definita il modulo verrà incluso ma il uso contenuto sarà nullo.
Codice:
#ifndef MACRO_MODULO
#define MACRO_MODULO

//dichiarazioni

#endif
Così le varie macro e funzioni verrano definite solamente se la macro definita dal modulo non è stata ancora definita.
come dici tu mi funziona ma non credo di aver capito bene l'ultimo argomento:
C:
#ifndef MACRO_MODULO
#define MACRO_MODULO

//dichiarazioni

#endif
 
come dici tu mi funziona ma non credo di aver capito bene l'ultimo argomento:
C:
#ifndef MACRO_MODULO
#define MACRO_MODULO

//dichiarazioni

#endif

Si chiama "include guard". In buona sostanza quei file headers possono essere inclusi da più parti. Se due file includono il medesimo non avviene la compilazione in quanto quel file è già stato incluso e andresti a ridefinire il suo contenuto (macro piuttosto che dichiarazione di funzioni o strutture).
Con l'include guard il preprocessore guarda se esiste - nell'esempio li sopra - MACRO_MODULO e se non è stata definita, viene eseguito l'if, definendo quindi MACRO_MODULO.
Viceversa, se MACRO_MODULO è già stata definita, non entrerà in quell'if. Da notare che ifndef significa proprio "if not defined".

Ti è stato anche suggerito da kernelspace nel suo primo post.
 
  • Mi piace
Reazioni: mattstack e zouth
vedo che tai usando gcc, dunque puoi anche crearti un semplice makefile, e digitare semplicemente make

Codice:
# makefile non testato

CC=gcc

BIN=tuoprog

# percorsi relativi
SRCDIR=src
INCDIR=include
OBJDIR=obj
BINDIR=.

SRCS:=$(wildcard $(SRCDIR)/*.c)
OBJS:=$(patsubst %.c,%.o,$(SRCS))
OBJS:=$(patsubst $(SRCDIR)%,$(OBJDIR)%,$(OBJS))

# qui usi i C/LDFLAGS che vuoi
CFLAGS=-c -pipe -fomit-frame-pointer -Wall -O0 -ggdb -std=c99 -D_XOPEN_SOURCE=500
LDFLAGS=

.PHONY: all $(library)

all: $(BINDIR)/$(BIN)

$(BINDIR)/$(BIN): $(OBJS)
    $(CC) $(LDFLAGS) $(OBJS) -o $@

$(OBJDIR)/%.o: $(SRCDIR)/%.c | $(OBJDIR)
    $(CC) -I$(INCDIR) $(CFLAGS) $< -o $@

$(OBJDIR):
    mkdir -p $(OBJDIR)

clean:
    rm $(OBJS)
    rm $(BIN)
 
  • Mi piace
Reazioni: DispatchCode
Come avrai capito non c'è uno standard né una convenzione più diffusa di altre. L'importante è che quando scrivi un progetto mantieni uno stile consistente. Io non dividerei i .h dai .c se non c'è la necessità di distribuire solo l'interfaccia (e.g., se devi scrivere una libreria closed-source). Solitamente faccio una cosa di questo tipo
Codice:
.
├── bin
│   └── projectname
├── Makefile
├── README.md
├── projectname
│   ├── main.c
│   ├── foo.h
│   ├── foo.c
│   ├── something
│   │   ├── bar.h
│   │   └── bar.c
└── test
    ├── foo.c
    └── bar.h
Se voglio guardare o modificare un file .h realisticamente sono anche interessato al file .c ad esso associato, e vice versa, quindi li voglio a portata di mano. Le subdirectory (nell'esempio, something) le faccio quando ha senso raggruppare: se è un progetto c++ faccio un mapping uno ad uno con i namespaces e se è un progetto c può capitarmi di fare una cosa di questo tipo
Codice:
.
├── list
│   ├── vector.h
│   ├── vector.c
│   ├── array.h
│   ├── array.c
│   ├── linkedlist.c
│   └── linkedlist.h
└── ...

Da errore a entrambi, ho quindi provato cosi:

#include "..\include\test.h"
Quello che vuoi scrivere nel codice è più importante di dove vuoi posizionare le directory. Se metti i .h nella stessa directory dei .c ti basta un #include "test.h" secco. Altrimenti, se fai come ti hanno suggerito gli altri, ti conviene compilare con qualcosa del tipo gcc -I ./include ./src/pippo.c e andare di #include <test.h>.

In alternativa agli include guards segnalo anche il #pragma once che per progetti su un po' più grandi diventa comodo. Non è standard, ma è ampiamente diffuso e ti evita qualche problema: non è solo syntactic sugar, se hai due file con lo stesso nome (e.g., src/pippo.h e test/pippo.h) fa automaticamente la cosa giusta. Non ho una grande preferenza a riguardo, anche gli include guards non mi dispiacciono, ma visto che li abbiamo menzionati penso che valga la pena citare anche l'alternativa "moderna".

Anche quella roba di compilare in più object files e linkarli successivamente... overrated. Si riducono i tempi di compilazione, ma aumentano le pene. I makefiles li ho sempre odiati e quel che è peggio è che non c'è un'alternativa decente. Ce ne sono un migliaio di alternative e fanno tutte schifo. Pare che il cmake "moderno" (da distinguersi dal cmake per come lo si usava anni fa) sia una delle alternative migliori, soprattutto per progetti c++, ma non mi sono mai degnato di impararlo, quindi ho poco da consigliare. Quando chiamare direttamente il compilatore diventa tedioso uso i makefiles solo perché non devo imparare niente di nuovo... ma ne farei volentieri a meno.

Ho provato a compilare ma non funziona:
gcc src/main.c src/test.c -o main
come mai?
Non funziona... ma qual'è l'errore? Realisticamente dovresti risolvere leggendo la parte precedente del messaggio mio messaggio... o facendo quel che ti ha detto kernelspace.
 
Come avrai capito non c'è uno standard né una convenzione più diffusa di altre. L'importante è che quando scrivi un progetto mantieni uno stile consistente. Io non dividerei i .h dai .c se non c'è la necessità di distribuire solo l'interfaccia (e.g., se devi scrivere una libreria closed-source). Solitamente faccio una cosa di questo tipo
Codice:
.
├── bin
│   └── projectname
├── Makefile
├── README.md
├── projectname
│   ├── main.c
│   ├── foo.h
│   ├── foo.c
│   ├── something
│   │   ├── bar.h
│   │   └── bar.c
└── test
    ├── foo.c
    └── bar.h
Se voglio guardare o modificare un file .h realisticamente sono anche interessato al file .c ad esso associato, e vice versa, quindi li voglio a portata di mano. Le subdirectory (nell'esempio, something) le faccio quando ha senso raggruppare: se è un progetto c++ faccio un mapping uno ad uno con i namespaces e se è un progetto c può capitarmi di fare una cosa di questo tipo
Codice:
.
├── list
│   ├── vector.h
│   ├── vector.c
│   ├── array.h
│   ├── array.c
│   ├── linkedlist.c
│   └── linkedlist.h
└── ...


Quello che vuoi scrivere nel codice è più importante di dove vuoi posizionare le directory. Se metti i .h nella stessa directory dei .c ti basta un #include "test.h" secco. Altrimenti, se fai come ti hanno suggerito gli altri, ti conviene compilare con qualcosa del tipo gcc -I ./include ./src/pippo.c e andare di #include <test.h>.

In alternativa agli include guards segnalo anche il #pragma once che per progetti su un po' più grandi diventa comodo. Non è standard, ma è ampiamente diffuso e ti evita qualche problema: non è solo syntactic sugar, se hai due file con lo stesso nome (e.g., src/pippo.h e test/pippo.h) fa automaticamente la cosa giusta. Non ho una grande preferenza a riguardo, anche gli include guards non mi dispiacciono, ma visto che li abbiamo menzionati penso che valga la pena citare anche l'alternativa "moderna".

Anche quella roba di compilare in più object files e linkarli successivamente... overrated. Si riducono i tempi di compilazione, ma aumentano le pene. I makefiles li ho sempre odiati e quel che è peggio è che non c'è un'alternativa decente. Ce ne sono un migliaio di alternative e fanno tutte schifo. Pare che il cmake "moderno" (da distinguersi dal cmake per come lo si usava anni fa) sia una delle alternative migliori, soprattutto per progetti c++, ma non mi sono mai degnato di impararlo, quindi ho poco da consigliare. Quando chiamare direttamente il compilatore diventa tedioso uso i makefiles solo perché non devo imparare niente di nuovo... ma ne farei volentieri a meno.


Non funziona... ma qual'è l'errore? Realisticamente dovresti risolvere leggendo la parte precedente del messaggio mio messaggio... o facendo quel che ti ha detto kernelspace.
Penso che il tuo modo di organizzare i file sia piu' comodo per includere i file, anche perche' cosi' non mi da errori.
 
Utimamente converto tutti progetti importanti usando autotools, in questo modo la procedura di compliazione e' standard, cross-arch ove possibile, e i progetti sono pronti per essere pubblicati come opensource. Autotools sono cmq un po' complessi da digerire e non sono l'unico modo di roganizzare un progetto oggidi.

Per quel che mi riguarda, se progetto resta in casa tua, puo fare come meglio ti trovi. Altrimenti ti invito a esplorare modalita' di organizzazione standard che trovi nelle varie distribuzioni opensource, nel senso, partitre bene puo essere utile per il futuro .

  • autotools
  • CMake
  • Ninja Build. ...
  • SCons. ...
  • Meson. ...
  • Premake. ...
  • MakeMe. ...
  • Ant.
etc.

googlando un po tra sorgenti che usano sopracitate tecnologie troverai il modo piu comune di organizzare i file. Copiare ok, capendo pero'.
 
Ultima modifica:
Penso che il tuo modo di organizzare i file sia piu' comodo per includere i file, anche perche' cosi' non mi da errori.
Vabbé, ma vale la pena capire come funziona questo meccanismo visto che è anche abbastanza didattico.

Se vuoi ottenere questo
Codice:
.
├── bin
│   └── main
├── include
│   └── test.h
└── src
    ├── main.c
    └── test.c

Puoi fare questa cosa qui
C:
// main.c
#include <stdio.h>
#include "../include/test.h"

int main() {
    int x = sum(6, 6);
    printf("%d\n", x);
    return 0;
}


// test.h
#ifndef TEST_H_
#define TEST_H_

int sum(int, int);

#endif // TEST_H_


// test.c
#include "../include/test.h"

int sum(int x, int y) {
    return x + y;
}
e compilare con gcc -o bin/main src/test.c src/main.c. In alternativa, puoi fare questa cosa qui
C:
// main.c
#include <stdio.h>
#include <test.h>

int main() {
    int x = sum(6, 6);
    printf("%d\n", x);
    return 0;
}


// test.h
#ifndef TEST_H_
#define TEST_H_

int sum(int, int);

#endif // TEST_H_


// test.c
#include <test.h>

int sum(int x, int y) {
    return x + y;
}
e compilare con gcc -I./include -o bin/main src/test.c src/main.c.

In caso hai tanti files .c, può essere conveniente compilarli separatamente (in object files) e unirli in un unico eseguibile usando il linker. Il vantaggio è che se modifichi un solo file non devi ricompilare l'intero progetto. In questo caso la differenza è impercettibile perché un progetto di prova impiega pochi millisecondi a compilarsi, ma su roba più sostanziosa la differenza si sente. In questo caso devi compilare in questo modo
Codice:
gcc -I./include -c src/test.c -o obj/test.o
gcc -I./include -c src/main.c -o obj/main.o
gcc obj/main.o obj/test.o -o bin/main
e se modifichi main.c, non devi ricompilare anche test. Con un build automation tool (e.g., GNU Make) puoi automatizzare il processo di verificare quali file sono stati modificati, ricompilare solo quelli e generare l'eseguibile. Puoi anche automatizzare l'esecuzione dei test, dei benchmarks e quant'altro. L'esempio di un makefile lo trovi nel post di kernelspace.

Volendo si possono generare anche tutti i file intermedi (e.g., i file assembly) e utilizzare strumenti diversi (e.g., ld) per capire meglio cosa fa il compilatore, cosa fa l'assemblatore e cosa fa il linker. GCC incorpora anche quelle funzionalità, quindi non c'è bisogno di usarli direttamente, ma può aiutare a capire il funzionamento di un compilatore. A quel punto, se ottieni un errore del tipo undefined reference to `sum' hai un'idea migliore su cosa sta accadendo: il problema è nella fase di linking e non nella fase di compilazione.
 
In alternativa agli include guards segnalo anche il #pragma once che per progetti su un po' più grandi diventa comodo. Non è standard, ma è ampiamente diffuso e ti evita qualche problema: non è solo syntactic sugar, se hai due file con lo stesso nome (e.g., src/pippo.h e test/pippo.h) fa automaticamente la cosa giusta. Non ho una grande preferenza a riguardo, anche gli include guards non mi dispiacciono, ma visto che li abbiamo menzionati penso che valga la pena citare anche l'alternativa "moderna".
 
  • Mi piace
Reazioni: mattstack
Stato
Discussione chiusa ad ulteriori risposte.