Spis treści Język interpretowany vs język kompilowany

advertisement
Spis treści
1 Język interpretowany vs język kompilowany
2 Program podstawowy
2.1 Pierwszy program w C++
2.2 Pierwszy program w C
2.3 Komentarze w tekście programu
2.4 Instrukcja include
3 Szybki wstęp do C dla znających pythona -- różnice i podobieństwa
3.1 Uruchamianie programu
3.2 Struktura programu
3.3 Wcięcia i inne podstawowe drobiazgi składniowe
3.4 Typy
3.5 Podstawowe operacje w C
3.6 Podstawowe konstrukcje: if, while, for
3.7 Funkcje
3.8 Wskaźniki
3.9 Przykładowy program
3.10 Zadania
3.10.1 Silnia
4 kompilacja (courtesy of wikipedia)
4.1 Zadanie
Język interpretowany vs język kompilowany
Do tej pory pisaliście w pythonie. Jest to język interpretowany. To znaczy wyglądało to tak, że
tworzyliście plik tekstowy, zgodnie z określonymi regułami, w którym zapisywaliście kod źródłowy
programu. Gdy chcieliście wykonać program, wywoływaliście interpreter pythona podając mu wasz
kod źródłowy w postaci tekstowej:
python moj_program.py
W językach kompilowanych (np. C, C++) po utworzeniu kodu źródłowego uruchamia się program
zwany kompilatorem, który w przeciwieństwie do interpretera, nie wykonuje od razu programu,
jedynie tworzy plik z kodem maszynowym. Np.:
gcc program.c -o program
utworzy z naszego kodu źródłowego program.c kod maszynowy program. Dopiero ten plik jest
wykonywalnym programem, zależnym od architektury komputera na którym został utworzony — jest
to kod maszynowy zrozumiały dla danej maszyny. Kompilację wystarczy wykonać raz, i nie potrzeba
już źródeł do uruchamiania programu wykonywalnego. Jednakże po każdej zmianie kodu źródłowego
program należy skompilować jeszcze raz. W przeciwnym razie zmiany te nie zostaną uwzględnione w
działaniu programu.
Program podstawowy
W podstawowym programie musimy mieć funkcję główną main (oraz biblioteki), która zostaje
automatycznie wywołana przy uruchomieniu programu. Składnia funkcji main:
int main(int argc, char *argv[], char *envp[])
{
….
return ;
}
My będziemy korzystać z takiej:
int main(int argc, char *argv[])
{
….
return ;
}
Funkcja main jest funkcją zwracającą wartość typu całkowitego (int przed main). Przy wywołaniu
programu przekazywane są jej następujące argumenty: argc (typu całkowitego) — całkowita liczba
przekazanych parametrów oraz tablica typu znakowego (char *argv[]), w której zapisane są
agrumenty jako napisy (stringi).
Tablice w C++ oraz C, numerowane są od 0 do n-1. Pierwszy argument przekazywany funkcji main,
czyli argv[0], to nazwa samego programu. argv[argc-1] jest ostatnim slowem, ktore wpiszesz,
wywolujac program.
Program, jeżeli wykona się poprawnie powinien zwrócić wartość 0 (zaszłości historyczne).
Pierwszy program w C++
Standardowo pierwszym naszym programem będzie wypisanie "hello world!". Jak to zrobimy w C++?
#include <iostream>
using namespace std;
int main()
{
cout<<"Hello world"<<endl;
return ;
}
Linijka po linijce:
1. Włączamy nagłówek biblioteki standardowej, której m.in. znajdują sie polecenia wypisania na
ekran.
2. Piszemy, że będziemy korzystać z globalnych klas, funkcji i obiektów, zdefiniowanych w
bibliotekach (włączonych).
3. Rozpoczynamy funkcję główną — nasz program. Każdy program, żeby działać musi mieć
funkcję główną. Można tez definiować inne funkcje — będziemy to robić na kolejnych
zajęciach. W najprostszym wypadku nagłówek funkcji main wygląda tak: int main().
4. Otwieramy ciało funkcji głównej {.
5. Wypisujemy na standardowe wyjście napis Hello world i przechodzimy do następnej linijki
poleceniem endl. Moglibyśmy też napisać cout << "Hello world\n"; "\n" jest tzw.
znakiem specjalnym.
6. Funkcja główna jest funkcją zwracająca liczbę całkowitą. Standardowo (jeżeli program się
wykonał poprawnie) zwracamy 0.
7. Zamykamy ciało funkcji głownej }.
Pierwszy program w C
Standardowo pierwszym naszym programem będzie wypisanie "hello world!". Jak to zrobimy w C++?
#include <stdio.h>
int main()
{
print("Hello world!\n")
return ;
}
Linijka po linijce:
1. Włączamy nagłówek biblioteki standardowej, której m.in. znajdują sie polecenia wypisania na
ekran.
2. Rozpoczynamy funkcję główną — nasz program. Każdy program, żeby działać musi mieć
funkcję główną. Można tez definiować inne funkcje — będziemy to robić na kolejnych
zajęciach. W najprostszym wypadku nagłówek funkcji main wygląda tak: int main().
3. Otwieramy ciało funkcji głównej {.
4. Wypisujemy na standardowe wyjście napis Hello world i przechodzimy do następnej linijki
poleceniem \n.
5. Funkcja główna jest funkcją zwracająca liczbę całkowitą. Standardowo (jeżeli program się
wykonał poprawnie) zwracamy 0.
6. Zamykamy ciało funkcji głownej }.
Komentarze w tekście programu
Zarówno w C jak i C++ komentarze jednolinijkowe zaczynają się od podwójnego ukośnika
odwrotnego //. Komentarze można też zawrzeć w /* i */.
Instrukcja include
Pliki nagłówkowe, zarówno systemowe, jak i autorstwa użytkownika, włącza się za pomocą
dyrektywy preprocesora #include:
#include <plik> używana jest w przypadku systemowych plików nagłówkowych. W tym
przypadku plik plik poszukiwany jest w standardowych katalogach — zwykle w
/usr/include.
#include "plik" używana jest w przypadku plików nagłówkowych autorstwa użytkownika.
Poszukiwane są one na początku w katalogu zawierającym kompilowany program, następnie w
katalogach zawartych w cudzysłowach, a następnie w standardowych katalogach, wktórych
znajdują się systemowe pliki nagłówkowe.
Nazwa pliku czy w nawiasach trójkątnych, czy w cudzysłowach traktowana jest jako napis.
Preprocesor nie rozpoznaje ani znaków wskazujących na rozpoczęcie komentarza (/*) ani ukośników
odwrotnych, które są traktowane jako zwykłe znaki, a nie znaki specjalne.
Szybki wstęp do C dla znających pythona -- różnice i
podobieństwa
Uruchamianie programu
Python jest językiem interpretowanym, a C kompilowanym. Dlatego też program pythonowy
wykonujemy podając jako argument komendzie interpretera plik z kodem źródłowym, który jest
następnie interpretowany przez interpreter i wykonywany. Programy pythonowe (a jednocześnie piki
z kodem pythonowym) mają rozszerzenie .py.
python moj_program.py
W C plik z kodem źródłowym ma rozszerzenie .c. Aby wykonać program, musimy pier przerobić plik
z kodem źródłowym na program -- ciąg bajtów oznaczających instrukcje zrozumiałe już bezpośrednio
dla procesora (procesor nie jest w stanie czytać tekstu kodu źródłowego, umie wykonywać proste
instrukcje na rejestrach).
gcc moj_program.c -o moj_program
A dopiero potem wykonać program. (Po fladze -o podajemy nazwę jaka ma zostać nadana
programowi wykonywalnemu. Domyślnie kompilator utworzy program wykonywalny o nazwie a.out)
./moj_program
Struktura programu
W programie pythonowym na początku mieliśmy często instrukcje
import modul
W pewnym sensie odpowiednikiem tego w C jest instrukcja:
#include <modul>
W pythonie do funkcji w modułu odwoływaliśmy się:
modul.funkcja_z_modulu()
W C możemy się bezpośrednio do nazwy funkcji odwoałać:
funkcja_z_modułu();
O ile w pythonie większość rzeczy to była konwencja, czyli importy mogliśmy równie dobrze w
środku pliku z kodem wpisać, o tyle w C instrukcje include faktycznie muszą być na początku, inazej
kompilator zgłosi błąd.
W programie pythonowym mogliśmy pisać dowolne instrukcje w dowolnym miejscu w pliku -- nie
było wymogu definiowania żadnych funkcji itd. W programie w C konieczna jest jedna podstawowa
funkcja main (opisana trochę wyżej). Tym co już wykona skompilowany program będize właśnie
funkcja main. Na zewnątrz tej funkcji możemy sobie podefiniować inne funkcje, i wykonać jeszcze
kilka rzeczy ze ściśle określonej puli, jak deklarowanie zmiennych/typów, ale nie możemy już z taką
dowolnością jak w pythonie wpisać pętli for w dowolnym miejscu w pliku.
Wcięcia i inne podstawowe drobiazgi składniowe
W pythonie do oznaczenia bloku programu znajdującego się na tym samym poziomie używamy wcięć.
W C możemy wcięcia robić dowolnie, natomiast zakres bloku oznaczamy klamerkami: {}
def jakas_funkcja(): # jest dwukropek, komentarz zaznaczamy poprzez #
print "hello world"
#include <stdio.h>
int jakas_funkcja() // nie ma dwukropka, "//" to znak komentarza
{ // blok jest oznaczony nawiasami klamrowi, poniższe wcięcia są tylko dla
ładności kodu
printf("Hello world!\n"); // po każdej linijce konieczny jest średnik
return ;
}
Typy
W pythonie nie ma czegoś takiego jak deklaracja zmiennych. Kiedy potrzebujemy zachować sobie w
pamięci wynik jakichś operacji, piszemy po prostu:
x = moja_funkcja()
nie uprzedzając nikogo wcześniej, że będziemy używać takiej zmiennej jak x.
W C już na początku pisania programu trzeba się zdecydować ile zmiennych będziemy używać i
jakiego będą typy.
int x; //deklaracja zmiennej
x = moja_funkcja();
Podstawowe typy w C:
int
integer, liczba całkowita zapisana na 32 bitach
short
liczba całkowita zapisana na 16 bitach
long
liczba całkowita zapisana na 64 bitach
float
32 bity
double
64 bity (python, którego my używamy jest zaimplementowany w C,
w pythonie do implementacji typu float jest użyty typ double z C)
char
character, 1 bajt
Podstawowe operacje w C
Takie same jak w pythonie:
+ dodawanie
- odejmowanie
* mnożenie
/ dzielenie
= operator przypisania wartości
<
mniejszy
<= mniejszy równy
== równy
!= różny
>= większy równy
>
większy
Różnice (operatory logiczne):
C Python
&& and
¦¦
or
!
not
Podstawowe konstrukcje: if, while, for
W C nie ma typy True i False -- podajemy 0 lub 1, wyrażenia logiczne ewaluują się do 0 lub 1.
if (warunek)
{
printf("True \n");
}
else
printf("False\n");
int i = initial_i;
while (i <= i_max)
{
// jakieś instrukcje
i = i + i_increment;
}
for (i = initial_i; i <= i_max; i = i + i_increment)
{
//jakieś instrukcje
}
Funkcje
typ_zwracany nazwa_funkcji(lista_argumentow)
{
//deklaracje zmiennych lokalnych
//instrukcje
return cos_typu_zwracanego;
}
na przykład:
void fun()
{
printf("fun");
}
int mnozenie(int a, int b)
{
return a * b;
}
Wskaźniki
Oto przykład obrazujący czym są wskaźniki:
int zmienna_x;
int* wskaznik_na_x;
zmienna_x = 42;
wskaznik_na_x = &zmienna_x;
printf("Wartosc wskaznika: %d, wartosc pod wskaznikiem: %d \n",
wskaznik_na_x, *wskaznik_na_x);
*wskaznik_na_x = ;
printf("wartosc x : %d \n", zmienna_x);
Przykładowy program
Program pokazujący jak uważnie trzeba korzystać ze wskaźników:
#include <stdio.h>
int test_wskaznikow(int a)
{
int zmienna_x;
int* wskaznik_na_x;
zmienna_x = a;
wskaznik_na_x = &zmienna_x;
printf("Wartosc wskaznika: %d, wartosc pod wskaznikiem: %d \n",
wskaznik_na_x, *wskaznik_na_x);
*wskaznik_na_x = ;
printf("wartosc x : %d \n", zmienna_x);
return wskaznik_na_x;
}
int main(){
int wsk_adres = test_wskaznikow(42);
printf("funkcja uzyla miejsca w pamieci o adresie %d \n", wsk_adres);
return ;
}
Ten program nie jest napisany do końca prawidłowo, tzn skompiluje się, ale podczas kompilacji
dostaniemy tzw. "warningi", ostrzeżenia, że prawdopodobnie zrobiliśmy coś nie do końca jak należy,
ale czysto teoretycznie program w takiej formie mógłby się wykonać:
user-46-113-227-209:~ magda$ gcc test.c
test.c: In function ‘test_wskaznikow’:
test.c:13: warning: format ‘%d’ expects type ‘int’, but argument 2 has type
‘int *’
test.c:16: warning: return makes integer from pointer without a cast
Chodzi o to, że odwołujemy się bezpośrednio do wskaźnika traktując do jako liczbę całkowitą.
Należało by pierw zrzutować go na odpowiedni typ, czyli poprawić następujące linijki:
printf("Wartosc wskaznika: %ld , wartosc pod wskaznikiem: %d \n",
(long)wskaznik_na_x, *wskaznik_na_x);
return (long)wskaznik_na_x;
Zauważmy teraz, że wykonanie programu wciąż wygląda podejrzanie:
user-46-113-227-209:~ magda$ ./a.out
Wartosc wskaznika: 140734799804988 , wartosc pod wskaznikiem: 42
wartosc x : 0
funkcja uzyla miejsca w pamieci o adresie 1606416956
Wewnątrz funkcji wypisywany jest dużo dłuższy adres z pamięciu, niż w funkcji main. Dlaczego tak
się dzieje? Wskazówka: zwróć uwagę na to, jakiego typu jest wskaźnik wewnątrz funkcji, a jako jaki
typ jest traktowany w funkcji main. Co należałoby poprawić?
Zadania
Silnia
Napisz program obliczający silnię zadanej liczby N. Zdefiniuj funkcję silnia, a następnie wywołaj ją w
funkcji main. W pierwszym podejściu możesz wartość N wpisać "na sztywno" w kodzie. W drugim
podejściu spróbuj pobierać ją jako parametr programu.
kompilacja (courtesy of wikipedia)
GCC (ang. GNU Compiler Collection) to zestaw kompilatorów do różnych języków programowania
rozwijany w ramach projektu GNU i udostępniany na licencji GPL oraz LGPL. GCC jest
podstawowym kompilatorem w systemach uniksopodobnych przy czym szczególnie ważną rolę
odgrywa w procesie budowy jądra Linuksa. Początkowo skrótowiec GCC oznaczał GNU C Compiler,
ponieważ był to kompilator wyłącznie do języka C.
Standardowo używamy kompilator gcc do kompilacji programów napisanych w C (rozszerzenie pliku
c), a g++ do kompilacji programów napisanych w C++ (rozszerzenie pliku cc).
Program gcc (wywoływany podczas kompilacji np. z linii poleceń) odpowiada za przetworzenie
argumentów, uruchomienie odpowiedniego kompilatora właściwego dla języka programowania w
jakim zakodowano plik z kodem źródłowym, wykonanie programu asemblera dla tak otrzymanego
wyniku oraz uruchomienie konsolidatora (linkera) w celu uzyskania pliku wykonywalnego.
Przykładowo dla pliku napisanego w C zostaną wykonane następujące programy: preprocesor cpp,
kompilator cc1, asembler as oraz konsolidator collect2 (dostępny zazwyczaj jako program ld).
Przydatne opcje:
-c, bez linkowania. Otrzymujemy pliki obiektowe *.o, używamy zwykle gdy nasz program
składa się z kilku plików z funkcjami i nagłówkami tychże funkcji (własne biblioteki).
-o (output), podanie nazwy pliku wynikowego: g++ -o hello hello.cc
-Wall wypisz wszystkie ostrzeżenia (Warnings all)
-O optymalizacja (-O, -O0, -O1, -O2, -O3, -Os)(kolejne stopnie optymalizacji)
-g tworzy informację potrzebną do debuggowania w naturalnym dla danego systemu formacie.
Debuggujemy zwykle za pomocą gdb. Opcji -g można używać z opcją -O. gdb można
uruchomić w emacsie (dlatego go tak lubimy).
Zadanie
Napisz dwa programy — hello.c i hello.cc wypisujące "Hello world!" w terminalu. Skompiluj je i
wywołaj. Dodaj do nich komentarze.
Download