Sok fájból álló C forráskód - hogyan kell jól csinálni?

Fórumok

Sziasztok,

Hobbi projektként fejlesztek egy c programot.
A forrás egyre több fájlból áll, és egyre körülményesebb a külön megírt függvények használata. Az idő nagyrészét az veszi el, hogy kikeresgetem, hogy melyik .h fájlban, pontosan milyen néven és paraméterekkel kell a függvényeket hívni.

Milyen technikák vannak sok fájlból álló C forrás hatékony kezelésére?
Hogyan csinálják ezt nagy programoknál?

Hozzászólások

Például előre megtervezzük...
Egy adott modul prototípusai egy adott headerbe kerülnek, stb.

Na, ez a tipikus, amikor nem tervezted meg. Ez nálam is előfordul.

Az valahol szerintem C-nél, C++-nál nem jó, ha már neked, mint fejlesztőnek is keresgélned kell azt, hogy mi hol van. Az IDE csak könnyítse meg a munkád, ne oldja meg helyetted! (Értsd: szerintem célszerű, ha egy adott IDE funkciót azonnal meg tudnál te is csinálni, gondolkodás nélkül, csak hosszabb ideig tartana).

Tényleg hasznos, ha modulokban gondolkodsz. Minden összetartozó felelősségi kört külön fejléc+forrás fileba rakni.

Előbb-utóbb célszerű valamilyen build systemet használnod. Ami lehetőleg több, mint egy egyszerű Makefile (semmi különösebb bajom a Makefileokkal, sok rendszeren csak ez érhető el, viszont kicsit nehézkes tud lenni a karbantartása).

Ez az előre tervezés egy nagy bullshit, meg is mondom miért: egy csomó információ csak menetközben válik elérhetővé, emiatt nagyon nehéz, szinte lehetlen előre optimális döntéseket hozni. Helyesebben tennénk, ha az eszközeink és munkafolyamataink az utólagos tervezést támogatnák és tennék egyszerűvé. Hogy akkor adjunk valaminek szilárd vázat, amikor már tényleg látszik, hogy jó. Nem pedig előre kitalálunk valami szerkezetet, amiről ugyan később kiderül, hogy rossz, de már nem lehet rajta változtatni, mert az túl drága lenne.

Szerintem új projekt esetén célszerű kezdetben mindent egy fájlba írni. Amikor már vannak letisztult struktúrák, amelyek már egy ideje lényegesebb változtatás nélkül vannak használva, akkor azokat érdemes kirakni külön fájlba, csökkentve ezzel a fő fájl méretét.

De a rendelkezésre álló információk alapján már lehet egy kezdeti váz/elképzelés.

Funkció, szint, stb. szerint érdemes mindig szétválogatni. Egy projektről azért el lehet dönteni már előre is, hogy kb. mekkora lesz. Ha párszáz sor, akkor nyilván nem érdemes vacakolni. Viszont ha látszik, hogy valami nagy fa lesz, akkor már rögtön lehetne funkció szerint fájlba írogatni. Megjelenítést pl. végezhet egy külön "fájl", hálózati kommunikációt egy másik, adatbázisműveleteket egy harmadik, bonyolult numerikus számításokat egy negyedik, stb.

"Ez az előre tervezés egy nagy bullshit, meg is mondom miért: egy csomó információ csak menetközben válik elérhetővé, emiatt nagyon nehéz, szinte lehetlen előre optimális döntéseket hozni. "

Na ez az igazan nagy bullshit. Specifikalni tudni kell es ehhez az kell, hogy valaki tudjon az ugyfellel is kommunikalni. Igen, vannak valtozo igenyek, amihez alkalmazkodni kell. Igen, megrendelot ugy kell utni es ugy kell belole kihuzni harapofogoval az infot. De az, hogy valaki nem ul le rendesen a megrendelovel es aprolekosan utanakerdezni, hogy de biztos ugy van, elofordulhat-e ilyen eset, mi van akkor, ha ez meg az, esatobbi esatobbi, az szimplan a kivitelezo trehanysaga, mert nem vegezte el a hazi feladatat, a specifikalast.

