Zadania

  1. Znajdź funkcję, która występowała w standardzie C, ale została z niego usunięta

Kod źródłowy należy pokazać na zajęciach.

Minimalny przykład

Stwórz plik tekstowy 01_min.c o następującej zawartości:

/* Poniższy program wypisuje ciąg znaków „hello world” */
#include <stdio.h>

int main()
{
        printf("hello, world\n");
        return 0;
}

Następnie skompiluj z wykorzystaniem programu cc. W terminalu (kbd:[Ctrl+`] w Visual Studio Code) uruchom następujące polecenie:

cc -o 01_min 01_min.c

A następnie uruchom program:

./01_min

Omówienie

Plik 01_min.c jest plikiem źródłowym (ang. source file) zawierającym kod w języku C. Rozszerzeniem plików źródłowych języka C jest .c.

Plik rozpoczyna się od komentarza, który dokumentuje zawartość programu. Język nie wymaga dokumentowania kodu - jest to dobra praktyka. Zastosowano komentarz blokowy (ang. block comment). Komentarz blokowy rozpoczyna się ciągiem znaków /\* i kończy */. Komentarze blokowe nie mogą być zagnieżdżone:

/* wewnątrz komentarza /* nadal wewnątrz */ poza */ poza

Następnie dołączany jest plik nagłówkowy (ang. header file) będący częścią biblioteki standardowej języka C. stdio.h zawiera deklaracje funkcji realizujących operacje wejścia/wyjścia, w tym użytej poniżej printf.

Kolejno deklarowana i definiowana jest funkcja main. Funkcja main jest funkcją, która jest uruchamiana w momencie uruchomienia programu. Posiada ona kilka możliwych sposobów definicji, w zależności od przyjmowanych przez program argumentów. W aktualnej formie argumenty przekazane do programu są ignorowane. Deklaracja funkcji składa się z następujących komponentów:

typ-zwracany nazwa-funkcji(argument1, argument2, ...);

Funkcja int main() nie wymaga argumentów i zwraca wartość całkowitą (int - integer). Oznacza to, że przed opuszczeniem funkcji musi zostać zwrócona wartość - stąd kończące jej ciało return 0;. Zgodnie z konwencją systemów Unixowych, wartość 0 oznacza pomyślne zakończenie wykonywania programu (wartości różne od 0 oznaczają zakończenie programu błędem). Funkcje, które nie zwracają wartości mają określony typ zwracany jako void.

W ciele funkcji, zawartym pomiędzy nawiasami klamrowymi ({ i }), wywoływana jest funkcja printf (man,window=_blank, cppreference,window=_blank). Pierwszym argumentem funkcji jest łańcuch znaków określający tzw. format - specyfikację co funkcja ma wypisać i jakich kolejnych argumentów może wykorzystywać. Funkcja wypisuje podany tekst na standardowe wyjście, będące w tym przypadku ekranem terminala w edytorze VS Code.

W łańcuchu znaków "hello, world\n" wykorzystano specjalny symbol \n oznaczający znak nowej linii. Funkcja printf wypisuje wyłącznie przekazaną do niej treść.

Kompilator cc

cc jest domyślnym kompilatorem języka C w systemach opartych o standard POSIX (m.in. dystrybucje Linuxa, macOS). Podstawowym parametrem jest przekazywana nazwa pliku źródłowego, zawierający kod w języku C. Poprzez parametr -o można wybrać nazwę wyjściowego pliku wykonywalnego - domyślną jest a.out.

Aby sprawdzić jaki kompilator jest kompilatorem domyślnym można użyć polecenia which i ls:

$ ls -l $(which cc)
lrwxrwxrwx 1 root root 3 lut  8 11:41 /usr/bin/cc -> gcc

Język C

Wykorzystanie

Standaryzacja

Język C jest aktywnie rozwijanym standardem ISO. Rozwijany jest przez międzynarodową komisję JTC1/SC22/WG14. Do publicznego dostępu dostępne są szkice standardów, a proces pracy komisji (w tym propozycje rozszerzania i zmieniana języka) są w większości jawne.

Poniżej opisano kolejno wydane standardy języka C wraz z głównymi funkcjonalnościami:

  • „The C Programming Language”, Brian Kernighan, Dennis Ritchie (1978) - pierwsza nieformalna specyfikacja języka C stworzona przez oryginalnych projektantów i programistów języka C (oraz systemu Unix)

  • Standard C89 (1989) - pierwsza formalna specyfikacja języka C, stworzona przez American National Standard Institute (ANSI)

  • Standard C99 (1999) - rozszerzenie standardu C89 przez ISO, m.in. pozwalające na mieszanie instrukcji i deklaracji wewnątrz funkcji, komentarze liniowe //, literały struktur

  • Standard C11 (2011) - wyrażenia generyczne oraz wielowątkowość (jako element standardu)

  • Standard C17 (2017) - poprawki do standardu C11, bez nowych funkcjonalności

  • Standard C23 (2023) - dołączanie plików binarnych przez #embed i funkcje biblioteczne wykonujące operacje bitowe

Kompilatory języka C oferują możliwość wyboru standardu wg którego program zostanie skompilowany. Najpowszechniejszą opcją jest parametr -std=<wersja>, gdzie wersja to m.in. wymienione powyżej standardy (zapisywane z użyciem małego c). Kompilatory mogą oferować dodatkowe wersje, w tym wsparcie planowanych przyszłych wersji (np. c2y jako oznaczenie kolejnej wersji języka C), wersji specyficznych dla danego kompilatora (np. gnu11 jako oznaczenie wersji zdefiniowanej przez kompilator GNU C - gcc). Rekomendowane jest używanie wyłącznie wersji języka C określonych przez standard ISO w celu zapewniania kompatybilności kodu źródłowego pomiędzy różnymi kompilatorami i systemami operacyjnymi.

Bezpieczeństwo

Szerokie zastosowanie komputerów, w tym w ramach infrastruktury krytycznej prowadzi do konieczności rozpatrywania języków programowania w kontekście cyberbezpieczeństwa. Bezpieczeństwo języka programowania można zdefiniować przez kilka wskaźników:

  1. możliwość modelowania wymagań zewnętrznych (wymagania biznesowe) wewnątrz danego języka

  2. posiadanie mechanizmów ochrony przed typowymi błędami osób programujących w danym języku/paradygmacie

  3. możliwość statycznej analizy kodu źródłowego

  4. możliwość formalnej analizy kodu źródłowego (w tym formalny dowód poprawności programu)

  5. łatwość manualnej analizy kodu źródłowego

Założenia projektowe języka C - prostota, uniwersalność, prosty model kompilacji - utrudniają realizację niektórych z powyższych założeń. Przykładem może być prostota języka ułatwia manualną analizę kodu źródłowego, ale utrudnia formalną weryfikację czy możliwość modelowania wymagań biznesowych.

Rekomendowane jest wykorzystywanie dodatkowych narzędzi weryfikujących poprawność tworzonych programów, jak i wykorzystywanie opcji kompilatora pozwalających na wykrywanie niebezpiecznych zachowań programu.

Podstawy języka C

Funkcja printf

Funkcja printf przyjmuje tzw. format, będący specyfikacją tego co ma być wypisane. Sekwencja znaków rozpoczynająca się od % ma specjalne znaczenie:

  • %% oznacza wypisanie znaku %

  • %d oznacza wypisanie kolejnego argumentu całkowitoliczbowego

  • %c oznacza wypisanie wartości liczbowej jako znaku ASCII

  • oraz inne. Pełna lista dostępna jest w ramach dokumentacji funkcji printf: (man,window=_blank, cppreference,window=_blank). Kolejne będą wprowadzane w miarę potrzeb.

Przykładowy program, pokazujący zastosowanie podstawowego formatu w funkcji printf:

#include <stdio.h>

int getRandomNumber()
{
        return 4; /* chosen by fair dice roll
                     guaranteed to be random */
}

int main()
{
        printf("printing some");
        printf(" text\n");
        printf("%d + %d = %d\n", 1, 2, 3);
        printf("Random number for today is %d\n", getRandomNumber());
}