Egy fordítóparancs nem pusztán kapcsolók gyűjteménye: egy buildlépés szerződését írja le. Meghatározza az elfogadott nyelvet, a keresett hibamintákat, az optimalizálási célt és azt, hogy milyen további eszközök kapják meg a feldolgozás eredményét. Ugyanaz a kapcsolólista nem megfelelő minden projekthez, fordítóverzióhoz és buildfázishoz.
A Fordítási fázisok és fordítási egységek rész alapján különítsük el a forrás feldolgozását, az objektumfájl előállítását és a végső linkelést. A Reprodukálható fordítási és diagnosztikai labor elvét követve a profilhoz mindig fordító-, target- és eszközverzió is tartozzon.
ISO C17-szabály: a constraint a nyelvi
leírásban kifejezetten előírt korlátozás. Például ha látható az
int f(int); prototípus, akkor az f(1, 2) hívás
megsérti az argumentumok számára vonatkozó követelményt.
Ha egy fordítási egység szintaktikai szabályt vagy constraintet sért, a megfelelő C-implementációnak legalább egy diagnosztikai üzenetet kell adnia. Ez nem jelent szükségképpen üzenetet minden hibáról, és nem írja elő általánosan a fordítás leállítását.
Ezzel szemben a warning a fordító diagnosztikai kategóriája. Jelölhet kötelezően diagnosztizálandó nyelvsértést, de figyelmeztethet teljesen szabályos, mégis gyanús kódra is. Egy használatlan lokális változó például önmagában nem teszi hibássá a C-programot.
GCC/Clang-kapcsolók: a következő nevek nem az ISO C részei. Pontos tartalmuk és alapértelmezésük verziófüggő.
| Kapcsoló | Rendeltetés | Fontos korlát |
|---|---|---|
-Wall |
Sok gyakori hibaminta diagnosztikáját bekapcsolja | Neve ellenére nem minden warningot kapcsol be |
-Wextra |
További, általában hasznos diagnosztikákat engedélyez | Nem jelenti az összes fennmaradó warningot |
-Wpedantic |
A kiválasztott ISO nyelvi módhoz kapcsolódó, többek között kiterjesztéseket érintő diagnosztikákat kér | Nem teljes szabványossági bizonyítás |
-Wconversion |
Értéket esetleg megváltoztató implicit konverziókra figyelmeztet | Tudatos konverziók mellett is sok jelzést adhat |
-Wshadow |
Más deklarációkat elfedő neveket jelez | Pontos lefedettsége fordítónként eltérhet |
A -std=c17 és a -Wpedantic együtt
értelmezendő: az előbbi választja ki a nyelvi módot. A warningmentes
build azonban sem a hordozhatóságot, sem a nem definiált viselkedés
hiányát nem bizonyítja.
A -Wconversion és a -Wshadow különösen
hasznos saját kódon, de örökölt projektben célszerű fokozatosan
bevezetni őket. Egy explicit cast elnémíthat egy konverziós warningot,
de nem teszi automatikusan helyessé a konverziót.
A -Werror a warningokat buildhibává emeli. Rögzített
CI-eszközláncon ez erős minőségi kapu lehet; változó fordítóverzió
mellett viszont egy új diagnosztika korábban elfogadott buildet is
megállíthat. A GCC és Clang -pedantic-errors kapcsolója
bizonyos, a szabvány által megkövetelt diagnosztikákat hibává emel; nem
egyszerű szinonimája a -Werror kapcsolónak.
Az optimalizálási szint azt szabályozza, milyen átalakításokat mérlegeljen a fordító. Nem a program helyességi fokozata, és nem univerzális teljesítményskála.
| Szint | Jellemző cél | Mire számítsunk? |
|---|---|---|
-O0 |
Az optimalizálások nagy részének mellőzése | Egyszerűbb forrásszintű követés; nem garantál szó szerinti forrás–utasítás megfelelést |
-Og |
Hibakeresést támogató optimalizálás | Hasznos kompromisszum lehet; GCC és Clang megvalósítása eltérhet |
-O1 |
Mérsékelt optimalizálás | Korlátozottabb fordítási költséggel végez átalakításokat |
-O2 |
Általános teljesítményorientált profil | Gyakori release-kiindulópont |
-O3 |
További, agresszívebb optimalizálás | Növelheti a kódméretet, és nem garantáltan gyorsabb |
-Os |
Kódméretre hangolt optimalizálás | Nem garantálja sem a legkisebb fájlt, sem a legjobb futásidőt |
A pontos optimalizálási lépések fordító-, verzió- és targetfüggők.
Különösen a -Og jelentését nem szabad két fordító között
azonosnak tekinteni.
A debug információ olyan metaadat, amely a gépi
program elemeit forrásfájlokhoz, sorokhoz, típusokhoz és változókhoz
kapcsolja. A -g ennek előállítását kéri; nem kapcsolja ki
az optimalizálást.
Indokolt kiinduló profilok:
-std=c17 -Og -g,
megfelelő warningokkal.-std=c17 -O0 -g, ha az optimalizált hibakeresés
félrevezetőnek tűnik.-std=c17 -O2 -g,
ugyanazzal a diagnosztikai alappal.Release-ben a -g incidenselemzéskor értékes. Az
optimalizált változó ettől még eltűnhet, több változó osztozhat
ugyanazon tárolón, és a debugger léptetése eltérhet a forrássorrendtől.
A debug információ jelenléte nem állítja vissza az optimalizálás előtti
programot.
A release-profilból nem következik automatikusan a
-DNDEBUG: az külön döntés, mert megváltoztatja az
assert használatának hatását. A -O3
választását pedig reprezentatív mérésnek kell indokolnia, nem a nagyobb
számnak.
A compiler driver, például a gcc,
vezérlőprogram: a bemenet és a kapcsolók alapján meghívja a szükséges
fordítási eszközöket.
| Kapcsoló | Elsődleges szerep |
|---|---|
-std=c17, -W... |
Nyelvi feldolgozás és diagnosztika |
-Iinclude, -DNAME=value |
Headerkeresés és preprocessor-konfiguráció |
-O2, -g |
Kódgenerálás és debug információ |
-c |
Objektumfájl előállítása, végső linkelés nélkül |
-Ldir |
Linkeléskor használt könyvtárkeresési útvonal |
-lname |
Könyvtár kiválasztása a linker számára |
-Wl,... |
Argumentumok továbbítása a linkernek |
A -I és a -L tehát nem felcserélhető. Az
előbbi deklarációkat tartalmazó headerek kereséséhez kell; az utóbbi a
linkelhető könyvtárakhoz. A header megtalálása nem biztosítja a benne
deklarált függvény implementációját.
Linux/GNU eszközláncon a -lwave szokásosan például
libwave.so vagy libwave.a nevű fájl
kiválasztásához vezet. A keresés részletei és a statikus vagy megosztott
változat választása a linkelési beállításoktól függ.
A
-Wl,-Map,build/app.mapdriverargumentum a linkernek két argumentumot továbbít:
-Map és build/app.map. Itt a vesszők a driver
által feldolgozott elválasztók.
A -L nem általános futásidejű könyvtárkeresési
beállítás. A dinamikus betöltés külön kérdés; ezt a Statikus és megosztott könyvtárak rész bontja
ki.
Fontos következmény: hagyományos, linkidőbeli
optimalizálás nélküli buildben a már elkészült .o fájlok
mellé tett -O2 nem optimalizálja újra a függvényeket. A
release-profilhoz a forrásokat is újra kell fordítani.
Egyes kapcsolók több fázist érintenek. Például a támogatott sanitizerprofiloknál a megfelelő beállítás rendszerint fordításkor és a végső driveres linkeléskor is szükséges. Ezért a kapcsolókat jelentésük, nem pusztán nevük alapján kell szétválasztani.
Laborprofil: ISO C17; Linux x86-64, System V ABI,
LP64; GCC 14.2.0, GNU binutils 2.43.1 és glibc 2.40. Összehasonlító
fordítóként Clang 19.1.7 használható ugyanazzal a rendszerkörnyezettel.
Ezek kijelölt laborverziók, nem itt végzett fordítás eredményei. A
parancsok feltételezik, hogy a gcc és az ar
ezekre a rögzített eszközökre mutat.
A példa három fájlból áll. Az include/wave.h:
#ifndef BOOK_WAVE_H
#define BOOK_WAVE_H
double wave_sample(double radians);
#endifAz src/wave.c:
#include "wave.h"
#include <math.h>
double wave_sample(double radians)
{
return sin(radians);
}Az src/main.c:
#include "wave.h"
int main(void)
{
double sample = wave_sample(0.5);
return sample > 0.0 ? 0 : 1;
}A program értékeket ad át; nem kezel dinamikus erőforrást vagy kölcsönzött mutatót. A hívás véges, érvényes argumentumot ad a matematikai függvénynek. A várt sikerjelzés nulla visszatérési érték, nem itt megfigyelt futási eredmény.
A fájlok létrehozása után, tiszta buildkönyvtárból:
mkdir -p build/debug
gcc -std=c17 -Wall -Wextra -Wpedantic -Wconversion -Wshadow \
-Og -g -Iinclude -c src/wave.c -o build/debug/wave.o
gcc -std=c17 -Wall -Wextra -Wpedantic -Wconversion -Wshadow \
-Og -g -Iinclude -c src/main.c -o build/debug/main.o
ar rcs build/debug/libwave.a build/debug/wave.o
gcc -fuse-ld=bfd build/debug/main.o \
-Lbuild/debug -lwave -lm \
-Wl,-Map,build/debug/app.map -o build/debug/appAz ar archívumot készít; ez nem a végső program
linkelése. Az utolsó parancsban a gcc driver hívja meg a
GNU BFD linkert.
Az argumentumok szerepe lépésenként:
main.o hivatkozik a wave_sample
függvényre.-Lbuild/debug -lwave elérhetővé teszi annak
implementációját.-lm a profil matematikai könyvtárát adja hozzá a
sin feloldásához.A -lm buildkövetelmény nem ISO C-szabály, hanem ennek a
rendszerkörnyezetnek a sajátossága. A könyvtárakat a rájuk hivatkozó
bemenetek után helyezzük; különösen statikus archívumoknál számít a
sorrend.
Release-változathoz mindkét forrást újra kell fordítani
-O2 -g beállítással, elkülönített
build/release útvonalakra, majd az archívumot és a
programot is újra előállítani. A debug- és release-objektumok keverése
ne legyen véletlen buildállapot.
ld linkeljen?A linker nem ugyanazokat az alapértelmezéseket ismeri, mint a nyelvi driver. A driver a telepített eszközlánc konfigurációja alapján választja ki többek között:
Linux/GNU környezetben a C-folyamat rendszerint nem közvetlenül a
main függvénynél indul. Ezért a puszta
ld main.o ... parancs akkor is hiányos lehet, ha minden
saját függvény implementációja rendelkezésre áll.
A gcc és a g++ közötti különbség sem
egyszerűen fájlkiterjesztés:
gcc telepített C++ frontenddel képes
.cpp fájlt fordítani, de a végső linkeléskor nem adja
automatikusan hozzá ugyanazokat a C++ könyvtárakat, mint a
g++.g++ automatikusan hozzáadja a C++
standard könyvtárat, tipikusan a libstdc++-t.g++ a .c bemenetet is alapértelmezetten
C++-ként kezeli; ezért nem helyettesíti következmények nélkül a
C-fordítás gcc parancsát.Vegyes programnál a C-forrást C-ként, a C++-forrást megfelelő C++
módban fordítsuk, a végső linkelést pedig rendszerint a C++ driver
végezze. Clang esetén ennek megfelelő pár a clang és a
clang++; a tényleges C++ könyvtár konfigurációfüggő.
Egy fejlesztő szerint a -O2 -g értelmetlen, mert az
optimalizált program nem hibakereshető. Javítsd az állítást, és javasolj
fejlesztői, illetve release-profilt.
Az alábbi, C17-es fordítási egységet csak diagnosztikai kiindulópontként kezeld:
int accepted_count(double value)
{
return value;
}Módosítsd úgy, hogy csak a
tartományt fogadja el, törtrészt elhagyó konverzióval. Minden más
bemenetre, NaN esetén is, adjon -1 értéket. Ellenőrző
fordítás:
gcc -std=c17 -Wall -Wextra -Wpedantic -Wconversion \
-c count.c -o count.oA laborprogramot valaki közvetlenül így linkeli:
ld build/debug/main.o -Lbuild/debug -lwave -lm -o appMiért hibás buildstratégia ez? Hogyan javítanád? Elegendő-e a
javított linkparancshoz -O2 kapcsolót adni release
készítéséhez?
Az optimalizált program hibakereshető, de a forrásszintű
megfigyelések korlátozottabbak lehetnek. Indokolt fejlesztői
kiindulópont a -Og -g, release-ben pedig a
-O2 -g, mindkettőnél explicit -std=c17 és
warningprofil.
Ellenőrzési feltétel: a debugger forrássorokat tudjon azonosítani; ne követeljük meg minden változó folyamatos megfigyelhetőségét.
C17-es megoldás:
int accepted_count(double value)
{
if (!(value >= 0.0 && value <= 100.0)) {
return -1;
}
return (int)value;
}A feltétel NaN esetén is elutasít, mert a tartományba tartozást
igazoló összehasonlítások nem teljesülnek. Az elfogadott tartomány
minden egész eredménye ábrázolható int típusban; a
konverzió a törtrészt elhagyja.
Ellenőrzési feltétel: például 0.0 → 0,
12.75 → 12, 100.0 → 100; negatív, száznál
nagyobb és NaN bemenetnél -1. A cast helyességét a
tartományellenőrzés indokolja, nem a warning eltűnése.
A közvetlen ld-hívásból hiányozhatnak a startup- és
runtime-összetevők, valamint szükséges keresési beállítások. A javítás a
kidolgozott példa driveres linkparancsa.
Ellenőrzési feltétel: a linklépést a megfelelő
nyelvi driver végezze. Release-hez minden érintett forrást újra kell
fordítani a release-profillal; a meglévő debug-objektumok mellé írt
-O2 önmagában nem elegendő.
A warningprofil hibakeresési eszköz, nem helyességi bizonyíték. Az
optimalizálást és a debug információt külön döntésként kezeljük. A
kapcsolókat buildfázis szerint rendezzük: -I a headerekhez,
-L és -l a linkeléshez, -Wl,
pedig a linkerargumentumok továbbításához tartozik. A végső linkelést
általában a megfelelő compiler driver végezze, rögzített és dokumentált
eszközlánccal.