Ott, ahol "hat van valami vaz, majd arra epitunk" modell megy, ott nem mernoki munka van, hanem kokemeny taknyolas.

"Szerintem új projekt esetén célszerű kezdetben mindent egy fájlba írni. "

Most ugye nem az n+1. pistuka BT homepage-rol van szo, hanem valami komplexebb dologrol?

----------------
Lvl86 Troll, "hobbifejlesztő" - Think Wishfully™

Szerkezet alatt mit ertesz?

--

Specifikalas nem egyenlo tervezessel. Azonban a tervezeshez szukseges a jo specifikacio, a jo specifikacio irasahoz meg (tapasztalataim szerint) szukseges mar az is, hogy atgondolja a specko szerzoje a feladatot, ezzel mar akarva-akaratlanul is belemegy picit a tervezes reszebe).

Ha a megvalositas soran derul ki, hogy a tervek alapjan valami nem megvalosithato, akkor ket eset lehetseges:
- maga a terv szar => tervezesi hiba.
- kimaradt valami a tervekbol => specifikalasi hiba.

Ha olyan igeny jon, ami teljesen felboritja az alkalmazast az azert eleg kemeny dolog. Akkor vagy valami teljesen mast igenyel a megrendelo vagy maga a tervezes soran lett valami csunyan elszurva, pl. adott igenyre hardcodeolva minden, semmi rugalmassag, nulla modularitas, etc. Modularitas kulon vicces temakor, mert amit modularisnak hivnak, az valojaban csak a felhasznalo szamara tunik modularisnak, felszin alatt egy nagy monolitikus cucc az egesz. Azonban altalaban nem jon olyan igeny, aminek teljesen boritania kellene egy _jol_ megtervezett alkalmazas architekturajat. Ha jol van megtervezve es megis, akkor meg szvsz inkabb az az eset lep fel, hogy valami total mas rendszerbe akarjak beleeroszakolni azt, ami eredetileg nem oda keszult.

Az meg, hogy valami menet kozben derul ki, (pl. projekt felenel kiderul, hogy a megrendelonek van egy keretrendszere, amit hasznalni kellene es emiatt ki kellene dobni a projekt 60%-at -- elozo melohelyemen egy projektnel megtortent ez a "baleset") vagy hogy igazabol nem az a feladat, amit eddig csinaltak vagy teljesen mashogy kellene, az egyertelmuen specifikacios hiba. Olyan szoftverfejlesztes nincs, hogy elkezdunk valamit, aztan majd kitalaljuk, hogy mi lesz. (Illetve van, de azt kutatasi projektnek hivjak, ami egy eleg spec kategoria).

(Jo, persze, olyan megrendelo is van, aki azt mondja, hogy "hat ezt en nem tudom leirni, de ha kerdes van, keressetek barmikor", de egy ilyet csak egy orult vallal be.)

----------------
Lvl86 Troll, "hobbifejlesztő" - Think Wishfully™

Kb. annyit, hogy hogyan végzed el a probléma dekompozícióját részproblémákra.

Tulajdonképpen annyit akartam mondani, hogy overdesignt kerülni kell. "Ezt azért csinálom így vagy úgy, hogy aztán majd könnyedén lehessen így vagy úgy bővíteni a kódot..." Én azt gondolom, hogy pazarlás bővítési felületeket létrehozni addig, amíg nincs semmi, ami arra a felületre csatlakozna. Vagy csak egy dolog csatlakozik, és így fölöslegesa felületet létrehozni, mert a részeket úgysincs mire cserélni.

Agile az egy uj dolog, es nem a waterfall alternativaja, esetleg csak az emberek agyaban.

Kis tortenelem:
Az agile az iterativ modellbol szarmazik (+egy kis pszichologia es marketing). Iterativ modell pedig papiron 1985 ota van.

