Jednou jsem se začal zajímat o obsah zásobníku funkce hlavního procesu v linuxu. Udělal jsem nějaký průzkum a nyní vám předkládám výsledek.

Možnosti popisu hlavní funkce:
1. int main()
2. int main(int argc, char **argv)
3. int main(int argc, char **argv, char **env)
4. int main(int argc, char **argv, char **env, ElfW(auxv_t) auxv)
5. int main(int argc, char **argv, char **env, char **jablko)

Argc - počet parametrů
argv - null-terminální pole ukazatelů na řetězce voleb příkazového řádku
env je null-terminální pole ukazatelů na řetězce proměnných prostředí. Každý řádek ve formátu NÁZEV=HODNOTA
auxv - pole pomocných hodnot (dostupné pouze pro PowerPC)
apple - cesta ke spustitelnému souboru (v MacOS a Darwin)
Pomocný vektor - pole s různými dodatečné informace, jako je efektivní ID uživatele, příznak setuid bit, velikost stránky paměti a podobně.

Velikost segmentu zásobníku lze zobrazit v souboru map:
cat /proc/10918/maps

7ffffffa3000-7ffffffff000 rw-p 00000000 00:00 0

Než zavaděč přenese řízení na main, inicializuje obsah polí parametrů příkazového řádku, proměnných prostředí a pomocného vektoru.
Po inicializaci vypadá horní část zásobníku asi takto pro 64bitovou verzi.
Senior adresa nahoře.

1. 0x7ffffffff000 Horní bod segmentu zásobníku. Volání způsobí segfault
0x7ffffffff0f8 NULA prázdno* 8 0x00"
2. název souboru char 1+ "/tmp/a.out"
char 1 0x00
...
env char 1 0x00
...
char 1 0x00
3. 0x7fffffffe5e0 env char 1 ..
char 1 0x00
...
argv char 1 0x00
...
char 1 0x00
4. 0x7fffffffe5be argv char 1+ "/tmp/a.out"
5. Pole náhodné délky
6. údaje pro auxv prázdno* 48"
AT_NULL Elf64_auxv_t 16 {0,0}
...
auxv Elf64_auxv_t 16
7. auxv Elf64_auxv_t 16 Příklad: (0x0e,0x3e8)
NULA prázdno* 8 0x00
...
env char* 8
8. 0x7fffffffe308 env char* 8 0x7fffffffe5e0
NULA prázdno* 8 0x00
...
argv char* 8
9. 0x7fffffffe2f8 argv char* 8 0x7fffffffe5be
10. 0x7fffffffe2f0 argc dlouhá int 8" počet argumentů + 1
11. Lokální proměnné a argumenty, funkce volané před main
12. lokální proměnné hlavní
13. 0x7fffffffe1fc argc int 4 počet argumentů + 1
0x7fffffffe1f0 argv char** 8 0x7fffffffe2f8
0x7fffffffe1e8 env char** 8 0x7fffffffe308
14. Proměnné místní funkce

“ – Popisy polí jsem v dokumentech nenašel, ale na skládce jsou dobře vidět.

U 32 bitů jsem to nekontroloval, ale s největší pravděpodobností stačí pouze vydělit velikosti dvěma.

1. Přístup k adresám nad horním bodem způsobí chybu Segfault.
2. Řetězec obsahující cestu ke spustitelnému souboru.
3. Pole řetězců s proměnnými prostředí
4. Pole řetězců s možnostmi příkazového řádku
5. Pole náhodné délky. Jeho výběr lze vypnout pomocí příkazů
sysctl -w kernel.randomize_va_space=0
echo 0 > /proc/sys/kernel/randomize_va_space
6. Data pro pomocný vektor (například řetězec "x86_64")
7. Pomocný vektor. Více podrobností níže.
8. Nulové terminálové pole ukazatelů na řetězce proměnných prostředí
9. Nulové pole ukazatelů na řetězce parametrů příkazového řádku
10.Machine word obsahující počet parametrů příkazového řádku (jeden z argumentů "vyšších" funkcí, viz bod 11)
11. Lokální proměnné a argumenty funkcí volaných před main(_start,__libc_start_main..)
12. Proměnné deklarované v main
13. Argumenty hlavní funkce
14. Proměnné a argumenty lokálních funkcí.

