A natív program indulása két külön szerződés találkozása. Az ISO C azt határozza meg, milyen feltételekkel kapja meg a vezérlést a C program, és milyen adatokat használhat. A Linux, az ELF-formátum, a hívási ABI és a C futtatókörnyezete azt valósítja meg, hogyan jut el idáig a folyamat.
Az ELF-betöltésről szóló rész alapján már
megkülönböztetjük a betöltőt és a dinamikus linkert. Most ezt kapcsoljuk
össze a main függvénnyel. A deklarációk olvasásához
elegendő a C szintaxis tömör áttekintése; a
pointerek részletes modelljét később bontjuk ki.
ISO C17 — hosted környezet. Hosted, vagyis teljes C
futtatási környezetben a program indulásakor meghívott függvény neve
main, visszatérési típusa int. Két szabványos
definíciós alakja:
int main(void): a program nem kér hozzáférést a
parancssori argumentumokhoz;int main(int argc, char *argv[]): a program megkapja az
argumentumszámot és az argumentumokat.A második alakban a paraméterként szereplő char *argv[]
típusa char **argv típusra igazodik. Ezért az
int main(int argc, char **argv) is megfelelő alak. A
paraméternevek szabadon választhatók.
Az implementáció további, dokumentált alakokat is támogathat. Ettől
azonban például a void main(void) nem válik hordozható
hosted C belépési ponttá. A main visszatérési értéke a
program befejezési állapotának része, nem elhagyható díszítés.
Az ISO C17 a következőket garantálja:
argc nemnegatív.argv[argc] null pointer.argc > 0, akkor argv[0] a program
nevét reprezentáló sztringre mutat. Ha a név nem érhető el, ez üres
sztring lehet.argc > 1, akkor az
argv[1]–argv[argc - 1] elemek a program
argumentumait reprezentáló sztringekre mutatnak.A sztring nullkarakterrel, '\0'-val lezárt
karaktersorozat. Ez nem azonos a null pointerrel: az üres sztringnek van
érvényes tárolója, amelynek első karaktere '\0'; az
argv[argc] viszont nem sztringre mutat.
Az argumentumsztringek tartománya tehát:
Az argv[argc] csak lezáró jelzőérték, nem nyomtatható ki
%s formátummal. Az argv[0] sem garantált
abszolút útvonal, és nem hiteles bizonyíték a futó végrehajtható fájl
azonosságára.
Linux/shell kapcsolódás. A shell még a program
indítása előtt feldolgozza az idézőjeleket és az argumentumképzést. Az
alpha "two words" szövegből szokásos shellhíváskor két
argumentum lesz, nem három; az idézőjelek általában nem kerülnek bele az
átadott sztringbe.
ISO C17. Az argc és argv
paraméterek, valamint az argumentumtömb által mutatott sztringek
módosíthatók. Az argumentumsztringek nem csupán a belépés pillanatában
használhatók: a program befejezéséig rendelkezésre állnak.
Ez nem jelent korlátlanul növelhető buffert. Egy argumentum meglévő karakterének átírása és egy hosszabb sztring bemásolása különböző művelet; az utóbbi túllépheti a rendelkezésre álló tárolót.
| Hozzáférés | Tulajdonos és élettartam | Változás, érvénytelenítés és hibakimenet |
|---|---|---|
Az eredeti argv argumentumtömb és sztringjei |
A tárolót a futtatókörnyezet biztosítja; a program kölcsönzött hozzáférést kap | A saját módosítás megváltoztathatja az adatot; a tárolóhoz való
hozzáférés a program befejezéséig érvényes. free nem
hívható rá |
argv[argc] |
Lezáró null pointer, nincs mögötte argumentumsztring | Dereferálása nem definiált viselkedés |
A getenv eredménye |
A környezetkezelés által biztosított, kölcsönzött hozzáférés | Későbbi környezetlekérdezés vagy platformspecifikus módosítás megváltoztathatja, illetve érvénytelenítheti; hiány esetén null pointer |
A táblázat az eredeti argumentumtárolóról szól. Ha az
argv paramétert más pointerértékre állítjuk, attól az
újonnan hivatkozott tároló nem örökli az eredeti
élettartam-garanciát.
ISO C17. A környezet szabványos lekérdezési felülete
a <stdlib.h> fejlécben deklarált getenv.
Például a getenv("APP_MODE"):
A visszaadott sztringet a program nem módosíthatja, és nem szabad
free-vel felszabadítania. Bár a függvény visszatérési
típusa char *, a helyi változóhoz célszerű
const char * típust használni. Ez kifejezi a hozzáférés
szerződését.
Egy későbbi getenv hívás felülírhatja a korábban kapott
sztringet. POSIX környezetben a környezetmódosító műveletekhez további
érvényességi korlátok tartoznak. Tartósan megőrzendő értékről ezért
saját másolat szükséges.
Linux-kiterjesztés. Egyes futtatókörnyezetek
támogatják az
int main(int argc, char *argv[], char *envp[]) alakot. Az
envp ilyenkor tipikusan null pointerrel lezárt környezeti
sztringtömb. Ez nem a két hordozható ISO C alak egyike; szabványos C
kódhoz használjuk a getenv felületet.
Linuxon az induló argumentumokat és környezetet az indító folyamat adja át. A gyermek környezetének későbbi módosítása nem írja át a szülő shell környezetét. Mindkét adatforrás külső bemenet: validálást igényelhet, és titkokat is tartalmazhat.
main függvényigA CRT, azaz C runtime startup a C futtatókörnyezet indulását biztosító kód. Nem azonos magával a teljes C standard könyvtárral, bár szorosan együttműködik vele.
Linux/ELF, szokásos glibc-alapú program. Az indulás fő útvonala:
_start belépési kódjára
állítja.main függvényt.main visszatérésekor a futtatókörnyezet normál
befejezést kezdeményez.| Réteg | Belépési szerződés |
|---|---|
| ISO C17 hosted program | A programindítás C-szintű függvénye a main |
| Linux ELF | A belépési cím az ELF fejléc adata; nem szükségképpen a
main címe |
| x86-64 System V ABI labor | Az induló gépi állapot az ABI folyamatinicializálási szerződését követi |
| glibc CRT | Tipikus belépési szimbólum a _start; gyakori
közreműködő a __libc_start_main |
A _start nem szokásos C függvényhívás címzettje: nincs
mögötte olyan rendes hívó, amelyhez egyszerű C return
művelettel visszatérhetne. A main viszont már a létrehozott
C környezetben fut.
A pontos feladatmegosztás libc-, toolchain- és linkelésimód-függő. Statikus programnál nincs szükség ugyanarra a dinamikusinterpreter-útvonalra. A fordítódriver azért fontos, mert rendszerint automatikusan hozzáadja a megfelelő startup objektumokat és könyvtárakat.
Az alábbi teljes program minimuma ISO C17 hosted környezet; maga a C kód nem igényel Linux-kiterjesztést.
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
if (printf("argc=%d\n", argc) < 0) {
return EXIT_FAILURE;
}
for (int i = 0; i < argc; ++i) {
if (printf("argv[%d]=%s\n", i, argv[i]) < 0) {
return EXIT_FAILURE;
}
}
const char *mode = getenv("APP_MODE");
if (printf("APP_MODE=%s\n",
mode != NULL ? mode : "(unset)") < 0) {
return EXIT_FAILURE;
}
if (fflush(stdout) == EOF) {
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}A választott külön laborprofil: Linux/x86-64, System V AMD64 ABI, LP64, glibc 2.39, GCC 14.2.0 vagy Clang 18.1.8. A parancsokban a fordítónevek ezeket a kiadásokat jelöljék. A példát itt nem kíséri fordítási vagy futtatási mérés.
gcc -std=c17 -Wall -Wextra -Wpedantic -O0 -g \
startup_demo.c -o startup_demo
clang -std=c17 -Wall -Wextra -Wpedantic -O0 -g \
startup_demo.c -o startup_demo-clangA szemléltető shellhívás:
APP_MODE=verbose ./startup_demo alpha "two words". Ennek
várt argumentumképe:
| Elem | Várt érték ebben a shellhívásban |
|---|---|
argc |
3 |
argv[0] |
./startup_demo |
argv[1] |
alpha |
argv[2] |
two words |
argv[3] |
null pointer |
getenv("APP_MODE") tartalma |
verbose |
Helyességi indoklás — bizonyításvázlat. A ciklus
csak akkor használja az argv[i] elemet, ha
.
Ezért nem adja át az argv[argc] null pointert a
%s konverziónak. A környezeti értéket külön ellenőrzi;
nincs újabb getenv vagy környezetmódosítás a lekérdezés és
felhasználás között. A program nem szabadít fel kölcsönzött tárolót.
A végső fflush azért szerepel, mert egy korábbi sikeres
printf után a pufferelt kiírás még meghiúsulhat. A program
ezt hibastátusszal jelzi. Éles szolgáltatásban az argumentumok és
környezeti értékek ilyen naplózása titokszivárgást okozhat.
ISO C17. A main kezdeti hívásából való
visszatérés normál programbefejezést jelent, a visszaadott értékkel
végzett exit hatásának megfelelően. A részletes
cleanup-szabályokat a leállásról szóló következő
rész tárgyalja.
A return 0; és a return EXIT_SUCCESS;
sikeres befejezést jelez. Az EXIT_FAILURE sikertelenséget
jelez; numerikus értékét nem kell minden implementációban
1-nek feltételezni. C17-ben a main záró
kapcsos zárójelének elérése is 0 visszaadásának felel meg.
Ez nem általánosítható minden int visszatérési típusú
függvényre.
Linux/POSIX. Normál befejezéskor a szülő számára hozzáférhető kilépési kód az átadott érték alsó nyolc bitje. Nemnegatív esetén:
Így return 256; esetén
,
amit a shell sikernek értelmezhet. Ezért nem helyes tetszőleges belső
hibaszámot változtatás nélkül folyamatstátuszként használni.
Egy közvetlenül futtatott egyszerű parancs után a shell
$? értéke mutatja annak státuszát. Jel miatti megszűnés más
kategória; a Bash például rendszerint
értékkel jelzi az
számú jel miatti befejezést. Ez shellkonvenció, nem a main
visszatérési szabálya.
ISO C17. Freestanding környezetben az induláskor
meghívott függvény nevét és típusát az implementáció határozza meg. Nem
követelmény a main, és nem feltételezhető a teljes hosted
standard könyvtár rendelkezésre állása.
A freestanding program nem egyszerűen „statikusan linkelt program”.
Egy statikus Linux CLI továbbra is lehet hosted C program. Hasonlóképpen
a GCC vagy Clang -ffreestanding kapcsolója önmagában nem
készít megfelelő linkerelrendezést, induló gépi állapotot vagy
eszközinicializálást.
A döntő különbség a vállalt futtatási szerződés, nem a fájlkiterjesztés vagy az, hogy van-e dinamikus linker.
Az argv[1] feltétel nélküli használata helyett előbb
argc > 1 szükséges. A getenv eredményének
közvetlen dereferálása helyett nullpointer-ellenőrzés kell. Az
argumentumok és környezeti értékek felszabadítása helyett a kölcsönzött
hozzáférés szabályát kell követni.
A main és az ELF-belépési cím azonosságának
feltételezése téves rétegkeverés. Ugyanilyen hiba a shell nyolcbites
státuszát az ISO C teljes, int típusú visszatérési
értékével azonosítani.
argv[0]-ról, ha argc == 0, illetve ha
argc == 1? Mit jelent az üres sztring?APP_MODE esetén használja a "standard"
értéket.main címe. A program return 256;
utasítással fejeződik be, a shell mégis sikert mutat. Magyarázd meg
mindkét jelenséget.Ha argc == 0, akkor argv[0] az
argv[argc] lezáró null pointer, nem sztring. Ha
argc == 1, akkor argv[0] érvényes sztringre
mutat, de lehet üres. Az üres sztring első karaktere
'\0'.
Az ellenőrzési feltétel: %s csak az
i < argc feltétellel elért argumentumelemhez
használható.
A következő C17-részlet első ellenőrzése a main elejére
kerüljön; a környezetlekérdezés pedig váltsa fel az eredeti
mode deklarációt:
if (argc > 2) {
return EXIT_FAILURE;
}
const char *mode = getenv("APP_MODE");
if (mode == NULL || mode[0] == '\0') {
mode = "standard";
}A || rövidzáras kiértékelése miatt null pointer esetén
nem történik mode[0] hozzáférés. A sztringliterál statikus
tárolási idejű; nem módosítjuk és nem szabadítjuk fel.
Ellenőrzéskor külön eset legyen a hiányzó, üres és nem üres környezeti érték, valamint két felhasználói argumentum elutasítása.
Az ELF-belépési cím rendszerint a CRT _start kódjára
mutat; a main később kap vezérlést. Az eltérés ezért
önmagában nem hiba.
A normál Linux-kilépési kód pedig:
A shell így sikert láthat. Ellenőrzéskor külön kell vizsgálni az
ELF-belépési pontot, a main elérését és a szülő által
megfigyelt státuszt.
A main a hosted C program belépési szerződése, nem
feltétlenül a folyamat első végrehajtott kódja. Az argumentumok és
környezeti értékek kölcsönzött adatok, eltérő módosíthatósági és
érvényességi szabályokkal. A visszatérési érték pedig több rétegen halad
át: az ISO C befejezési szerződésén, az operációs rendszer
státuszábrázolásán és végül a shell értelmezésén.