A waterfallra meg ennel is elobb leteztek alternativak. Sot, amit te waterfallnak hivsz az nem az, amit 1970-ben W. W. Royce kitalalt, hanem mar akkor is rossz peldakent hozta fel.

Fejlesztesi modellt ugy kell megvalasztani, hogy a megrendelo es a fejleszto igenyeit is kielegiti. Ennyi. Problema csak es kizarolag akkor lep fel, ha inkompetensek a modell iranyitoi/vegrehajtoi, vagy rosszul lett a modell a helyzethez valasztva.

És majd "menet közben kialakul", hogy az ügyfél milyen feltételek szerint és mennyit fizet? :)

Kell szerződés, aztán speckó, aztán tervek, aztán kód, végül tesztek, mindegyikhez egyeztetés kell, és minden egyes lépést keményen le kell papírozni. A fejlesztők garit adnak a hibákra, az ügyfél meg fizet külön a változási igényekért. Ez így korrekt mindkét fél számára. Mikor a kóder leírja az első sort, addigra már a moduljának vagy akár az egész terméknek a terve megvan a tervező fejében.

Akinek sikerül olyan ügyfelet találnia, aki agilisebb dolgokban hisz, és ezt alá is írjátok, akkor gratulálni tudok csak :). Úgy értem, kétesélyes, ha ki tudod dumálni, hogy az ügyfél fizesse a fölösleges köröket is, az egy aranybánya, ha meg csak a végtermékért fizetnek, az egy végtelen fail sorozat.

--

Azert szerintem az agilis projektek is ugy indulnak, hogy kb. megvan a projekt speckoja, csak kevesbe reszletes, tehat mittomen nincs megadva, hogy kek szinu gombokat akarnak, amik rakattintva atvaltjak az egerkurzort forgo puttova/delfinne, hanem csak a feladat van specifikalva, hogy az adott cuccnak mit kell csinalni, es az implementalas soran mar ezek kerulnek kifejtesre. Szerintem se lehet ugy nekiindulni valaminek, hogy nem tudjuk, mi a feladat, hanem mindig csak a kovetkezo lepcsofokot latjuk, valamennyire tudni kell, hogy mi lesz a vegtermek.
--

Ki oda vagyik, hol szall a galamb, elszalasztja a kincset itt alant. | Gentoo Portal 

Tipikusan egy agile projektet time and material alapon fizetnek. Mondjuk havonta az elvégzett munka alapján.

Hogy összesen mennyit fizet, az valóban kialakul. Ha az ügyfél elégedetlen, akkor keveset fizet és leállítja a projektet. Ha elégedett az eredménnyel, akkor a projekt addig megy, amíg új igényei vannak.

Sikerül olyan ügyfelet találnia? Jelenleg az új ügyfelek nagy része agile projektet szeretne. Legalább is nálunk ez a tapasztalat, folyamatosan egyre nagyobb része a projekteknek agile.

A minimum az volna, hogy .h fájlokba szeded ki a függvényeid deklarációját.
Nagy előrelépés, ha az editorodat is betanítod a függvényeidre (ctags segít, ha az editor is hagyja).
És a legtöbbet azzal teszel magadnak, ha írsz egy lokális mangyűjteményt a függvényeidhez - ez egyrészt segít holnapután is tudni, hogy ki kicsoda, és ilyenkor szokott kiderülni, hogy mi mindent nem gondolt végig az ember, vagy gondolt máshogy, mint kéne.

Valamilyen ügyes szerkesztő programot vagy fejlesztői környezetet használnak.
Innen vagy innen biztos tudsz választani az igényeidnek megfelelően.

"A másik kérdés, hogy meg kell tudni valahonnan, hogy pontosan melyik .h fájlt kell include-olni, és addig az IDE se tud segíteni."
Ez a tipikus nem tervezés, mert ezek szerint az nem megy, hogy az azonos dologhoz tartozó dolgokat egy file-ba szeded. Ha más nem, rendezd ABC szerint;)