Pomocný vektor
Pro i386 a x86_64 nemůžete získat adresu prvního prvku pomocného vektoru, ale obsah tohoto vektoru lze získat jinými způsoby. Jedním z nich je přístup do oblasti paměti bezprostředně po poli ukazatelů na řetězce proměnných prostředí.
Mělo by to vypadat nějak takto:
#zahrnout #zahrnout int main(int argc, char** argv, char** env)( Elf64_auxv_t *auxv; //x86_64 // Elf32_auxv_t *auxv; //i386 while(*env++ != NULL); //najít začátek pomocného vektoru pro ( auxv = (Elf64_auxv_t *)env; auxv->a_type != AT_NULL; auxv++)( printf("addr: %p typ: %lx je: 0x%lx\n", auxv, auxv->a_type, auxv-> a_un .a_val); ) printf("\n (void*)(*argv) - (void*)auxv= %p - %p = %ld\n (void*)(argv)-(void*)(&auxv ) =%p-%p = %ld\n ", (void*)(*argv), (void*)auxv, (void*)(*argv) - (void*)auxv, (void*)(argv ), (void*)(&auxv), (void*)(argv) - (void*)(&auxv)); printf("\n argc kopie: %d\n",*((int *)(argv - 1)); vrátit 0;)
Struktury Elf(32,64)_auxv_t jsou popsány v /usr/include/elf.h. Funkce pro vyplňování struktur v linux-kernel/fs/binfmt_elf.c

Druhý způsob, jak získat obsah vektoru:
hexdump /proc/self/auxv

Nejčitelnější reprezentace se získá nastavením proměnná prostředí LD_SHOW_AUXV.

LD_SHOW_AUXV=1ls
AT_HWCAP: bfebfbff // možnosti procesoru
AT_PAGESZ: 4096 //velikost stránky paměti
AT_CLKTCK: 100 //krátky frekvence aktualizace ()
AT_PHDR: 0x400040 //informace v záhlaví
AT_PHENT: 56
AT_PHNUM: 9
AT_BASE: 0x7fd00b5bc000 //adresa tlumočníka, tj. ld.so
AT_FLAGS: 0x0
AT_ENTRY: 0x402490 //vstupní bod programu
AT_UID: 1000 //ID uživatele a skupiny
AT_EUID: 1000 //nominální a účinné
AT_GID: 1000
AT_EGID: 1000
AT_SECURE: 0 //je nastaven příznak setuid
AT_RANDOM: 0x7fff30bdc809 //Adresa 16 náhodných bajtů,
generované při spuštění
AT_SYSINFO_EHDR: 0x7fff30bff000 //ukazatel na stránku použitý pro
//systémová volání
AT_EXECFN: /bin/ls
AT_PLATFORM: x86_64
Vlevo je název proměnné, vpravo hodnota. Všechny možné názvy proměnných a jejich popisy naleznete v souboru elf.h. (konstanty s předponou AT_)

Návrat z main()
Po inicializaci kontextu procesu se řízení nepřenese do main(), ale do funkce _start().
main() již volá z __libc_start_main. Tento poslední funkce má zajímavou vlastnost – předává se mu ukazatel na funkci, která se má provést po main(). A tento ukazatel přirozeně prochází zásobníkem.
Obecně platí, že argumenty __libc_start_main mají tvar podle souboru glibc-2.11/sysdeps/ia64/elf/start.S
/*
* Argumenty pro __libc_start_main:
*out0:hlavní
*out1: argc
*out2: argv
* out3: init
* out4: fini //funkce volaná po main
* out5: rtld_fini
*out6: stack_end
*/
Tito. abyste získali adresu ukazatele fini, musíte přesunout dvě strojová slova z poslední lokální proměnné main.
Zde je to, co se stalo (operabilita závisí na verzi kompilátoru):
#zahrnout void **ret; neplatný *opustit; void foo()( void (*boo)(void); //ukazatel funkce printf("Přepsat zásobník!\n"); boo = (void (*)(void))leave; boo(); // fini ( ) ) int main(int argc, char *argv, char *envp) ( unsigned long int mark = 0xbfbfbfbfbfbfbfbf; // označení pro práci od ret = (void**)(&mark+2); // extrahování adresy , funkce voláno po dokončení (fini) opustit = *ret; // uložit *ret = (void*)foo; // přepsat návrat 0; // zavolat foo() )

