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():

  1. /*------------------------------------------------*/
  2. /* c05preklad/main.c                              */
  3.  
  4. #include "number1.h"
  5. #include "number2.h"
  6.  
  7. int main(void) {
  8.         number1();
  9.         number2();
  10.         return 0;
  11. }
  12. /*------------------------------------------------*/

Hlavičkové soubory, které deklarují funkce number1() a number2().

  1. /*------------------------------------------------*/
  2. /* c05preklad/number1.h                           */
  3. #include <stdio.h>
  4. #define N 1
  5. void number1(void);
  6. /*------------------------------------------------*/
  1. /*------------------------------------------------*/
  2. /* c05preklad/number2.h                           */
  3. #include <stdio.h>
  4. void number2(void);
  5. /*------------------------------------------------*/

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.)

  1. /*------------------------------------------------*/
  2. /* c05preklad/number1.c                           */
  3.  
  4. #include "number1.h"
  5.  
  6. static int getSomeNumber() {
  7.         return N;
  8. }
  9.  
  10. void number1() {
  11.         int x;
  12.         x = getSomeNumber();
  13.         printf("n1 = %i\n", x);
  14. }
  15. /*------------------------------------------------*/
  1. /*------------------------------------------------*/
  2. /* c05preklad/number2.c                           */
  3.  
  4. #include "number2.h"
  5.  
  6. static int getSomeNumber() {
  7.         return 2;
  8. }
  9.  
  10. void number2() {
  11.         printf("n2 = %i\n", getSomeNumber());
  12. }      
  13. /*------------------------------------------------*/

Program můžete přeložit a spustit třeba takto:

$ gcc -o main -Wall main.c number1.c number2.c
$ ./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.

$ cpp number1.c
$ 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 number1.c
$ 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 number1.c -Wall
$ clang -c number2.s -Wall
$ ls *.o
number1.o number2.o

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()).

$ clang -o main main.c -Wall number1.o number2.o
$ ./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.

$ clang -c main.c
$ 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.

Komentář Hlášení chyby
Created: 16.8.2014
Last updated: 21.7.2017
Tato stánka používá ke svému běhu cookies, díky kterým je možné monitorovat, co tu provádíte (ne že bych to bez cookies nezvládl). Také vás tu bude špehovat google analytics. Jestli si myslíte, že je to problém, vypněte si cookies ve vašem prohlížeči, nebo odejděte a už se nevracejte :-). Prohlížením tohoto webu souhlasíte s používáním cookies. Dozvědět se více..