Nézz meg egy profibb fejlesztőeszközt, hogy mit tud! Csak hogy lásd, hogy mi a cél :-). Ha úgyis hobbi, akkor mellékszálként próbáld ki a Java-t vagy a C#/.NET-et (ezek un. menedzselt nyelvek) Eclipse-szel, vagy Visual Studio-val. (Biztos van más is, ami jó, de én ezeket ismerem.)

* Minden azonnal lefordul és a hibákat az editorban megjelöli (háttérszálon, a munka lassítása nélkül)
* Ha elkezdesz beírni valamit, akkor a lehetséges befejezéseket kiadja automatikusan. A javaslatokhoz azonnal elérhető a hozzájuk tartozó dokumentáció (amit persze meg kell írni, de közvetlenül a kódba elég beírni).
* Minden ami másik forrásbeli elemet hivatkozik, az navigálható az editorban közvetlenül.
* Vannak eszközök arra (find references), hogy egy adott elemnek az összes hivatkozását megkeresd.
* Vannak eszközök elemek átnevezésére az összes hivatkozással együtt (refactor/rename).

Ezeket a funkciókat C projektekre (a nyelv túlzottan szabad szerkezete miatt) szinte lehetetlen jól megcsinálni. Ezért nem is nagyon van olyan IDE, ami mindenre tökéletesen működik. Ha a projekted nem egyértelműen csak C-ben valósítható meg, akkor bizonyos méret felett érdemes Java-ra, vagy C#-ra váltani emiatt is.

Ha viszont maradsz C vonalon, akkor ahogy mások is írták, sokat kell fordítani a tervezésre, dokumentációra, logikus elnevezésre. Ha be tudod tartani egy adott rendszer konvencióit, akkor állítólag el lehet érni hasonlót, mint amit az Eclipse/Java tud. Én még nem láttam ilyet élesben működni. Egy olyan egyszerű eszközzel, ami tud sok fájlban egyszerre és gyorsan keresni (krusader, Eclipse, akármi) is már sokat el lehet érni. A legutóbbi mikrovezérlőre írt munkámat ilyennel csináltam. De azért ez nagyon fapad a menedzselt nyelvekhez képest.

+1

Egy szomorúságom van:

"* Minden azonnal lefordul és a hibákat az editorban megjelöli (háttérszálon, a munka lassítása nélkül)
* Ha elkezdesz beírni valamit, akkor a lehetséges befejezéseket kiadja automatikusan. A javaslatokhoz azonnal elérhető a hozzájuk tartozó dokumentáció (amit persze meg kell írni, de közvetlenül a kódba elég beírni).
* Minden ami másik forrásbeli elemet hivatkozik, az navigálható az editorban közvetlenül.
* Vannak eszközök arra (find references), hogy egy adott elemnek az összes hivatkozását megkeresd.
* Vannak eszközök elemek átnevezésére az összes hivatkozással együtt (refactor/rename).
"

Ezeket már a Turbo Pascal is tudta évtizedekkel ezelőtt, kár, hogy egy opensoruce/free ide sem nőtt még fel idáig...

Nem emlékszel?

* Minden azonnal lefordul és a hibákat az editorban megjelöli (háttérszálon, a munka lassítása nélkül)

Az editor ablakban mutatta a hibás sort, s kiírta hogy mi a hiba.

"* Ha elkezdesz beírni valamit, akkor a lehetséges befejezéseket kiadja automatikusan. A javaslatokhoz azonnal elérhető a hozzájuk tartozó dokumentáció (amit persze meg kell írni, de közvetlenül a kódba elég beírni)."
Igaz, ez lehet, hogy már csak a Borland Pascal 7.0-ban volt, de határozottan emlékszem, hogy volt benne (tán ctrl+space-re tudtad előhívni a listát is)

"* Minden ami másik forrásbeli elemet hivatkozik, az navigálható az editorban közvetlenül."
Lehet, hogy ez is csak BP7.0 feauture, igaz, viszont volt benne, legalábbis volt olyan opció, hogy ráálltál egy szóra, amit ha felismert, akkor meg tudtad nyitni azt a .pas -t, amiben az az elem van.