Doufám, že to bylo zajímavé.
Hodně štěstí.

Děkujeme uživateli Xeor za užitečný tip.

Při vytváření konzolové aplikace v programovacím jazyce C++ se automaticky vytvoří řádek velmi podobný tomuto:

int main(int argc, char* argv) // parametry funkce main().

Tento řádek je záhlaví hlavní funkce main() , parametry argс a argv jsou deklarovány v závorkách. Pokud je tedy program spuštěn příkazový řádek, pak je možné tomuto programu předat nějaké informace, k tomu jsou zde parametry argc a argv . Parametr argc je datového typu int a obsahuje počet parametrů předávaných hlavní funkce. Navíc argc je vždy alespoň 1, i když nepředáváme žádné informace, protože jméno funkce je považováno za první parametr. Parametr argv je pole ukazatelů na řetězce. Přes příkazový řádek lze předávat pouze data typu řetězec. Ukazatele a řetězce jsou dvě velká témata, pro která byly vytvořeny samostatné sekce. Veškeré informace jsou tedy přenášeny prostřednictvím parametru argv. Pojďme si vyvinout program, který budeme spouštět přes příkazový řádek. Linka Windows a předat mu nějaké informace.

// argc_argv.cpp: Určuje vstupní bod pro aplikaci konzoly. #include "stdafx.h" #include pomocí jmenného prostoru std; int main(int argc, char* argv) ( if (argc ><< argv<

// kód Kód::Blocks

// Kód Dev-C++

// argc_argv.cpp: Určuje vstupní bod pro aplikaci konzoly. #zahrnout pomocí jmenného prostoru std; int main(int argc, char* argv) ( if (argc > 1)// pokud předáme argumenty, pak argc bude větší než 1 (v závislosti na počtu argumentů) ( cout<< argv<

Po odladění programu otevřete příkazový řádek Windows a přetáhněte spustitelný soubor našeho programu do okna příkazového řádku, na příkazovém řádku se zobrazí úplná cesta k programu (cestu k programu však můžete napsat ručně), po že můžete stisknout ENTER a program se spustí (viz obrázek 1).

Obrázek 1 - Parametry hlavní funkce

Protože jsme program pouze spustili a nepředali jsme mu žádné argumenty, objevila se zpráva Not arguments. Obrázek 2 ukazuje spuštění stejného programu přes příkazový řádek, ale s předáním argumentu Open.

Obrázek 2 - Parametry hlavní funkce

Argumentem je slovo Open , jak můžete vidět z obrázku, toto slovo se objevilo na obrazovce. Můžete předat několik parametrů najednou a oddělit je čárkou. Pokud je nutné předat parametr skládající se z několika slov, musí být uzavřena do dvojitých uvozovek a tato slova pak budou považována za jeden parametr. Obrázek například ukazuje spuštění programu a předává mu argument sestávající ze dvou slov - Funguje to .

Obrázek 3 - Parametry hlavní funkce

A pokud odstraníte uvozovky. Pak uvidíme pouze slovo To. Pokud neplánujete při spouštění programu předávat žádné informace, můžete odstranit argumenty ve funkci main(), můžete také změnit názvy těchto argumentů. Někdy dochází k úpravám parametrů argc a argv, ale vše závisí na typu vytvářené aplikace nebo na vývojovém prostředí.

Nepovinné a pojmenované argumenty

Nepovinné argumenty

