Překlad krok za krokem
V této kapitole ukáži, jak se ze zdrojového souboru vytvoří spustitelný program, krok za krokem.
Nejdříve se podívejte na zdrojové soubory, které použiji k výkladu. Jsou velmi jednoduché. Chci na nich ukázat, jak můžete přeložit program, který se skládá z více zdrojových souborů.
Nejdříve funkce main()
:
Hlavičkové soubory, které deklarují funkce number1()
a number2()
.
A nakonec zdrojáky, které definují těla funkcí number1()
a number2()
.
Každý zdroják navíc definuje statickou funkci getSomeNumber()
. (Statické funkce
jsou viditelné jen v rámci svého zdrojového souboru. To pomáhá vyhnout se kolizi jmen.)
Program můžete přeložit a spustit třeba takto:
$ ./main
n1 = 1
n2 = 2
Preprocesor
Zpracování zdrojového souboru preprocesorem je první krok při překladu. O preprocesoru už víte z kapitoly Preprocesor.
K prohnání zdrojového souboru preprocesorem můžete použít buďto cpp
,
nebo gcc
s volbou -E
.
$ clang -E number1.c
Oba příkazy udělají totéž - vypíšou na standardní výstup něco takového:
# 4 "./number1.h" 2
void number1(void);
# 5 "number1.c" 2
static int getSomeNumber() {
return 1;
}
void number1() {
int x;
x = getSomeNumber();
printf("n1 = %i\n", x);
}
Z výstupu jsem vynechal všechny deklarace z <stdio.h>, protože by to bylo moc dlouhé.
Řádky začínající znakem # považujte za komentář. Jak vidíte, preprocesor můžete používat samostatně,
takže ho můžete zneužít i na jiné věci, než je jen programování v C/C++ :-). Všiměte si, že
funkce getSomeNumber()
už nevrací N
, ale 1
.
Assembler
Zdrojový kód se z jazyka C překládá nejdříve do assembleru. Pokud chcete kód v assembleru vidět,
spusťte gcc
s volbou -S
. Výsledkem bude soubor s příponou .s
.
Můžete taky přidat volbu pro optimalizaci kódu (-O2
) a sledovat, jak se výsledek
v assembleru změnil.
$ clang -S -O2 number2.c
$ diff number1.s number2.s
1c1
< .file "number1.c"
---
> .file "number2.c"
3c3
< .globl number1
---
> .globl number2
5,6c5,6
< .type number1,@function
< number1: # @number1
---
> .type number2,@function
> number2: # @number2
10c10
< movl $1, %esi
---
> movl $2, %esi
14c14
< .size number1, .Ltmp0-number1
---
> .size number2, .Ltmp0-number2
20c20
< .asciz "n1 = %i\n"
---
> .asciz "n2 = %i\n"
Jupí, optimalizace funguje!
Z výsledku je patrné, že jediné rozdíly jsou v názvu funkce a v tom, že jedna funkce vrací číslo 1 a druhá číslo
2 (viz řádky s movl
). Jinak optimalizace zabrala a lokální
proměnná x se z funkce number1()
vypařila.
Pokud byste optimalizaci nezapli, našli byste v souboru number1.s
o jednu instrukci assembleru více.
Hodnota z volání getSomeNumber()
by se totiž skutečně nejdříve uložila do x a v druhém kroku
předala funkci printf()
, zatímco v number2.s
se hodnota předává rovnou do funkce printf()
.
Objektový soubor
Dalším krokem je vytvoření objektového souboru. Ten už obsahuje instrukce srozumitelné procesoru, ale není to ještě spustitelný soubor, protože mu chybí některé informace potřebné pro spuštění operačním systémem.
Objektový soubor vytvoříte pomocí přepínače -c
. Můžete přeložit jak zdroják v C/C++,
tak zdroják v assembleru. Výsledkem je soubor s příponou .o
.
$ clang -c number2.s -Wall
$ ls *.o
number1.o number2.o
Linkování
Linkování je proces, při kterém se vezmou objektové soubory a sestaví se z nich výsledný program.
Programu gcc můžete ke kompilaci předat jak zdrojové soubory v C/C++ nebo assembleru, tak i objektové soubory.
Když už máte objektový soubor, původní zdrojový soubor (.c) už nepotřebujete. Ale pořád potřebujete
deklarace funkcí v hlavičkových souborech, abyste mohli funkce z objektových souborů používat (například
main.c používá hlavičkové soubory kvůli funkcím number1()
a
number2()
).
$ ./main
n1 = 1
n2 = 2
K liknování se používá program ld
, který je spouštěn programem gcc
automaticky.
Program ld
potřebuje spoustu složitých voleb, aby správně fungoval,
takže jeho přímé volání se nedoporučuje.
$ ls *.o
main.o number1.o number2.o
$ clang -o main *.o
$ ./main
n1 = 1
n2 = 2
Součástí výsledného souboru (spustitelného programu) budou jen ty funkce, které se v programu využijí. Pokud byste měli třeba v number1.o definované další funkce, které se nikde nevolají, nebudou vám ve výsledném souboru zabírat místo. Objektové soubory můžete klidně smazat, výsledný program už je nepotřebuje.
Závěr
Když nic jiného, tak si z této kapitoly odneste alespoň vytváření objektových souborů (.o). Výhoda je jasná. Nemusíte kompilovat všechny zdrojové soubory najednou, ale můžete hezky odladit a zkompilovat jeden soubor po druhém. Ušetříte tím spoustu času. A to nejen kvůli ladění, ale i samotný překlad z .c do objektového souboru (který probíhá při překladu z .c vždy), zabírá nezanedbatelný čas, zvláště u větších projektů. Později ukáži, jak tento překlad zautomatizovat pomocí nástroje make. Pokud používáte nějaké vývojové prostředí (Visual Sutido, Code::Blocks), tak to se už o takovou automatizaci postará samo.