"* Vannak eszközök arra (find references), hogy egy adott elemnek az összes hivatkozását megkeresd.
* Vannak eszközök elemek átnevezésére az összes hivatkozással együtt (refactor/rename)."

Azért nem sima rename volt, az való igaz, hogy nem olyan fejlett refactor volt benne, mint egy mai VS-ben...

Bullshit. Ezeket kb mind tudja az Eclipse CDT-vel, a QtCreator, a KDevelop, csak, hogy az elterjedtebbeket említsem.
De emacs és vim fanok írnak neked egy listát, hogy milyen pluginokkal kapod meg ezek 90%-at az említett programokban...

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee." -- Ted Ts'o

vim, ctags, ctrl + ] definiciora ugras. mindezt eleg nagy project mellett hasznalom evek ota es nekem bevalt

nagy forrásfában navigálásra/keresésre (pl. melyik függvény melyik forrásfájlban van) és még sok másra is az Understand-et használjuk. az egyetlen említhető negatívuma az, hogy a makrókat nem értékeli ki (de ez lehet, hogy újabb verziókban már változott)

Sokat segít, ha szisztematikus elnevezést adsz a C fájlodnak és csak a témával foglalkozol benne, nem össze-vissza szórsz bele mindent. Példa: hálózati kommunikációval a tcpcomm.c és tcpcomm.h fájlokban található tcpcomm_putdata() stb. függvények vannak. És ide nem való olyan függvény, ami compress.c -be lenne való.

Aztán amikor van egy önálló nagy blokk, alkönyvtárba lehet tenni és ott kezelni - akár külön Makefile-val, amit a fő Makefile ügyesen be tud hívni. Picit olyan ez, mint a faszerkezet. Az apró függvényeket már nem feltétlen célszerű a main függvényből hivogatni, arra ott vannak a megfelelő részegységek. Tehát a jó széttagolás szintén sokat segít.

Én legalábbis így csináltam, amikor még nagy projektjeim voltak.

Szép Makefile írását is érdemes elsajátítani.

Erre való a ctags program, amihez megfelelő editor is kell.

Lehet, hogy félreértem a problémádat, de ugye a header fájlokba beleírod a függvényeket.
A headereket beinclude-olod, és akkor nem kell emlékezni, hogy melyik fájlban van. Ha a paraméterekre nem emlékszel, de a függvény neve megvan, akkor meg használj ctags-t és valami szerkesztőt, ami odaugrik neked a deklarációhoz.

Egyetlen dolgot tudok elképzelni, hogy annyi függvényed van, hogy már a nevüket nem tudod fejben tartani, de akkor ez lehetne mind egy fájlban is - nem a fájlok száma a gond.

Én is most kezdek foglalkozni komolyabb mennyiségű kóddal. Nagyon hasznosak a forráskód-böngészők, hogy csak egy ingyenest mondjak: http://sourcenav.sourceforge.net/ (mint ahogy - most látom - fentebb is említette valaki), de ugyanezt a funkcionalitást tudja az eclipse-cdt is (http://www.eclipse.org/cdt/), és még egy csomó más hasznos szolgáltatást is (pl. típusok végigkövetését, makrókifejtést, a deklarációk és implementációk mutatása, fájlokon átívelő átnevezések stb.) Én tehát ez utóbbira szavaznék.

Az Eclipse akkora ordas nagy szívás C programozáshoz, hogy azt elmondani is nehéz. Tipikusan az a tool amihez a programozó alkalmazkodik, és nem fordítva.
Ha végig evvel a szutyokkal dolgozol, még elmegy, de bármilyen meglévő kódot alátenni....

Olyan ez mint a vi, de itt még az sincs, hogy lassú betanulás után lesz egy extrém hatékony eszköz a kezedben. Ez a folyamat végén is csak egy szarkupac marad, csak megtanulod befogni az orrod.

Sajnos egyre nehezebb kikerülni,mert boldog-boldogtalan erre áll át.