C# 4.0 zavádí novou funkci, která zlepšuje pohodlí zadávání argumentů při volání metody. Tento nástroj se nazývá volitelné argumenty a umožňuje definovat výchozí hodnotu pro parametr metody. Tato hodnota se použije ve výchozím nastavení, pokud není pro parametr při volání metody zadán žádný odpovídající argument. Proto není nutné uvádět argument pro takový parametr. Volitelné argumenty usnadňují volání metod, kde jsou na některé parametry aplikovány výchozí argumenty. Mohou být také použity jako "krátká" forma přetížení metody.

Hlavním impulsem pro přidání volitelných argumentů byla potřeba zjednodušit interakci s objekty COM. Několik objektových modelů společnosti Microsoft (například Microsoft Office) poskytuje funkce prostřednictvím objektů COM, z nichž mnohé byly napsány již dávno a byly navrženy pro použití volitelných parametrů.

Příklad použití volitelných argumentů je uveden níže:

Použití systému; pomocí System.Collections.Generic; pomocí System.Linq; pomocí System.Text; jmenný prostor ConsoleApplication1 ( třída Program ( // Argumenty b a c jsou volitelné při volání static int mySum(int a, int b = 5, int c = 10) ( return a + b + c; ) static void Main() ( int sum1 = mySum(3); int sum2 = mySum(3,12); Console.WriteLine("Sum1 = "+sum1); Console.WriteLine("Sum2 = "+sum2); Console.ReadLine(); ) ) )

Je třeba mít na paměti, že všechny volitelné argumenty musí být nutně uvedeny vpravo od požadovaných. Kromě metod lze v konstruktorech, indexerech a delegátech použít volitelné argumenty.

Jednou z výhod volitelných argumentů je, že usnadňují programátorovi zpracovávat komplexní volání metod a konstruktorů. Koneckonců, je často nutné nastavit více parametrů v metodě, než je obvykle požadováno. A v případech, jako je tento, mohou být některé z těchto parametrů nastaveny jako volitelné pečlivým použitím volitelných argumentů. To znamená, že je třeba předat pouze ty argumenty, které jsou v tomto konkrétním případě důležité, a ne všechny argumenty, které by jinak měly být vyžadovány. Tento přístup nám umožňuje racionalizovat metodu a zjednodušit programátorovi manipulaci s ní.

Pojmenované argumenty

Další funkcí, která byla do C# přidána s vydáním .NET 4.0, je podpora tzv pojmenované argumenty. Jak víte, při předávání argumentů metodě musí pořadí, ve kterém se objevují, zpravidla odpovídat pořadí, ve kterém jsou parametry definovány v samotné metodě. Jinými slovy, hodnota argumentu je přiřazena parametru podle jeho pozice v seznamu argumentů.

Pojmenované argumenty jsou navrženy tak, aby toto omezení překonaly. Pojmenovaný argument umožňuje zadat název parametru, kterému je přiřazena jeho hodnota. A v tomto případě už na pořadí argumentů nezáleží. Pojmenované argumenty jsou tedy poněkud podobné dříve zmíněným inicializátorům objektů, i když se od nich liší svou syntaxí. Chcete-li zadat argument podle názvu, použijte následující formu syntaxe:

název_parametru: hodnota

Tady název_parametru označuje název parametru, kterému je hodnota předána. Název_parametru musí být samozřejmě název platného parametru pro volanou metodu.

Programům C můžete předat určité argumenty. Když se na začátku výpočtu zavolá main(), předají se mu tři parametry. První z nich určuje počet argumentů příkazu při přístupu k programu. Druhým je pole ukazatelů na znakové řetězce obsahující tyto argumenty (jeden argument na řetězec). Třetí je také pole ukazatelů na znakové řetězce, slouží k přístupu k parametrům operačního systému (proměnným prostředí).

Každý takový řádek je reprezentován jako:

proměnná = hodnota\0

Poslední řádek lze najít dvěma koncovými nulami.

Pojmenujme argumenty funkce main(): argc, argv a env (jakákoli jiná jména jsou možná). Pak jsou povoleny následující popisy:

main(int argc, char *argv)

main(int argc, char *argv, char *env)

Předpokládejme, že na jednotce A: je nějaký program prog.exe. Pojďme to řešit takto:

A:\>prog.exe soubor1 soubor2 soubor3

Potom argv je ukazatel na řetězec A:\prog.exe, argv je ukazatel na řetězec file1 a tak dále. Na první aktuální argument ukazuje argv a na poslední argv. Pokud argc=1, pak za názvem programu na příkazovém řádku nejsou žádné parametry. V našem příkladu argc=4.

rekurze

Rekurze je způsob volání, při kterém funkce volá sama sebe.

Důležitým bodem při sestavování rekurzivního programu je organizace výstupu. Zde je snadné udělat chybu, že funkce bude důsledně volat sama sebe donekonečna. Rekurzivní proces proto musí krok za krokem problém zjednodušit tak, aby se pro něj nakonec objevilo nerekurzivní řešení. Použití rekurze není vždy žádoucí, protože může vést k přetečení zásobníku.

Funkce knihovny

V programovacích systémech se podprogramy pro řešení běžných problémů spojují do knihoven. Mezi tyto úkoly patří: výpočet matematických funkcí, vstup/výstup dat, zpracování řetězců, interakce s nástroji operačního systému atd. Použití knihovních rutin šetří uživatele nutnosti vyvíjet vhodné nástroje a poskytuje mu doplňkovou službu. Funkce obsažené v knihovnách jsou dodávány s programovacím systémem. Jejich deklarace jsou uvedeny v souborech *.h (jedná se o tzv. include nebo hlavičkové soubory). Proto, jak je uvedeno výše, na začátku programu s knihovními funkcemi by měly být řádky jako:

#zahrnout<включаемый_файл_типа_h>

Například:

#zahrnout

K dispozici jsou také prostředky pro rozšiřování a vytváření nových knihoven s uživatelskými programy.

Globálním proměnným je přiděleno pevné místo v paměti po dobu trvání programu. Lokální proměnné jsou uloženy v zásobníku. Mezi nimi je paměťová oblast pro dynamickou alokaci.

Funkce malloc() a free() se používají k dynamickému přidělování volné paměti. Funkce malloc() paměť alokuje, funkce free() ji uvolní. Prototypy těchto funkcí jsou uloženy v hlavičkovém souboru stdlib.h a vypadají takto:

void *malloc(velikost_t velikost);

void *free(void *p);

Funkce malloc() vrací ukazatel typu void; pro správné použití musí být hodnota funkce převedena na ukazatel na příslušný typ. Při úspěchu funkce vrátí ukazatel na první bajt volné paměti o velikosti. Pokud není dostatek paměti, vrátí se hodnota 0. Operace sizeof() se používá k určení počtu bajtů potřebných pro proměnnou.

Příklad použití těchto funkcí:

#zahrnout

#zahrnout

p = (int *) malloc(100 * sizeof(int)); /* Přidělte paměť pro 100

celá čísla */

printf("Nedostatek paměti\n");

pro (i = 0; i< 100; ++i) *(p+i) = i; /* Использование памяти */

pro (i = 0; i< 100; ++i) printf("%d", *(p++));

zdarma(p); /* Volná paměť */

Před použitím ukazatele vráceného malloc() se musíte ujistit, že je dostatek paměti (ukazatel není null).

Preprocesor

C preprocesor je program, který zpracovává vstup do kompilátoru. Preprocesor se podívá na zdrojový program a provede následující akce: připojí k němu dané soubory, provede substituce a také spravuje podmínky kompilace. Preprocesor je určen pro programové řádky, které začínají symbolem #. Na jeden řádek je povolen pouze jeden příkaz (direktiva preprocesoru).

Směrnice

#define náhrada identifikátoru

způsobí, že následující text programu nahradí pojmenovaný identifikátor textem náhrady (všimněte si chybějícího středníku na konci tohoto příkazu). Tato směrnice v podstatě zavádí definici makra (makra), kde „identifikátor“ je název definice makra a „substituce“ je posloupnost znaků, kterými preprocesor nahradí zadaný název, když jej najde v textu programu. Název makra se obvykle píše velkými písmeny.

Zvažte příklady:

První řádek způsobí, že program nahradí identifikátor MAX konstantou 25. Druhý umožňuje použít v textu místo úvodní složené závorky (() slovo BEGIN.

Všimněte si, že vzhledem k tomu, že preprocesor nekontroluje kompatibilitu mezi symbolickými názvy definic maker a kontextem, ve kterém jsou použity, doporučuje se, aby tyto identifikátory nebyly definovány direktivou #define, ale klíčovým slovem const s explicitním typem indikace (to platí spíše pro C + +):

const int MAX = 25;

(typ int lze vynechat, protože je standardně nastaven).

Pokud direktiva #define vypadá takto:

#define identifier(identifikátor, ..., identifikátor) ​​substituce

a mezi prvním identifikátorem a úvodní závorkou není mezera, pak se jedná o definici substituce makra s argumenty. Například po zobrazení řádku jako:

#define READ(val) scanf("%d", &val)

příkaz READ(y); se zachází stejně jako scanf("%d",&y);. Zde je argument argument a substituce makra se provádí s argumentem.

Pokud jsou v substituci dlouhé definice, které pokračují na dalším řádku, je na konec dalšího pokračování řádku umístěn znak \.

Do definice makra můžete umístit objekty oddělené znaky ##, například:

#define PR(x, y) x##y

Poté PR(a, 3) zavolá substituci a3. Nebo například definice makra

#define z(a, b, c, d) a(b##c##d)

změní z(sin, x, +, y) na sin(x+y).

Znak # umístěný před argument makra označuje, že je převeden na řetězec. Například po směrnici

#define PRIM(var) printf(#var"= %d", var)

následující část textu programu

se převádí takto:

printf("rok""= %d", rok);

Pojďme si popsat další direktivy preprocesoru. Direktivu #include jsme viděli již dříve. Může být použit ve dvou formách:

#include "název souboru"

#zahrnout<имя файла>

Účinkem obou příkazů je zahrnutí souborů se zadaným názvem do programu. První načte soubor z aktuálního adresáře nebo adresáře zadaného jako prefix. Druhý příkaz hledá soubor ve standardních umístěních definovaných v programovacím systému. Pokud soubor, jehož název je napsán v uvozovkách, není v zadaném adresáři nalezen, bude hledání pokračovat v podadresářích zadaných pro příkaz #include<...>. Direktivy #include mohou být vnořeny do sebe.

Další skupina direktiv umožňuje selektivně kompilovat části programu. Tento proces se nazývá podmíněná kompilace. Tato skupina zahrnuje direktivy #if, #else, #elif, #endif, #ifdef, #ifndef. Základní tvar směrnice #if je:

#if konstantní_výraz příkazová_sekvence

Zde se kontroluje hodnota konstantního výrazu. Pokud je pravdivá, provede se daná sekvence příkazů, a pokud je nepravdivá, je tato posloupnost příkazů přeskočena.

Akce direktivy #else je podobná akci příkazu else v jazyce C, například:

#if konstantní_výraz

posloupnost_2

Pokud je konstantní výraz pravdivý, pak se provede sekvence_operátorů_1, a pokud je nepravda, provede se sekvence_operátorů_2.

Direktiva #elif znamená akci typu "else if". Hlavní forma jeho použití je následující:

#if konstantní_výraz

sekvence_příkazů

#elif konstantní_výraz_1

příkaz_sekvence_1

#elif konstantní_výraz_n

sekvence_výroků_n

Tato forma je podobná konstrukci jazyka C formuláře: if...else if...else if...

Směrnice

#ifdef identifikátor

nastavuje, zda je zadaný identifikátor aktuálně definován, tzn. zda byl zahrnut do direktiv ve tvaru #define. Zobrazit řádek

#ifndef identifikátor

zkontroluje, zda zadaný identifikátor není aktuálně definován. Za kteroukoli z těchto direktiv může následovat libovolný počet řádků textu, případně obsahující příkaz #else (nelze použít #elif) a končící řádkem #endif. Pokud je kontrolovaná podmínka pravdivá, pak jsou všechny řádky mezi #else a #endif ignorovány, a pokud je nepravda, pak řádky mezi kontrolou a #else (pokud není žádné slovo #else, pak #endif). Direktivy #if a #ifndef lze vnořit jedna do druhé.

Zobrazit směrnici

#undef identifikátor

způsobí, že zadaný identifikátor bude považován za nedefinovaný, tzn. není vyměnitelný.

Zvažte příklady. Tyto tři směrnice jsou:

zkontrolujte, zda je definován identifikátor WRITE (tj. byl to příkaz ve tvaru #define WRITE...), a pokud ano, pak je jméno WRITE považováno za nedefinované, tzn. není vyměnitelný.

směrnice

#define WRITE fprintf

zkontrolujte, zda není identifikátor WRITE definován, a pokud ano, pak je místo jména fprintf určen identifikátor WRITE.

Direktiva #error je napsána v následujícím tvaru:

#error error_message

Pokud se objeví v textu programu, kompilace se zastaví a na displeji se zobrazí chybová zpráva. Tento příkaz se používá hlavně během fáze ladění. Všimněte si, že chybová zpráva nemusí být uzavřena do dvojitých uvozovek.

Direktiva #line je určena ke změně hodnot proměnných _LINE_ a _FILE_ definovaných v programovacím systému C. Proměnná _LINE_ obsahuje číslo řádku právě prováděného programu. Identifikátor _FILE_ je ukazatel na řetězec s názvem kompilovaného programu. Direktiva #line je napsána takto:

#line number "název souboru"

Číslo je zde jakékoli kladné celé číslo, které bude přiřazeno proměnné _LINE_, název_souboru je volitelný parametr, který přepíše hodnotu _FILE_.

Direktiva #pragma umožňuje předat některé instrukce kompilátoru. Například čára

označuje, že v programu C jsou řetězce jazyka symbolických instrukcí. Například:

Zvažte některé globální identifikátory nebo názvy maker (názvy definic maker). Je definováno pět takových jmen: _LINE_, _FILE_, _DATE_, _TIME_, _STDC_. Dva z nich (_LINE_ a _FILE_) již byly popsány výše. Identifikátor _DATE_ určuje řetězec, který ukládá datum, kdy byl zdrojový soubor přeložen do objektového kódu. Identifikátor _TIME_ určuje řetězec, který ukládá čas, kdy byl zdrojový soubor přeložen do objektového kódu. Makro _STDC_ má hodnotu 1, pokud jsou použity standardně definované názvy maker. Jinak tato proměnná nebude definována.


Někdy při spouštění programu je užitečné předat mu nějaké informace. Obvykle jsou tyto informace předány funkci main() prostřednictvím argumentů příkazového řádku. Argument příkazového řádku je informace, která se zadává do příkazového řádku operačního systému za názvem programu. Chcete-li například začít kompilovat program, musíte na příkazový řádek po výzvě zadat následující:

CC název_programu

název_programu je argument příkazového řádku, který určuje název programu, který se chystáte zkompilovat.

Pro přijetí argumentů příkazového řádku se používají dva speciální vestavěné argumenty: argc a argv . Parametr argc obsahuje počet argumentů na příkazovém řádku a je to celé číslo a vždy je alespoň 1, protože první argument je název programu. A parametr argv je ukazatel na pole ukazatelů na řetězce. V tomto poli každý prvek ukazuje na nějaký argument příkazového řádku. Všechny argumenty příkazového řádku jsou řetězce, takže převod jakýchkoli čísel do požadovaného binárního formátu musí být poskytnut v programu při jeho vývoji.

Zde je jednoduchý příklad použití argumentu příkazového řádku. Na obrazovce se zobrazí slovo Hello a vaše jméno, které je nutné zadat jako argument příkazového řádku.

#zahrnout #zahrnout int main(int argc, char *argv) ( if(argc!=2) ( printf("Zapomněli jste zadat své jméno.\n"); exit(1); ) printf("Ahoj %s", argv) ; vrátit 0 ;)

Pokud jste tento program pojmenovali (jméno) a vaše jméno je Tom, pak pro spuštění programu zadejte do příkazového řádku jméno Tom. V důsledku spuštění programu se na obrazovce objeví zpráva Ahoj, Tome.

V mnoha prostředích musí být všechny argumenty příkazového řádku navzájem odděleny mezerou nebo tabulátorem. Čárky, středníky a podobné znaky se nepovažují za oddělovače. Například,

Běžte, utíkejte

se skládá ze tří znakových řetězců, zatímco

Eric, Rick, Fred

je jednoznakový řetězec – čárky se obecně nepovažují za oddělovače.

Pokud řetězec obsahuje mezery, pak v některých prostředích může být řetězec uzavřen do dvojitých uvozovek, aby se zabránilo vytvoření více argumentů. V důsledku toho bude celý řetězec považován za jeden argument. Další informace o nastavení voleb příkazového řádku ve vašem operačním systému naleznete v dokumentaci k danému systému.

Je velmi důležité správně deklarovat argv. Nejčastěji to dělají takto:

Char *argv;

Prázdné hranaté závorky označují, že pole má neurčitou délku. Nyní můžete přistupovat k jednotlivým argumentům indexováním pole argv. Například argv ukazuje na první řetězec znaků, což je vždy název programu; argv ukazuje na první argument a tak dále.

Dalším malým příkladem použití argumentů příkazového řádku je níže uvedený odpočítávací program. Tento program odpočítává od nějaké hodnoty (zadané na příkazovém řádku) a pípne, když dosáhne 0. Všimněte si, že první argument obsahující počáteční hodnotu je převeden na celočíselnou hodnotu pomocí standardní funkce atoi () . Pokud je druhým argumentem příkazového řádku (a vezmeme-li název programu jako třetí argument) řetězec "display" (výstup na obrazovku), pak se zobrazí výsledek odpočítávání (v opačném pořadí). na obrazovce.

/* Program pro zpětné počítání. */ #zahrnout #zahrnout #zahrnout #zahrnout int main(int argc, char *argv) ( int disp, počet; if(argc<2) { printf("В командной строке необходимо ввести число, с которого\n"); printf("начинается отсчет. Попробуйте снова.\n"); exit(1); } if(argc==3 && !strcmp(argv, "display")) disp = 1; else disp = 0; for(count=atoi(argv); count; --count) if(disp) printf("%d\n", count); putchar("\a"); /* здесь подается звуковой сигнал */ printf("Счет закончен"); return 0; }

Všimněte si, že pokud nejsou zadány argumenty příkazového řádku, zobrazí se chybová zpráva. Programy s argumenty příkazového řádku často dělají následující: když uživatel spustí tyto programy bez zadání požadovaných informací, zobrazí se pokyny, jak správně zadat argumenty.

Chcete-li získat přístup k jednomu znaku v jednom z argumentů příkazového řádku, zadejte druhý index do argv. Například následující program vypíše znak po znaku všechny argumenty, se kterými byl volán:

#zahrnout int main(int argc, char *argv) ( int t, i; for(t=0; t

Pamatujte, že první index argv poskytuje přístup k řetězci a druhý index poskytuje přístup k jeho jednotlivým znakům.

Obvykle se argc a argv používají k předání počátečních příkazů programu, které bude potřebovat při spuštění. Například argumenty příkazového řádku často určují data, jako je název souboru, možnost nebo alternativní chování. Použití argumentů příkazového řádku dává vašemu programu "profesionální vzhled" a usnadňuje použití v dávkových souborech.

Názvy argc a argv jsou tradiční, ale nejsou povinné. Tyto dva parametry ve funkci main() můžete pojmenovat, jak chcete. Některé kompilátory mohou také podporovat argumenty -additional pro main(), takže se ujistěte, že si prostudujte dokumentaci ke svému kompilátoru.

Když program nevyžaduje parametry příkazového řádku, je nejběžnější explicitně deklarovat funkci main() jako bez parametrů. V tomto případě je klíčové slovo void použito v seznamu parametrů této funkce.