- A Clang a C/C++ frontend az LLVM ökoszisztémán belül, míg az LLVM a teljes fordítási infrastruktúraként működik.
- Fontos különbségek vannak a GCC-hez képest a nyelvi kiterjesztésekben, az alapértelmezett beállításokban és az LTO-kezelésben, amelyek befolyásolják a kód viselkedését.
- A Gentoo és más disztribúciók lehetővé teszik a Clang/LLVM és a GCC kombinálását fordítási környezeteken és csomagonkénti tartalék megoldásokon keresztül.
- Az olyan fejlett funkciók, mint a ThinLTO és a PGO, javítják a Clang/LLVM teljesítményét, de a jelzők módosítását és a tipikus kompatibilitási hibák kezelését igénylik.
Ha a modern fordítóprogramok világába tekintünk, a nevek Clang és LLVM Mindenhol felbukkannak, és gyakran felcserélhetően használják őket. Ezen betűszavak mögött azonban különböző fogalmak húzódnak meg, amelyeket érdemes megérteni, különösen akkor, ha a legtöbbet szeretnéd kihozni a rendszeredből, a Linux disztribúciódból, vagy a C, C++ vagy Objective-C projektjeidből.
A mindennapi gyakorlatban sok fejlesztő inkább a GCC-hez van hozzászokva, de egyre gyakoribb, hogy olyan környezetekkel találkozunk, amelyek prioritást élveznek Clang, mint frontend és LLVM, mint infrastruktúraAz egyik fordítóprogramról a másikra való váltás nem csupán egy másik bináris fájl futtatásáról szól: vannak olyan árnyalatok a kompatibilitás, az optimalizálás, az alapértelmezett beállítások és az éles viselkedés terén, amelyek mindent megváltoztathatnak.
Az első dolog, amit tisztázni kell, az az, hogy Az LLVM nem egyetlen fordítóprogramAz LLVM nem csupán egy eszköz vagy eszközök és könyvtárak gyűjteménye, amelyek különféle frontendek, köztük a Clang alapjául szolgálnak. Az LLVM többek között tartalmaz köztes kódoptimalizálókat, backendeket gépi kód generálására számos architektúrához, valamint klasszikus eszközök, például a következők helyettesítőit: ar, nm, ranlib vagy akár linkerek, mint például lld.
A Clang a maga részéről a előlapja C, C++ és Objective-C nyelvek, Objective-C++, CUDA és RenderScript az LLVM ökoszisztémán belül. Fő funkciója a forráskód elemzése, annak ellenőrzése, hogy megfelel-e a nyelvi szabványnak, egyértelmű diagnosztika készítése és lefordítása LLVM köztes reprezentációra (IR), amelyet aztán az eszköztár többi része optimalizál és végrehajtható kóddá alakít.
Ezért, amikor az emberek a „Clang használatáról” beszélnek egy rendszerben, valójában azt használják Clang, mint front-end fordító és LLVM, mint back-endazzal a lehetőséggel, hogy az LLVM kiegészítő segédprogramjait (binutils, linker, futásidejű könyvtárak stb.) is használjuk. Lehetséges például a Clang használata a GCC közvetlen helyettesítőjeként, de továbbra is használható a GCC C++ szabványos könyvtára, annak futásidejei, és általában a GNU infrastruktúra nagy része.
Egy fontos szempont, hogy sok Linux rendszerben a GCC komponensek (standard C++ könyvtár, unwinder, OpenMP, fertőtlenítő könyvtárak stb.) még mindig megtalálhatók. a rendszer alapvető építőelemeiEnnek ellenére fokozatosan elterjedt az a lehetőség, hogy egy szinte teljes egészében LLVM-re épülő eszközláncot építsenek, sőt, a GNU binutils nagy részét is lecserélték, és gyakorlatilag elkerülhetetlen komponensként csak a klasszikus C könyvtárat hagyták meg, amely általában glibc.
A Clang, az LLVM és a GCC közötti kapcsolat
Miután tisztáztuk mindegyik szerepét, fontos megérteni, hogy a Clang/LLVM és a GCC hogyan viszonyulnak egymáshoz teljes eszközláncként. Mindkét projekt hasonló célt követ: hatékony és helyes kódot fordíts számos architektúra és platform esetében, de ezt eltérő belső kialakítással és az alapértelmezett értékekkel és a nyelvi kiterjesztésekkel kapcsolatos eltérő döntésekkel teszik.
A Clang projekt egyik kimondott célja egy fenntartása Nagyfokú kompatibilitás a GCC-hez tervezett kóddalA gyakorlatban ez azt jelenti, hogy számos disztribúcióban, mint például a Gentoo-ban, megpróbálhatod a Clangot alapértelmezett fordítóként használni a rendszercsomagok nagy részéhez. Azonban ez az elképzelés, hogy a Clangot rendszerszinten használd, még mindig némileg kísérleti jellegű: egyes csomagok nagyon specifikus GCC kiterjesztésektől függenek, mások a GCC alapértelmezett beállításaiból feltételeznek bizonyos viselkedéseket, és néhány, bár lefordul, futásidejű problémákat okoz.
Amikor a Clang globális használatát erőltetjük, és valami elromlik, a klasszikus megoldás általában egy olyan környezet meghatározása, amely a következőket tartalmazza: tartalék GCC használatávalEbben az összefüggésben a GCC-t olyan csomagokhoz használják, amelyek nem működnek jól a Clang-gal vagy az LLVM által biztosított alternatív könyvtárakkal és futtatókörnyezetekkel. Ez a Gentoo-ban nagyon gyakori vegyes megközelítés konfigurációkon keresztül valósul meg a következőben: /etc/portage/make.conf és az egyes fordítókra jellemző környezeti fájlok.
Egy másik aspektus, amiben jelentősen eltérnek, a végrehajtás módja. Kapcsolatidő-optimalizálás (LTO)A Clang/LLVM kidolgozta saját megközelítését, amelyben a ThinLTO az ajánlott mód, míg a GCC az LTO fázisaihoz eltérő tervet alkalmaz. A gyakorlatban ez azt jelenti, hogy az LTO viselkedése, teljesítménye és a lehetséges hibák jelentősen eltérhetnek a használt fordítóprogramtól függően.
Főbb különbségek a GCC-hez képest
A mindennapi használatot befolyásoló legjelentősebb különbségek közé tartoznak az egyes fordítók által támogatott nyelvi kiterjesztések. A Clang arra törekszik, hogy kompatibilis legyen a GCC ökoszisztéma nagy részével, de Nem támogat bizonyos GCC-specifikus kiterjesztéseket., például beágyazott függvények. Ez különösen az egyik oka annak, hogy a Clangnak nehézségei akadtak az olyan kritikus csomagok fordításában, mint a sys-libs/glibcAzonban folyamatban van a munka azon, hogy a glibc kompatibilisebb legyen az alternatív eszközökkel.
A lebegőpontos műveletek kezelésével kapcsolatos jelzőkben is vannak különbségek. A GCC alapértelmezés szerint aktív. -ftrapping-math, míg a Clang alapértelmezés szerint a következőt használja: -fno-trapping-mathEz az eltérés azt jelenti, hogy bizonyos lebegőpontos kivételekkel szembeni viselkedés fordítóprogramonként eltérő lehet, ha a fejlesztő nem határozza meg explicit módon, hogyan szeretné ezeket az eseteket kezelni a projektjében.
Egy másik fontos szempont a szemantikus interpozíció kezelése. A GCC alapértelmezés szerint engedélyezi ezt. -fsemantic-interpositionEz lehetővé teszi a szimbólumok beillesztését a megosztott könyvtárakba az ELF összekapcsolási szabályok szerint, de korlátozhatja bizonyos interprocedurális optimalizálásokat. A Clang ezzel szemben alapértelmezés szerint végrehajtja a függvények közötti optimalizálást, és felajánlja a lehetőséget. -fno-semantic-interposition hogy tovább kihasználjuk ezeket az optimalizációkat, amikor a kód ezt lehetővé teszi, és nem klasszikus interpozíción alapul.
Ezek a tervezési különbségek aprónak tűnhetnek, de valódi hatással vannak a szoftverek fordulására és viselkedésére. Gyakori, hogy ami "tökéletesen működik" a GCC-vel, az megköveteli... módosítások a jelzőkben vagy a forráskódban hogy helyesen forduljon és fusson a Clanggal, és fordítva, különösen olyan projektekben, amelyek feszegetik a szabvány határait, vagy a linkelés nagyon finom részleteitől függenek.
Apró, de releváns különbségek
Az alapértelmezett építési beállítások szintjén vannak kevésbé nyilvánvaló árnyalatok is, amelyeket érdemes tudni. Például a GCC alapértelmezés szerint ezt a beállítást használja. -ffp-contract=fast, míg a Clang az alapértelmezett értéket veszi fel -ffp-contract=onA GCC konfigurációja némileg agresszívabb, és olyan módon képes átrendezni vagy optimalizálni, ami bizonyos numerikusan érzékeny helyzetekben kissé kockázatosabb. A Clang az alapértelmezett beállításaival általában konzervatívabb viselkedést mutat, amit sokan biztonságosabb viselkedésnek tartanak, kivéve, ha a cél kifejezetten a teljesítmény maximalizálása.
A vektorizálással kapcsolatban a 12-es verzióig a GCC nem végzett vektoroptimalizálást a következő szinten: -O2 vagy alacsonyabbA Clang azonban a fenti szinteken aktiválja a vektoroptimalizálást. -O1, kivéve a -Ozahol az SLP vektorizálóra korlátozódik. Bár ez ritkán okoz közvetlen problémákat, megmagyarázza, hogy miért fordul elő néha ugyanaz a kód a fordítótól függően eltérő hozamokmég látszólag egyenértékű optimalizálási jelzőkkel is.
Az LTO fázisok egy másik terület, ahol a két projekt eltér egymástól. A GCC és a Clang LTO fázisairól úgy tartják, hogy eltérő módon működnek. drasztikusan eltérőEz azt jelenti, hogy azok a csomagok, amelyek GCC alatt LTO-val jól fordulnak és viselkednek, nem biztos, hogy Clang alatt is jól működnek, és fordítva. Nincs általános szabály: sok esetben tesztelés, konkrét hibák és az egyes projektek sajátosságai kérdése.
Továbbá vannak apró gyakorlati részletek is, mint például az a tény, hogy a Clang bizonyos disztribúciókkal való integrációja során, Ne telepítse közvetlenül a /usr/binde a környezeti változóhoz hozzáadott meghatározott útvonalakon PATHEz olyan eszközöket érint, mint a sudo, akik egy PATH Maga a Clang „el van fehérítve” és bináris fájllá van fordítva, így amikor megjelenik egy új verzió, előfordulhat, hogy az már nem lesz elérhető a következő helyről: sudo amíg a jogosultságkezelő eszközt újra nem fordítják vagy újra nem konfigurálják.
Telepítés és konfiguráció Clang/LLVM segítségével
Az olyan disztribúciókban, mint a Gentoo, a Clang és a többi LLVM komponens vezérlése a következőn keresztül történik: USE zászlók és specifikus változók, mint például LLVM_TARGETSEz utóbbi határozza meg, hogy az LLVM háttérrendszerek mely architektúrákra épülnek, ami kulcsfontosságú, ha több CPU-t vagy eszközt szeretne támogatni.
A Clang telepítéséhez általában a csomagkezelőt kell használni, és miután telepítve van a rendszerre, beállítható, hogy bizonyos csomagok elsődleges fordítójaként vagy globálisan működjön. A Gentoo-ban a Clang alapértelmezett fordítóként való beállításának tipikus módja a változók módosítása. CC y CXX az aktában /etc/portage/make.conf, a Clang futtatható fájljaira és azok C++ megfelelőjére irányítva őket.
Egy másik nagyon rugalmas stratégia a környezeti fájlok használata a /etc/portage/envahol egy Clangon és egy másik GCC-n alapuló fordítói "profil" van definiálva. Ez lehetővé teszi a fordítói profilok hozzárendelését a fájlon keresztül. /etc/portage/package.env, csomagonként különböző fordítókPéldául a rendszer nagy részében használjuk a Clangot, de a problémás vagy rendkívül érzékeny csomagokon kényszerítsük ki a GCC-t.
Vannak történelmi részletek, amelyeket figyelembe kell venni. A 14.0.0 verzió előtt a Clang... Nem volt más választásom default-pie hasonló a GCC-hezEz manuális hozzáadást igényelt -fPIC en CFLAGS y -pie en LDFLAGS független pozicionálással rendelkező végrehajtható fájlok generálásához. A modern verziókban ez leegyszerűsödött, de ha régebbi konfigurációkból származik, érdemes áttekinteni és kitisztítani az elavult hivatkozásokat a flags változókban.
Mindenesetre, még ha egy Clangra és LLVM-re erősen fókuszáló rendszert is építesz, akkor is szükséged lesz rá GCC bizonyos csomagokhoz például a glibc vagy a wine. Néhány disztribúció hibakövetőket tart fenn, amelyek lefordítják az összes olyan csomagot, amely nem fordul le a Clang-gal, segítve annak eldöntését, hogy mikor kell a GNU fordítóhoz fordulni.
Tartalék környezetek és a fordító választása
Kísérleti LLVM-központú profilok használatakor (nem ugyanaz, mint a Clang egyszerű telepítése) korlátozások merülnek fel a tartalék környezetekkel kapcsolatban. Egy tipikus "GCC tartalék" környezet nem biztos, hogy a várt módon működik, ha a teljes verem például a következő használatára van beállítva: libc++, mint standard C++ könyvtárIlyen esetekben a zászlók, mint például -stdlib=libc++ amikor a GCC-t ebben a vészhelyzeti környezetben hívják meg, és még akkor is előfordulhat, hogy a viselkedés nem a vártnak megfelelően alakul.
A gyakorlati ötlet az, hogy létrehozzunk /etc/portage/env egy konfigurációs fájl, például compiler-gcc, meghatározva a GCC-vel való fordításhoz szükséges környezeti változókat. Ezután a /etc/portage/package.envA környezetet használó csomagok kiosztásra kerülnek. Ez a minta különböző kombinációkkal ismétlődik: Clang LTO nélkül, Clang LTO-val, GCC LTO nélkül, GCC LTO-val stb.
Így, amikor egy csomag Clang-el hibát jelez (GCC kiterjesztések, LTO problémák, keresztfüggőségek stb. miatt), akkor elég Adja hozzá a másik környezettel fordított csomagok listájáhozEzáltal a Clang és a GCC együttélése meglehetősen kezelhető, feltéve, hogy fegyelmezetten karbantartod ezeket a konfigurációs fájlokat.
„Emberibb” szinten sok felhasználó kíváncsi arra, hogy egy konfigurációs szkript melyik fordítót választja, ha mindkettő telepítve van. A build rendszer jellemzően egyértelmű szabályokat követ: olyan környezeti változókat vizsgál, mint például CC y CXXEllenőrizd, hogy mely fordítók érhetők el a PATHés bizonyos esetekben bizonyos neveket részesít előnyben, például gcc o clangTehát a „preferencia” nem varázslatos: azt a rendszerkonfiguráció és a felhasználó által meghatározott paraméterek határozzák meg.
A Clang/LLVM haladó szintű használata: LTO, PGO és egyebek
A Clang nagyon jól integrálható a következőkkel: fejlett optimalizálási technikák mint például az LTO (Link Time Optimization) és a PGO (Profile Guided Optimization). Az LTO esetében a fordítóprogram LLVM bitkódot generál a hagyományos objektumkód helyett, és az optimalizálások nagy részét a csatolási fázisra halasztja.
A Clang két fő LTO-típust támogat. Egyrészt a LTO teljesamely egyszerre elemzi a teljes linkegységet; ez a klasszikus megközelítés, hasonló a GCC-hez, de manapság már nem ajánlott elsődleges opcióként. Másrészt van egy ThinLTOahol a linkegységet beolvassák és több részre osztják. Minden rész csak a hatóköréhez kapcsolódó kódot tartalmazza, csökkentve a memóriafogyasztást, felgyorsítva a fordítást és növelve a párhuzamosságot a teljesítmény túlzott feláldozása nélkül.
A gyakorlatban a ThinLTO aktiválásához egy jelzőt használnak, például -flto=thin a fordítási változókban. Ha a teljes LTO-t használni szeretnéd, egyszerűen cseréld le erre: -fltoanélkül, hogy a két mód között jelentős kompatibilitási különbségek lennének. Azonban érdemes megjegyezni, hogy ha a csomag clang-common Nem az USE jelzővel készült. default-lld, hozzá kell majd tenni -fuse-ld=lld a LDFLAGS így az LLVM linkert használják.
Használhatod az LLVM binutils eszközeit is, például llvm-ar, llvm-nm y llvm-ranlibkülönösen LTO által generált bitkódokkal való munka esetén. Ezek kifejezetten a formátum megértésére tervezett alternatívák, bár a gyakorlati tapasztalatok projektenként eltérőek, és nem mindig kínálnak egyértelmű javulást a standard eszközökhöz képest.
A PGO tekintetében az LLVM ökoszisztéma olyan komponenseket biztosít, mint a clang-runtime USE jelzővel sanitize y compiler-rt-sanitizers olyan zászlókkal, mint profile u orcA USE jelző aktiválása pgo Globális vagy csomagszinten valós idejű programvégrehajtási információk gyűjthetők és továbbíthatók a fordítóprogramnak ezekkel a profilokkal, így az a tényleges használat alapján optimalizálhatja a gyorskód-útvonalakat.
A fentieken túl a Clang nagyon jól működik gyorsítótárazó rendszerekkel, mint például ccacheA Clang telepítése után ezek a projektek általában szinte automatikusan működnek, felgyorsítva az újrafordításokat. A speciálisabb területeken pedig olyan projektek, mint a LégcsavarA Propeller egy PGO megközelítés, amelyet olyan eszközökkel kapcsolatos problémák, például a Bolt, különösen a memóriafogyasztás kezelésére terveztek. A Propeller a Clangra támaszkodik, és olyan függőségeket igényel, mint... app-arch/zstd a USE jelzővel static-libs, egy meglehetősen konkrét forrásból származó összeállítás mellett.
Gyakori problémák és azok kezelése a Clang használatával
Azokban a környezetekben, ahol a Clang a fő fordítóprogram, a leggyakoribb hibák általában néhány tipikus mintázatba csoportosulnak. Az első egyértelmű példa a fordítási hibák LTO használatakorHa egy csomagot a következővel fordítanak le: -flto És ha ismétlődő hibák jelennek meg a Portage naplókban, akkor egy praktikus megoldás az LTO letiltása az adott csomaghoz egy olyan környezet használatával, mint a compiler-clang LTO nélkül.
Néha, még ha az LTO le van tiltva a hibás csomagban, a probléma továbbra is fennáll, mert Egy másik függő könyvtár LTO-val lett lefordítva, és hibásan működik.Klasszikus példa erre, amikor egy csomag, mint például boehm-gc A függősége miatt szétrobban libatomic_ops LTO-val fordítják, és váratlan viselkedést produkál. Ilyen esetekben az LTO nélküli függőséget is újra kell építeni, és biztosítani kell, hogy mindkét csomag konzisztens környezetben legyen lefordítva.
Egy másik gyakori probléma akkor jelentkezik, amikor a forráskód a következőt használja: GNU kiterjesztések a megfelelő szabvány megadása nélkül a zászlón keresztül -std=A GCC jellemzően számos ilyen felhasználást engedélyez anélkül, hogy egy adott szabványt követelne meg, míg a Clang letiltja ezeket a ritkább kiterjesztéseket, hacsak nincs kifejezetten feltüntetve. Ha egy csomag ezektől a kiterjesztésektől függ, akkor olyan jelzőkkel kell lefordítani, mint például: -std=gnu89, -std=gnu99 o -std=gnu++98, a nyelvnek és az elvárt színvonalnak megfelelően.
Ennek a problémának a tipikus tünete a látás több beágyazott függvénydefiníció a fordítási naplókban. Ez azért van, mert a Clang alapértelmezés szerint C99 inline szabályokat használ, amelyek nem illeszkednek jól a gnu89Ebben az esetben a kényszerítés -std=gnu89 Ez általában elegendő; ha nem, akkor mindig van lehetőség az ütköző csomag GCC-vel történő fordítására a tartalék környezetek egyikének használatával.
A kétségek gyakran akkor is felmerülnek, amikor a rendszer hibákat jelez, például sudo: clang: command not foundAz történik ott, hogy a Clang telepítve lett egy olyan elérési úton, amely hozzáadódik a PATH a felhasználótól, de A sudo fenntartja a saját belső PATH-játA bináris fordítási folyamat során meghatározott elérési út nem fogja tartalmazni a Clang elérési útját, amíg a sudo újra nem fordítódik, vagy a konfigurációja nem módosul. Ezért a sudo nem fogja megtalálni a Clangot, bár egy normál felhasználó problémamentesen futtathatja.
A Gentoo-t vagy más, részletes hibakövetéssel rendelkező disztribúciókat használók számára a Clang-problémák fő hivatkozása általában a következő: egy adott hibakövető Ide vannak központosítva az összes ismert hiba a csomagokban, amelyek nem fordulnak le vagy nem futnak megfelelően ezzel az eszközlánccal. Ha új hibát találnak, a felhasználókat arra ösztönzik, hogy nyissanak meg egy jelentést, és zárolják azt az általános hibakövetőben, hogy a közösség kijavíthassa, vagy dokumentálhassa a megoldásaikat.
Ha összehasonlítjuk ezeket a darabokat, láthatjuk, hogy a tandem Clang + LLVM Egy nagyon hatékony, rugalmas és modern ökoszisztémát kínál, amely azonban számos rendszerben szorosan együtt létezik a GCC-vel, különösen olyan érzékeny szinteken, mint a C könyvtár vagy a nagyon régi csomagok. Ha megértjük a különbségeiket, azt, hogy hogyan egészítik ki egymást, és milyen módosításokra van szükség a flagekben, LTO-kban vagy nyelvi szabványokban, a váltás kevésbé lesz ismeretlenbe ugrás, és sokkal értékesebb eszköz a fejlesztői környezet vagy az egyéni Linux rendszer beállításakor.
Szenvedélyes író a bájtok és általában a technológia világáról. Szeretem megosztani tudásomat írásban, és ezt fogom tenni ebben a blogban, megmutatom a legérdekesebb dolgokat a kütyükről, szoftverekről, hardverekről, technológiai trendekről stb. Célom, hogy egyszerű és szórakoztató módon segítsek eligazodni a digitális világban.