Nem erre gondoltam. Van autosave funkcio szinte minden IDE-ben mar, viszont ha pont a mentes kozben szakad ossze a cucc, akkor esetleg mar csak a verziokezelobol tudod helyreallitani a fajlodat. Vagyis jo esellyel par ora munkadnak lehet integetni. Es egy olyan IDE, ami random ossze tud szakadni, siman osszeszakadhat epp mentesnel is. Nekem csak egyszer volt ilyenem, es iszonyu makom volt, mert azt a fajlt csak ujraformazas miatt nyitottam meg (mondjuk nem is eclipse-sel, de hat van azon kivul mas pocsek IDE is).
--

Ki oda vagyik, hol szall a galamb, elszalasztja a kincset itt alant. | Gentoo Portal 

Én megszokásból pár percenként minimálisan mentek, ahogy navigálok az osztályok között, folyamatosan nyitom-zárom őket, nem szoktam 300-at felhalmozni a tabok között, márpedig ez mentéssel jár (azaz a fájlrendszerre kerül a cucc, nyugodtan elkresselhet az IDE). Főleg, hogy folyamatosan futtatgatom a unit és integrációs teszteket, ami mentés/fordítás nélkül nem megy. Szóval szerintem elég esélytelen órák munkáját elveszteni. Egyébként most hogy feljött a téma, meg nem mondom hogy mikor fagyott utoljára. Nem a hetekben, az biztos.

--
http://neurogadget.com/

Hat, az eclipse UI messze nem a felhasznalobaratsagarol hires.

Ezzel mondjuk pont nem volt problémám. Van benne elég hotkey ahhoz hogy kényelmesen lehessen vele dolgozni. Az eclipse-ben inkább az zavar, hogy néha iszonyat lassú és instabil. Ettől függetlenül lehet vele dolgozni (mármint Java projekten).

Bar, mostanaban sokat dicserik az IntelliJ IDEA-t is.

Néha megpróbálkozok vele, de linuxon olyan iszonyat ronda hogy az már tényleg nagyon zavaró.

--
CyclonePHP

A legtöbb időt a feladat halogatásával spórolod meg. Ha ezt a
mondatot nem érted, akkor minden más tanács felesleges.

> Sol omnibus lucet.

Lehet, hogy csak én nem értem, mert gyógyszert is szedek, meg fáradt is vagyok, de mit akar ez jelenteni?
a.) Addig kell halogatni a melót, amíg a hülye ötletek kikopnak az igények közül?
b.) A sok halogatás után, az idő szűkössége miatt lehet csak igazán jól melózni?
c.) Addig kell halogatni a melót, amíg ki nem rúgnak, vagy el nem távolítanak a közeléből?

Vagy valami másra gondolsz?
Szeretnék túljutni ezen a rejtélyen, hogy a többi tanács se legyen felesleges!

Lanygos, lanygyos....

A c/ pont nem játszik, az a/ nagyon is a b/ kevésbé.
Az ülepedés, fejben rendeződés, probléma tisztulás
rendkívül fontos. A feladatot kóstolgatni kell, írni
egy-két alaprutint. Sohasem szabad in medias res
belevágni.

A topiknyitó problémája a tapasztalatlanság. Ez nem baj.
Fussa meg a felesleges köröket. Nagyon jó, hogy észrevette:
hülyeségre fecsérli az idejét. Rá fog jönni a saját
magának legmegfelelőbb megoldásra. Ezt hívják tapasztalat-
szerzésnek.

Aztán, ha profi lesz, először lebeszéli a feladatról
a megrendelőt, és, ha csak az nagyon ragaszkodik hozzá,
akkor vállalja el a munkát. És csak és kizárólag akkor
írja le az első programsort, ha megérkezett az előleg.

A feladathalogatásnak egyébként külön irodalma van.
> Sol omnibus lucet.

Én így szoktam írni (pl.) :

int sum (int a, int b) /* FGV: összeadás, paraméterek: 2 int, visszatér: összeggel */
{
return a + b;
}

Utána a projekt mappában: grep 'FGV:' *.h listázza a függvényeket. :)

