Programování v jazyce C Preprocesor, varargs, POSIX
Preprocesor C, co není C
Překlad po částech 1. Preprocessing "gcc -E hello.c > hello.i" rozvinutí maker, expanze include 2. Kompilace "gcc -S hello.i" syntaktická kontrola kódu, typicky chybová hlášení 3. Sestavení "as hello.s -o hello.o" assembly do strojového kódu 4. Linkování "gcc hello.o" nahrazení relativních adres absolutními Při běžném překladu proběhnou všechny kroky automaticky, nemusíme pouštět každý zvlášť
Preprocesor - motivace Preprocesor předzpracování zdrojový kód pro překladač Potřebuje překladač zpracovávat poznámky? ne, neovlivní nijak výsledný kód, lze odstranit Musíme mít veškerý kód v jediném souboru? ne, je výhodné dělit do podčástí (knihovny apod.) až před poskytnutí překladači jsou části kódu spojené Jak říct, že funkce printf() opravdu existuje, ale je definovaná v jiném zdrojovém souboru? musíme uvést hlavičku funkce pro překladač (deklarace) zbytečné uvádět manuálně hlavičku funkce printf do každého souboru preprocesor nám umožní snadno vkládat pomocí #include Chceme vždy překládat celý zdrojový kód? ne, můžeme mít např. platformově závislé části s podmíněným překladem
Preprocesor Většina příkazů preprocesoru začíná znakem # Znak # nemusí být na začátku řádku, ale nesmí před ním být žádný další token NE int a = 1; #define DEBUG Dělá textové náhrady nad zdrojovým kódem jazykově nezávislé, preprocesor "neví" nic o syntaxi jazyka C Příkazy preprocesoru jsou z předzpracovaného kódu odstraněny doplňují se značky pro překladač gcc -std=c99 -E -o soubor.i soubor.c
Preprocesor - #include #include "vlozme.h" namísto této řádky se vloží obsah souboru vlozme.h Typicky použijeme pro vkládání hlavičkových souborů (*.h) #include <soubor>... hledá se ve standardních hlavičkových souborech #include "soubor"... hledá se nejprve v aktuálním adresáři Můžeme použít i pro vkládání obsahu jakýchkoli jiných souborů například bitmapu z gimpu :-) #include "soubor2.c" gcc -std=c99 -o pokus soubor1.c soubor2.c Lze (weak symbol) ale zřídka (goto efekt)
Preprocesor - #include *.c - demo soubor2.c int symbol_konflikt(int a, int b) { return a + b; } soubor1.c #include <stdio.h> #include soubor2.c int main(void) { int b = symbol_konflikt(0, 0); printf("b=%d\n", b); } return 0;
Preprocessor makra bez parametrů #define JMÉNO_MAKRA hodnota_makra jméno_makra se v kódu nahradí za hodnota_makra Označení jmen maker velkými písmeny je konvence je zřejmé, co jsou makra a budou tedy nahrazena Často používáno např. pro konstanty #define ARRAYSIZE 10000 hodnota makra jméno makra Pozn.: pro konstanty ale raději const int ARRAYSIZE = 10000; Nahradí se jen samostatné tokeny, nikoli podčásti tokenu int arraysize = ARRAYSIZE; int arraysize = ARRAYSIZEBAD; => bez změny int array [ARRAYSIZE]; => int array [10000];
Rozsah platnosti makra Makro je v kódu platné od řádku jeho uvedení nahrazení proběhne až pro následující řádky pozor, makro může být platné i v dalších souborech (#include) Platnost makra lze zrušit pomocí #undef jméno_makra int value = X; #define X 100 value = X; #undef X value = X; int value = X; value = 100; value = X; Makro lze definovat v kódu nebo jako přepínač překladu #define DEBUG gcc Djméno_makra => gcc DDEBUG gcc Djméno_makra=hodnota_makra => gcc DARRAYSIZE=100
Makro - redefinice Makro může být předefinováno často spíše nezáměrná chyba, proto varování překladače warning: "VALUE" redefined Pokud potřebujete předefinovat, oddefinujte nejprve předchozí Hodnota makra může být prázdná #define VALUE 100 #define VALUE 1000 #define VALUE 100 #undef VALUE #define VALUE 1000 #define DEBUG často použito pro podmíněný překlad (viz. následující)
Preprocesor podmíněný překlad Chceme vždy přeložit celý zdrojový kód? ne nutně, část kódu může vynechat chceme mít zároveň jediný zdrojový kód, ne násobné (nekonzistentní) kopie Např. v ladícím režimu chceme dodatečné výpisy #ifdef DEBUG printf("just testing"); #endif Např. máme části programů závislé na platformě little vs. big endian, Unix vs. Windows #ifdef _WIN32
Ukázka #ifdef #include <stdlib.h> #include <stdio.h> #define VERBOSE int main(void) { int a = 0; int b = 0; scanf("%d %d", &a, &b); #ifdef VERBOSE printf("a=%d b=%d\n", a, b); #endif printf("a+b=%d\n", a + b); Zakomentováním odstraníme dílčí výpis Namísto definice VERBOSE v kódu můžeme použít: Přepínač překladače gcc DVERBOSE Nastavení v QT Creator: DEFINES += VERBOSE Pokud není VERBOSE definován, tento řádek nebude vůbec přítomný ve výsledné binárce } return 0;
Podmíněný překlad doplnění Příkazy preprocesoru pro podmíněný překlad #if, #ifdef, #ifndef, #else, #elif, #endif Podmínky se vyhodnotí v době překladu! nelze použít na testování proměnných a funkcí minimální podpora složitějších podmínek #ifdef X ekvivalent využití operátoru preprocesoru defined #if defined X k dispozici závorkování, logické operátory #if (! defined APPLE ) && (defined UNIX ) printf("snehurka zachranena\n"); #endif
Zamezení opakovanému vkládání souboru Opakované vložení hlavičkového souboru je nepříjemné překladač hlásí násobnou deklaraci funkcí apod. obtížné pohlídat vkládání souboru jen jednou "manuálně" S pomocí podmíněného překladu lze řešit vložení obsahu souboru podmíníme (ne-)existující definicí makra #ifndef JMENOSOUBORU_H ve vkládaném souboru makro definujeme #define JMENOSOUBORU_H #ifndef _STDIO_H_ #define _STDIO_H_ // obsah souboru stdio.h #endif Při prvním vkládání se obsah souboru vloží a zadefinuje makro JMENOSOUBORU_H, které zamezí dalšímu vložení U souborů *.c se typicky nepoužívá nepoužíváme #include "soubor.c" ale #include "soubor.h"
Preprocesor parametrická makra assert() makro nebo funkce?
Preprocesor makra s parametry Makra můžeme použít pro náhradu funkcí #define JMÉNO_MAKRA (argumenty) tělo_makra \ pokračování_těla_makra Preprocesor nahradí (rozvine) výskyty makra včetně jejich zadaných argumentů argumenty makra v definici se nahradí za reálné argumenty při použití makra #define ADD(a, b) (a + b) int main(void) { int z = ADD(5, 7) * 3; return 0; } int main(void) { int z = (5 + 7) * 3; return 0; } #define ADD(a, b) (a + b) int main(void) { int x = 0; int y = 0; int z = ADD(x, y); z = ADD(x, 5.7); } return 0;
Preprocesor makra s parametry #define ADD(a, b) (a + b) int add(int a, int b) { return a + b; } int main(void) { int x = 0; int y = 0; } int z = ADD(x, y); z = add(x, y); return 0; 23 int z = ADD(x, y); 0x00401399 <+85>: mov 0x48(%esp),%eax 0x0040139d <+89>: mov 0x4c(%esp),%edx 0x004013a1 <+93>: lea (%edx,%eax,1),%eax 0x004013a4 <+96>: mov %eax,0x44(%esp) 24 z = add(x, y); 0x004013a8 <+100>: mov 0x48(%esp),%eax 0x004013ac <+104>: mov %eax,0x4(%esp) 0x004013b0 <+108>: mov 0x4c(%esp),%eax 0x004013b4 <+112>: mov %eax,(%esp) 0x004013b7 <+115>: call 0x401d5c <add> 0x004013bc <+120>: mov %eax,0x44(%esp)
Preprocesor problémy s parametry #define je_mocnina_dvou(cislo) \ (!((cislo - 1) & (cislo))) int main(void) { int i = 2; while (i < 8) { printf("%d mocnina 2: %d\n", i, je_mocnina_dvou(i++)); } return 0; } Očekávaný výstup 2 mocnina 2: 1 3 mocnina 2: 0 4 mocnina 2: 1 5 mocnina 2: 0 6 mocnina 2: 0 7 mocnina 2: 0 Reálný výstup 4 mocnina 2: 0 6 mocnina 2: 0 8 mocnina 2: 0
Funkce inline inline int je_mocnina_dvou(int cislo) { return!((cislo - 1) & (cislo)); } //... printf("%d\n", je_mocnina_dvou(i++)); Makra s parametry typicky zavedeny z důvodu rychlosti pokud je standardní funkce, musí se připravit zásobník... Při použití makra vložen přímo kód funkce namísto volání Optimalizující překladač ale může sám vložit kód funkce namísto volání (tzv. inlining) rychlostní optimalizace na úkor paměti (delší kód) překladače jsou obecně v optimalizaci velice dobré! Pomocí klíčového slova inline signalizujeme funkci vhodnou pro vložení překladač ale může ignorovat (jen doporučení) Výrazně snazší ladění než v případě maker!
Makra problémy s typem Makro nemá žádný typ jde o textovou náhradu během preprocessingu velmi náchylné na textové překlepy Např. pozor na #define ARRAYSIZE 10000; int array[arraysize]; hodnotou makra je zde 10000; // int array[10000;]; => chyba, ale odhalí už překladač Preprocesor nemůže kontrolovat typovou správnost zvyšuje se riziko nesprávné interpretace dat Často problém díky automatickým implicitním konverzím díky automatické konverzi překladač neohlásí chybu paměť s konstantou 100 je interpretována jako řetězec #define VALUE 100 printf("%s", VALUE);
Makra problémy s rozvojem Velký pozor na přesný výsledek rozvoje #define ADD(a, b) a + b int main(void) { int z = ADD(5, 7) * 3; return 0; } int main(void) { int z = 5 + 7 * 3; return 0; } používejte preventivní uzávorkování #define ADD(a, b) ((a) + (b)) int main(void) { int z = ADD(5, 7) * 3; return 0; } int main(void) { int z = ((5) + (7)) * 3; return 0; }
Další makra a direktivy LINE, FILE, DATE poskytována preprocesorem literály #error "Required feature unavailable" #pragma ovlivňuje chování překladače #pragma unroll Mnoho dalších v závislosti na verzi překladače a standardu
Makra - shrnutí Makra se vyhodnocují při překladu, nikoli při běhu Snažte se minimalizovat jejich používání #include OK #ifndef SOUBOR_H OK #define MAX 10... raději const int MAX = 10; #define VALUE_T int... lépe typedef int VALUE_T; Používejte inline funkce namísto funkčních maker inline int add(int, int); ne #define ADD(a,b) (a+b) Podmíněný překlad je častý při využití operací závislých na platformě
Makra bonus - operátory # a ## # operátor, převádějící svůj argument na řetězec #define str(_expr) # _expr printf("vyraz %s\n", str(3+4)); // vytiskne Vyraz 3+4 ## operátor, řetězící tokeny #define list(_name, _type) \ typedef struct list_ ## _name ## _item { \ _type value; \ list(intlist, int) // typedef struct list_intlist_item {...
Makra bonus - assert() #define assert(_expr) do { \ if (!(_expr)) { \ fprintf(stderr, "Assertion failed on %s:%d (%s)\n", FILE, LINE, # _expr ); \ raise(sigabrt); \ exit(-1); /* unreachable */ \ } \ } while (0) // prazdny assert zahodi se obsah argumentu #define assert(_expr)
Funkce s proměnným počtem argumentů jak to ta printf() zvládá
Funkce s proměnným počtem argumentů Některé funkce má smysl používat s různými počty a typy argumentů printf("%s%c %d %s", "Hell", 'o', 4, "world"); nemá smysl definovat funkce pro všechny možné kombinace Argumenty na konci seznamu lze nahradit výpustkou... int printf ( const char * format,... ); první argument je formátovací řetězec, dále 0 až N argumentů Výslovně uvedené argumenty jsou použity normálně Argumenty předané na pozici výpustky jsou přístupné pomocí dodatečných maker hlavičkový soubor stdarg.h va_start, va_arg a va_end
Přístup k argumentům 1. Definujeme ve funkci proměnnou typu va_list va_list arguments; 2. Naplníme proměnnou argumenty v proměnné části va_start(arguments, number_of_arguments); 3. Opakovaně získáváme jednotlivé argumenty va_arg(arguments, type_of_argument); 4. Ukončíme práci s argumenty va_end(arguments);
Poznámky k argumentům Seznam argumentů lze zpracovat jen částečně a předat další funkci (která může zpracovat zbytek) Jazyk C neumožňuje zjistit počet argumentů při volání funkce lze přímo předat prvním parametrem: void foo(int num,...); lze odvodit z prvního argumentu: int printf(const char* format,...); format = "%s%c %d %s" -> 4 args, char*,char,int,char*
Proměnný počet argumentů - příklad #include <stdarg.h> #include <stdio.h> #include <stdlib.h> void varfoo(int number,...) { va_list arg; va_start(arg,number); int valueint = va_arg(arg,int); char* valuechar = va_arg(arg,char*); printf("%d dynamic params are: %d, %s\n", number,valueint,valuechar); va_end(arg); return; } int main(void) { varfoo(2,123,"end"); return 0; }
POSIX Konečně něco více než Hello World! pomocí malloc
POSIX rodina norem OS Svázáno s operačními systémy typu UNIX Jazyk C je součástí POSIXu (C vyvinuto pro Unix) Zastřešil předchozí vývoj v systémovém API POSIX.1 (1988) POSIX.1-2008 (2008)
POSIX C library Snaha o vytvoření jednotného okolního prostředí (API) umožňující přenositelnost programů vyžadujících interakci s OS http://en.wikipedia.org/wiki/c_posix_library Knihovna funkcí pro interakci s OS, pokrývá širokou oblast kontrola procesů (spouštění, komunikace, ukončení) práce se vstupními a výstupními zařízeními (pipes...) práce s vlákny, synchronizační mechanismy (mutex) spouštění příkazů shellu...
POSIX - kompatibilita Unix/Linux dobrá dlouhodobá podpora, bez problémů Linux Programmer's Manual ( aisa:~ # man -s 2,3 jmenofunkce ) MS Windows omezená implementace standardu, jen některé distribuce, funkce obvykle začínají _ https://msdn.microsoft.com/en-us/library/z0kc8e3z.aspx MS preferuje využívání svých funkcí Win32 API... Cygwin implementace jádra POSIX normy pro C MinGW podobné Cygwin, ale windows-native backend viz absence %lld
POSIX feature test macros POSIX rozšiřuje standardní knihovnu některé funkce potřeba povolit feature_test_macros 0: #define _POSIX_C_SOURCE 2 1: #include <unistd.h> Položka feature test macros v manuálu Feature Test Macro Requirements for glibc (see feature_test_macros(7)): lstat(): /* glibc 2.19 and earlier */ _BSD_SOURCE /* Since glibc 2.20 */_DEFAULT_SOURCE _XOPEN_SOURCE >= 500 _XOPEN_SOURCE && _XOPEN_SOURCE_EXTENDED /* Since glibc 2.10: */ _POSIX_C_SOURCE >= 200112L
Ohlašování chyb #include <errno.h> Součást standardu jazyka C Globální proměnná int errno Pokud funkce standardní knihovny či POSIXu selže, obnáší chybový kód chybové kódy definovány jako konstanty například pro chyby matematických funkcí EDOM nulujte před použitím funkce, která chybu nastaví Specifikace C99 sekce 7.5 Příkaz man errno
Pohodlnější práce s argumenty getopt #include <unistd.h> Usnadňuje práci s argumenty Iterativní přístup, podpora různých způsobů int getopt(int argc, char * const argv[], const char *optstring) Proměnná optstring řetězec přepínačů Iterativně volána, stav v návratovém kódu <0 konec, jinak přepínač Příkazové řádce./program -r 8 -R odpovídá optstring r:r
Práce se soubory a adresáři #include <dirent.h> Popisovač souborů (file descriptor) index do tabulky souborů v jádře OS (handle) DIR *opendir(const char *dirname); struct dirent *readdir(dir *dirp); int closedir(dir *dirp); Pozor, funkce mohou být stavové
(Ne)stavovost funkcí Jak vrátit jména pro 1001 souborů v adresáři? Jediným funkčním zavoláním? je nutné naformátovat jména do jediného řetězce Co použít jako oddělovač? speciální znak nepraktické, který zvolit? koncová nula? Používá se, jak byste udělali? Zavedení stavové funkce každé zavolání vrátí další soubor např. readdir() vrátí po každém zavolání další soubor v adresáři stav musí být uchováván (OS) a uvolněn!
Zjištění obsahu adresáře 1. Otevření adresáře (funkce opendir) vytvoří stavovou strukturu u OS (DIR) připojenou na daný adresář 2. Postupné procházení adresáře (funkce readdir) každé zavolání vrátí další soubor v adresáři formou struktury (struct dirent) 3. Práce s nalezeným souborem (struct dirent) dirent.d_name např. pomocí C funkce fopen() 4. Ukončení práce s adresářem (funkce closedir) uvolní stavovou strukturu u OS
Výpis obsahu adresáře void PosixPrintFiles(const char* path) { DIR *dir = NULL; if ((dir = opendir(path))) { // connect to directory struct dirent *direntry = NULL; while ((direntry = readdir(dir))!= NULL) {// get next item printf("file %s\n", direntry->d_name); // get name } closedir(dir); // finish work with directory } } Jak rozlišit podadresář od souboru? Jak zjistit další informace o souboru? (čas, práva) Jak projít zanořenou strukturu adresářů?
Rozlišení adresáře od souboru Problém v rozdílné podpoře v Unixu / Windows Linux: struct dirent.d_type 4, 10 adresář, 8 soubor makro S_ISDIR, funkce stat nebo lstat <sys/stat.h> Windows: omezená podpora, dirent.d_type nemusí být dostupný např. není defaultně v MinGW zkusit otevřít položku pomocí opendir() #define DT_UNKNOWN 0 #define DT_DIR 4 #define DT_REG 8 #define DT_LNK 10 pokud se nepodaří, nemusí být adresář (proč?)
Převzato z http://pubs.opengroup.org/onlinepubs/009696699/functions/stat.html struct dirent *dp; struct stat statbuf; struct passwd *pwd; struct group *grp; struct tm *tm; char datestring[256];... // Open directory opendir etc. /* Loop through directory entries. */ while ((dp = readdir(dir))!= NULL) { /* Get entry's information. */ if (stat(dp->d_name, &statbuf) == -1) continue; /* Print out type, permissions, and number of links. */ printf("%10.10s", sperm (statbuf.st_mode)); printf("%4d", statbuf.st_nlink); /* Print out owner's name if it is found using getpwuid(). */ if ((pwd = getpwuid(statbuf.st_uid))!= NULL) printf(" %-8.8s", pwd->pw_name); else printf(" %-8d", statbuf.st_uid); /* Print out group name if it is found using getgrgid(). */ if ((grp = getgrgid(statbuf.st_gid))!= NULL) printf(" %-8.8s", grp->gr_name); else printf(" %-8d", statbuf.st_gid); /* Print size of file. */ printf(" %9jd", (intmax_t)statbuf.st_size); tm = localtime(&statbuf.st_mtime); /* Get localized date string. */ strftime(datestring, sizeof(datestring), nl_langinfo(d_t_fmt), tm); printf(" %s %s\n", datestring, dp->d_name); Úvod } do C, 21.4.2015
Vlákna v POSIXu Vlákna umožňují spustit několik úkolů paralelně při jednom jádře CPU se střídají při více jádrech CPU mohou běžet paralelně Pracovní vlákno (Worker thread) funkce obsahující kód pro vykonání data předaná při spuštění funkci jako argument spuštění potřebného počtu vláken Použití vláken může vyžadovat synchronizaci ochrana před nevhodným souběžným použitím zdrojů zápis do paměti, přístup k souboru... https://computing.llnl.gov/tutorials/pthreads/ http://www.yolinux.com/tutorials/linuxtutorialposixthreads.html
#include <stdio.h> #include <stdlib.h> #include <pthread.h> void *print_message_function( void *ptr ) { char *message; message = (char *) ptr; printf("%s \n", message); } spuštění vlákna funkce pro vlákno zpracování argumentu funkce pro vlákno int main() { pthread_t thread1, thread2; char *message1 = "Thread 1"; char *message2 = "Thread 2"; int iret1, iret2; struktura pro kontrolu vlákna /* Create independent threads each of which will execute function */ iret1 = pthread_create( &thread1, NULL, print_message_function, (void*) message1); iret2 = pthread_create( &thread2, NULL, print_message_function, (void*) message2); počkáme na /* Wait till threads are complete before main continues. Unless we */ dokončení vláken /* wait we run the risk of executing an exit which will terminate */ /* the process and all threads before the threads have completed. */ pthread_join( thread1, NULL); pthread_join( thread2, NULL); argument pro funkci printf("thread 1 returns: %d\n",iret1); printf("thread 2 returns: %d\n",iret2); return 0; } Převzato Úvod do C, a upraveno 21.4.2015 z http://www.yolinux.com/tutorials/linuxtutorialposixthreads.html
Další práce se soubory Vytvoření dočasného souboru FILE* tmpfile(void); Přejmenování a přesunutí souboru int rename(const char *from, const char *to); Smazání souboru či adresáře int remove(const char *pathname); Asynchronní přístup int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
Další funkce POSIXu Naleznete v přednášce Návaznost jazyka C na OS (Šimon Tóth), web předmětu
BONUS lokalizace gettext #include <libintl.h> Výběr katalogu zpráv char * textdomain(const char *domainname) Získání formátovacího řetězce zprávy char * gettext(const char *msgid); Makro _(msg) syntaktický cukr pro gettext(msg) Přeuspořádání argumentů: %2$s %1$s https://fedoraproject.org/wiki/how_to_do_i18n_through_ge ttext