--
Coding for fun. ;)

Szerintem a topicnyitónak a különféle eszközök helyett inkább azt kellene elmagyarázni, hogy hogyan lenne célszerű strukturálni egy normális C projektet.

(Amire más nyelvekben tök szép ajánlások vannak mellesleg.)

----------------
Lvl86 Troll, "hobbifejlesztő" - Think Wishfully™

Szerintem itt mindenki a saját kedvenc IDE[szerű] megoldását propagálta itt eddig ahelyett, hogy egy kezdő (C) fejlesztőnek adna tanácsot vagy példát, hogy hogyan is szokás felépíteni egy szoftver architektúráját.

----------------
Lvl86 Troll, "hobbifejlesztő" - Think Wishfully™

Ha másképp nem megy akkor ágyúval verébre: QtCreator .
Lehet vele egyszerű C++ projekteket is kezelni.

Nem akarok új topicot nyitni, mert nagyon erősen kapcsolódik a kérdésem a topichoz.

Adott két adatstruktúra, melyek egymásra hivatkoznak, és eltérő header fájlban vannak deklarálva (tekintsünk el attól, hogy ennek így nincs sok értelme):

a.h:


#ifndef A_H
#define A_H

#include "b.h"

struct A
{
	struct B *b;
};

#endif

b.h:


#ifndef B_H
#define B_H

#include "a.h"

struct B
{
	struct A *a;
};

#endif

Persze ez így nem fog lefordulni, hiszen az egyik headert előbb includeolja a fordító, az includeolja a másik headert, az viszont már nem tudja includeolni az egyiket ezért nem fogja ismerni a benne deklarált struktúrát. Megoldás lenne, ha mindkét headerben a struktúra elé bekerülni egy struct MásikTípus; sor, de ennél jobbat találtam ki. Kódban (az 1. sorok változtak):

a.h:


struct A;

#ifndef A_H
#define A_H

#include "b.h"

struct A
{
	struct B *b;
};

#endif

b.h:


struct B;

#ifndef B_H
#define B_H

#include "a.h"

struct B
{
	struct A *a;
};

#endif

Vagyis elvileg a struct MásikTípus; akkor is includeolásra kerül, ha a header többi része nem, így a kód lefordulhat. A gond csak az, hogy a gyakorlatban ez nem történik meg. A kérdésem az, hogy miért nem?

Az álmoskönyvek szerint nem túl jó ötlet a guard-on kívülre kódot rakni. A másik nem jó ötlet, hogy a fordítás függ attól, hogy adott include előtt mi van.
Valami ilyesmire van szükséged:

a.h:

#ifndef A_H
#define A_H

struct B;

struct A
{
struct B *b;
};

#endif

És az a.c -ben szerepel az include b.

Szerk.:
Jól tévedek, hogy a header-ben vannak kifejtett függvények, amik használni akarják a másik header-ből jövő struct elemeit?
Ebben az esetben ezert nem működik (tegyük fel, hogy elősször az a.h-t húzza be):

struct A;
struct B;
struct A;

struct B
{
struct A *a;
};

// b.h tovabbi tartalma tartalama

struct A
{
struct B *b;
};

// a.h tovabbi tartalma

Ez azért lesz, mert először behúzza az a.h-t, és itt definiálja is a guard macrót, az a.h behúzza a b.h-t, ami szintén definiálja a saját guard macrójat, a b.h behúzza az a.h-t, de itt már létezik az a guard makrója, ezért innen már csak a 'struct A;' kerül bele.

"If you must mount the gallows, give a jest to the crowd, a coin to the hangman, and make the drop with a smile on your lips" The Wheel of Time series

"Az álmoskönyvek szerint nem túl jó ötlet a guard-on kívülre kódot rakni."

Esetünkben a kívülre rakott kódot akárhányszor beszúrhatjuk egy forrásba, szóval szerintem ebből nem lehet probléma.

"Jól tévedek, hogy a header-ben vannak kifejtett függvények, amik használni akarják a másik header-ből jövő struct elemeit?"

Nem, a headerben nincsenek kifejtett függvények.

A kifejtett kód korrekt, pont ez volt a cél. Ez nálam minden probléma nélkül lefordul, az nem okozhat problémát, hogy kétszer szerepel a struct A; sor.

"Megoldás lenne, ha mindkét headerben a struktúra elé bekerülni egy struct MásikTípus; sor, de ennél jobbat találtam ki."

Ez mitől jobb megoldás?

"Vagyis elvileg a struct MásikTípus; akkor is includeolásra kerül, ha a header többi része nem, így a kód lefordulhat. A gond csak az, hogy a gyakorlatban ez nem történik meg. A kérdésem az, hogy miért nem?"

Az a vicces, hogy minden mókolás nélkül is lefordul, pedantic-kal meg minden. Egyelőre nem is értem, hogy miért és hogy.

-E-s fordítás után:
# 1 "asd.cpp"
# 1 ""
# 1 ""
# 1 "asd.cpp"
# 1 "a.h" 1

# 1 "b.h" 1

# 1 "a.h" 1
# 5 "b.h" 2

struct B
{
struct A *a;
};
# 5 "a.h" 2

struct A
{
struct B *b;
};
# 2 "asd.cpp" 2

int main()
{
A a;
B b;
}

Ha valami guru el tudja ezt magyarázni, azt megköszönném. Már hogy miért nem kell legalább forward deklarálni, hogy a B típus megadásánál az A névre ne sírjon.
----
India delenda est.
Hülye pelikán

"Ez mitől jobb megoldás?"

Az a konvencióm, hogy minden adattípust valamelyik headerben deklarálom, és az adattípus használatához egyedül a header includeolása legyen szükséges. Ez vonatkozik a headerekben levő deklarációkra is, vagyis ha én az a.h-ban a B struktúrát akarom használni, akkor ne kelljen explicit megadnom egy forward deklarációt az a.h-ban, hanem a b.h includeolásával legyen elérhető a B struktúra. Mivel egymásra hivatkoznak, ezért mindenképp kell forward declaration, de inkább legyen a struct B; sor a b.h-ban, mint az a.h-ban.

"Az a vicces, hogy minden mókolás nélkül is lefordul, pedantic-kal meg minden. Egyelőre nem is értem, hogy miért és hogy."

Na akkor itt jön az, hogy én msvc-vel próbálom lefordítani, te meg gcc-vel fordítottad. Tehát most már azt is tudjuk, hogy fordítófüggő a hiba. :)

"Az a konvencióm, hogy minden adattípust valamelyik headerben deklarálom, és az adattípus használatához egyedül a header includeolása legyen szükséges."

Ezzel erősen szembemész az árral, ahol próbáljuk limitálni az includeok számát. Ha amit lehet, forward deklarálsz, meglepő módon lehet csökkenteni az inklúdok számát. Olyan dolgokat is lehet forward deklarálni, amikre sok ember nem is gondolna :)
----
India delenda est.
Hülye pelikán

Jogos az észrevétel.

Egyébként az a gyanúm, hogy picit túlegyszerűsítettem a problémát, és mivel mindkét struktúrában a másik struktúrára csak pointert tárolunk, ezért fordításkor a fordító csak azzal foglalkozik, hogy a struktúrába pointert tegyen, és ott nem foglalkozik annak típusával. Vagyis ezért fordul gcc-vel, és ebben a formában talán msvc-ben is (kipróbálhattam volna...)

A valódi probléma C++-ban van, két osztály, az egyik gyakorlatilag egy interface, és egy metódusának argumentumában van egy referencia a másik osztályra. A másik osztály pedig leszármazik ebből az interfaceből.

Leesett, benéztem. Tehát a fordító pont olyan sorrendben includeolt volna, hogy a leszármazó osztály elé került a forward declaration, ami ott nem elég, viszont az interface már ismerte a hivatkozott osztály teljes deklarációját, miközben ott erre nem is volt szükség.

Köszönöm!