Laboratorium wstępne do zajęć z SOP1. Środowisko wykonania programu POSIX. #
Pierwsze zajęcia wymagają od ciebie pewnego przygotowania, jednak głównie mają służyć wyjaśnieniu wszelkich wątpliwości co do zasad prowadzenia zajęć i konfiguracji środowiska na dalsze laboratoria. Przeczytaj proszę informacje podane w działach GIT, program, regulamin i harmonogram. Zerknij też na materiały pomocnicze.
W dziale z materiałami znajdziesz tutoriale do samodzielnego wykonania w ramach przygotowania do kolejnych zajęć. Można też się skonsultować droga mailową lub osobiście w czasie dyżurów prowadzących laboratoria.
W ramach zerowych laboratoriów zrobimy proste ćwiczenie symulujące pracę na punktowanych laboratoriach. Zadanie będzie wymagać od ciebie znalezienia kilku błędów i rozszerzenia programu o prostą funkcjonalność. Dzięki temu będzie można sprawdzić, jak twoje środowisko sprawuje się w akcji. W przypadku znalezienia problemów będzie można je wyeliminować przed pierwszymi punktowanymi laboratoriami.
Przygotowanie środowiska #
W przeciwieństwie do poprzednich przedmiotów nie wymagamy żadnego konkretnego środowiska. Niemniej jednak odpowiedni edytor powinien potrafić:
- wyświetlać błędy kompilacji na żywo wraz z kodem (co powoduje, że dowiadujemy się o nich znacznie szybciej, jak również pozwala nam znacznie łatwiej znaleźć odpowiedni fragment kodu),
- podpowiadać nazwy funkcji (przydatne przy wpisywaniu dłuższych nazw),
- dawać możliwość uruchomienia pisanego programu z debuggerem.
Przykładowym edytorem, który dobrze się sprawdzi, jest Visual Studio Code (nie mylić z Visual Studio) oraz QT Creator, które są częstym wyborem ze względu na niski próg wejścia. Innymi częstymi wyborami są Emacs oraz Vim (oraz niedostępny domyślnie w laboratorium Neovim), które jednak cechują się wyższym progiem wejścia. Wszystkie wymienione edytory wymagają dodatkowej konfiguracji, którą warto przeprowadzić przed pierwszym punktowanym laboratorium. Oczywiście warto, żeby konfiguracja edytora w domu była podobna do tej laboratorium.
Nie zalecamy korzystania z większych środowisk, takich jak na przykład CLion, osobom które dobrze ich nie znają.
W przeciwnym wypadku ich dodatkowe funkcje mogą bardziej przeszkadzać niż pomagać.
Ponadto nie wolno używać środowisk opartych na przeglądarce,
takich jak na przykład https://www.onlinegdb.com.
Wynika to z tego, że przechowują one kod wyłącznie w przeglądarce,
co w przypadku problemów ze stabilnością komputera może skutkować utratą kodu i
koniecznością zaczęcia od nowa.
Jeżeli Państwo będą korzystać z Visual Studio Code, to może pojawić się problem, w którym edytor podświetla część nazw jako nieznanych mimo tego, że program się kompiluje (zwykle są to nazwy związane z sygnałami, na przykładsigset_torazSIG_BLOCK). Rozwiązaniem tego jest znalezienie wC/C++: Edit Configurations (UI)ustawieniaC standardi zmiana go z opcji zaczynającej się odcna odpowiadającą opcję zaczynającą się odgnu, na przykładgnu17zamiastc17.
Zadania przygotowujące do laboratorium #
Uwagi wstępne:
- To jest bardzo łatwy tutorial, ale za to długi, kolejne będą coraz trudniejsze i krótsze
- Szybkie przejrzenie tutoriala prawdopodobnie nic nie pomoże, należy samodzielnie uruchomić programy, sprawdzić jak działają, poczytać materiały dodatkowe takie jak strony man. W trakcie czytania sugeruję wykonywać ćwiczenia a na koniec przykładowe zadanie.
- Na żółtych polach podaję dodatkowe informacje, niebieskie zawierają pytania i ćwiczenia. Pod pytaniami znajdują się odpowiedzi, które staną się widoczne dopiero po kliknięciu. Proszę najpierw spróbować sobie odpowiedzieć na pytanie samemu a dopiero potem sprawdzać odpowiedź.
- Pełne kody do zajęć znajdują się w załącznikach na dole strony.
- Materiały i ćwiczenia są ułożone w pewną logiczną całość, czasem do wykonania ćwiczenia konieczny jest stan osiągnięty poprzednim ćwiczeniem, dlatego zalecam wykonywanie ćwiczeń w miarę przyswajania materiału.
- Materiały zakładają, że umiesz używać języka C w stopniu podstawowym (tzn. umiesz operować na pamięci, przetwarzać stringi itp.). Jeżeli nie czujesz się pewnie z tymi operacjami, polecam przypomnieć sobie materiał z przedmiotu Programowanie 1.
- Większość ćwiczeń wymaga użycia konsoli poleceń, zazwyczaj zakładam, że pracujemy w jednym i tym samym katalogu roboczym więc wszystkie potrzebne pliki są “pod ręką” tzn. nie ma potrzeby podawania ścieżek dostępu.
- Czasem podaję znak $ aby podkreślić, że chodzi o polecenie konsolowe, nie piszemy go jednak w konsoli np.: piszę “$make” w konsoli wpisujemy samo “make”.
- To, co ćwiczymy, wróci podczas kolejnych zajęć. Jeśli po zajęciach i teście coś nadal pozostaje niejasne, proszę to poćwiczyć. Prowadzący także mogą pomóc rozwiać wątpliwości.
Zadanie 1 - stdout #
Cel: Napisać i skompilować za pomocą programu make najprostszy program używający standardowego strumienia wyjścia
Co student musi wiedzieć:
- man 3p stdin
- man 3p printf
- man stdlib.h
- man make
- Tutorial na temat gcc i make
Absolutnie nie doradzam szukania stron man przez googla, bo często znajdują się stare dokumenty a czasem dokumentacje innych systemów (HP-UX,solaris), które mogą się różnić od standardu i zawierać błędy. Najlepiej używać lokalnego systemu man na stacjach w laboratoriach.
Dokumentacja systemowa man podzielona jest na sekcje, opis tych sekcji jest podany na stronie opisu komendy man.
Uwaga! Proszę szukać informacji głównie na stronach posix (3p), a nie na stronach dokumentacji implementacji Linuksowej (2 i 3), różnice mogą być bardzo duże, obowiązuje znajomość standardu przed znajomością implementacji
Gdzie znaleźć dokumentację samego polecenia man?Odpowiedź
Jeśli ćwiczysz na własnym systemie Linux, upewnij się, że masz zainstalowane strony z dokumentacją posix, jeśli nie to z pomocą “Wujka Google” sprawdź jak doinstalować te strony i zrób to.
Czemu wpisanie man printf nie pomoże nam w zrozumieniu funkcji printf?Odpowiedź
rozwiązanie prog1.c:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
printf("Hello world\n");
return EXIT_SUCCESS;
}
Zwróćcie uwagę na konieczność zwrócenia kodu wyjścia w funkcji main, chociaż można zmienić int na void to kłóci się to z wymogami co do wartości, jakie mają zwracać wszystkie procesy wg POSIX.
\n na końcu tekstu nie jest przypadkowe, bez tego po wykonaniu programu w konsoli kolejny tzw. znak zachęty (zazwyczaj
$) nie pojawi się w nowej linii. Dobrą praktyką w systemach UNIX jest kończenie strumieni tekstowych oraz plików tekstu
znakiem nowej linii. Drugi aspekt to chęć opróżnienia bufora standardowego strumienia - stdout i stderr są buforowane
liniami, będzie jeszcze o tym mowa.
Zamiast podawać zero jako wartość zwracaną w razie pełnego powodzenia programu lepiej korzystać ze zdefiniowanych
stałych: EXIT_SUCCESS i EXIT_FAILURE, znajdziecie je w man stdlib.h
Skąd wiadomo jakie pliki nagłówkowe trzeba włączyć?Odpowiedź
Skompiluj program poleceniem “make prog1”, używasz w ten sposób domyślnego szablonu kompilacji programu GNU make.
Uruchom program wynikowy. Czemu taki sposób kompilacji będzie dla nas nieprzydatny?Odpowiedź
rozwiązanie Makefile
all: prog1
prog1: prog1.c
gcc -Wall -fsanitize=address,undefined -o prog1 prog1.c
.PHONY: clean all
clean:
rm prog1
Struktura pliku Makefile jest opisana w tutorialu podanym na początku ćwiczenia, najważniejsze dla nas to pamiętać o obowiązkowym tabulatorze przed instrukcją kompilacji, pierwszy cel w pliku jest jednocześnie domyślnym celem (make == make all w powyższym przykładzie), cele opisane jako PHONY nie są powiązane z fizycznie istniejącymi plikami.
Nazwa każdego celu w makefile (poza .PHONY) musi być powiązana z plikiem wynikowym kompilacji (lub linkowania), częstym błędem studentów jest zapominanie o konsekwentnej zmianie wszystkich nazw w pliku Makefile podczas kopiowania powyższego pliku do innych przykładów!
Jak za pomocą programu make i podanego Makefile usunąć stary plik wykonywalny?Odpowiedź
Jak za pomocą programu make i podanego Makefile przeprowadzić kompilacje?Odpowiedź
Jak przekierować wyjcie tego programu do pliku?Odpowiedź
Jak teraz wyświetlić ten plik?Odpowiedź
Zrób kopię programu prog1.c, niech się nazywa prog1b.c. Przerób plik Makefile tak, aby kompilował ten nowy plik do binarki o nazwie prog1b, zwróć uwagę, aby przy faktycznej kompilacji (a nie tylko w Makefile) była obecna flaga -Wall. Jeśli uda Ci się to za pierwszym razem to spróbuj sprowokować błąd celowo zmieniając niektóre prog1b z powrotem na prog1 w różnych konfiguracjach.
Zadanie 2 - stdin, stderr #
Cel: Rozwinąć prog1.c tak, aby wypisywał na ekran powitanie dla imienia podanego z linii poleceń, imiona powyżej 20 znaków powinny generować błąd programu (natychmiastowe wyjście i komunikat)
Co student musi wiedzieć:
- man 3p fscanf
- man 3p perror
- man 3p fprintf
Dla ułatwienia pisania dodajmy makro:
#define ERR(source) (perror(source),\
fprintf(stderr, "%s:%d\n", __FILE__, __LINE__),\
exit(EXIT_FAILURE))
-Zwróć uwagę na makra __FILE__ i __LINE__, pokazują lokalizację wywołania w pliku źródłowy, to cecha preprocesora C.
- Brak średnika po makro jest celowy.
- Komunikaty o błędach zawsze wypisujemy na stderr.
- W reakcji na większość błędów kończymy program, stąd exit.
dodatkowy kod do prog2.c
char name[22];
scanf("%21s", name);
if (strlen(name) > 20) ERR("Name too long");
printf("Hello %s\n", name);
Przerób Makefile z poprzedniego przykłady tak, aby można było skompilować ten program.
Uruchom i przetestuj program.
Czemu w kodzie pojawia się 21 jako rozmiar maksymalny w formatowaniu scanf (%21s)?Odpowiedź
Czemu deklarujemy 22 jako rozmiar tablicy na ciąg znaków, skoro czytamy najwyżej 21 znaków?Odpowiedź
Jak można zmienić sposób wywołania tego programu tak, aby komunikat o ewentualnym błędzie wykonania nie pojawił się na
ekranie?Odpowiedź
Gdy podajemy zbyt długie imię, pojawia się komunikat “Name too long: Success”, czemu success? Zmienna errno nie jest ustawiana, bo to błąd w naszym kodzie, a nie w wywołaniu funkcji bibliotecznej - makro jest napisane przyszłościowo pod kątem błędów w funkcjach.
Gdy podamy podwójne imię np.: “Anna Maria” program wypisze tylko pierwsze, w scanf prosimy o jeden wyraz, a nie całą linię. Całe linie będziemy czytać za chwilę.
Program nie zaczyna się od zapytania użytkownika o imię, tylko od razu oczekuje danych. Takie podejście jest bardzo UNIXowe, takie programy lepiej nadają się do użycia w skryptach lub w przetwarzaniu danych z plików (następne zadanie). Na drugim biegunie jest podejście interaktywne (bardziej w stylu Windows), z grzeczną prośbą o podanie imienia na początku. Wybór stylu interakcji zależy od zastosowania, można połączyć oba, jeśli rozpoznamy (f. isatty) czy program działa interaktywnie na konsoli, czy może jest to tzw. przetwarzanie wsadowe.
W prog2.c nie wykorzystano wartości zwracanej przez scanf. Niestety zbyt długi wyraz na stdin (dla specyfikacji %s) nie jest traktowany jako błąd i scanf() zwraca 1 (tak jak i dla wyrazu krótszego od 21 znaków)
W programie występują tzw. “magic numbers” 20,21,22. Nie powinno się ich tworzyć to zły styl programowania, trudno potem zmienić limit z 20 linii na 30 bez analizy kodu. Powinno się dodać stałą (#define MAXL 20) i wszystko od niej wyprowadzić (MAXL+1 , MAXL+2)
Musimy wcisnąć enter, aby program mógł wykonać scanf, wynika to z liniowego buforowania terminala, natychmiastowe czytanie znaków z klawiatury wymaga sporo pracy w tym usunięcie buforowania terminala (man 3p tcsetattr), dodania flagi O_NONBLOCK i czytania bezpośrednio z deskryptora, a nie strumienia. Zazwyczaj łatwiej do tego użyć biblioteki (np. ncurses). Nie będziemy się tym zajmować na SOP.
Zadanie 3 - stdin cd.. #
Cel: Rozwinąć prog2.c tak, aby wypisywał na ekran powitanie dla każdego ciągu imion (słów) podanego ze standardowego wejścia. Program ma pobierać całe linie tekstu (do 20 znaków) i wypisywać na stdout. Operacja jest powtarzana aż do napotkania końca strumienia (C-d). Linie dłuższe niż 20 znaków mają być skracane, ale błąd nie ma być zgłaszany
Co student musi wiedzieć:
- man 3p fgets
C-d (czyli w notacji Windowsowej Ctrl d) powoduje zamknięcie strumienia, zupełnie jakbyśmy wykonali na nim close.
Gdy strumień napotyka permanentny koniec danych to taki stan nazywamy EOF (end of file).
Zwróć uwagę, że to nie to samo co chwilowy brak danych,
gdy program czeka na “powolnego” człowieka, aby ten coś wpisał.
Są dwie różne sytuacje które musisz rozróżniać!
C-d działa tylko po znaku nowej linii! Strumień tekstowy powinien kończyć się znakiem nowej linii.
C-c wysyła SIGINT do aktywnej grupy procesów - zazwyczaj pozwala zgrabnie zakończyć cały program
C-z zwiesza program ( wysyłając SIGSTOP), można potem poleceniem jobs obejrzeć listę takich wiszących programów i
przywrócić wybrany do życia piszac %N gdzie N to numer wiszącego procesu
C-\ wysyła SIGQUIT, kończy program i generuje zrzut pamięci
C-s zamraża terminal, nie ma wpływu na proces, o ile ten nie przepełni bufora, wtedy musi czekać. Mając nawyk
zapisywania pracy Ctrl S z Windows, łatwo można sobie niechcący zawiesić terminal. Aby odwiesić terminal, naciskamy C-q.
Powyższe skróty działają w powłoce bash, której używamy w labach (też w kilku innych powłokach).
kod do prog3.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_LINE 20
int main(int argc, char **argv)
{
char name[MAX_LINE + 2];
while (fgets(name, MAX_LINE + 2, stdin) != NULL)
printf("Hello %s", name);
return EXIT_SUCCESS;
}
all: prog1 prog2 prog3
prog1: prog1.c
gcc -Wall -fsanitize=address,undefined -o prog1 prog1.c
prog2: prog2.c
gcc -Wall -fsanitize=address,undefined -o prog2 prog2.c
prog3: prog3.c
gcc -Wall -fsanitize=address,undefined -o prog3 prog3.c
.PHONY: clean all
clean:
rm prog1 prog2 prog3
Skompiluj za pomocą polecenia make i uruchom program, jak dla powyższego Makefile skompilować tylko jeden cel?Odpowiedź
Sprawdź, jak się zachowa dla ciągów 20 i 21 znakowych. Czemu akurat tak?Odpowiedź
Czemu w wywołaniu printf nie dodaliśmy znaku nowej linii na końcu, a mimo to powitania wyświetlają się w oddzielnych liniach?Odpowiedź
Czemu rozmiar bufora jest MAX_LINE+2?Odpowiedź
Zwróć uwagę, że fgets pracuje na dowolnym strumieniu, nie tylko na stdin.
Ten program jest wolny od “magic numbers” i tak powinno być zawsze.
Plik testowy dane.txt
Alice
Marry Ann
Juan Luis
Tom
Utwórz plik tekstowy z powyższą zawartością.
Jak skłonić nasz program, aby pobrał dane z pliku, a nie z klawiatury (na dwa sposoby)?Odpowiedź 1
Odpowiedź 2
Zadanie 4 - parametry wywołania programu 1 #
Cel: Napisać program wyświetlający wszystkie parametry wywołania programu
Co student musi wiedzieć:
- man 1 xargs
kod do prog4.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_LINE 20
int main(int argc, char **argv)
{
int i;
for (i = 0; i < argc; i++)
printf("%s\n", argv[i]);
return EXIT_SUCCESS;
}
Pełny kod Makefile
CC=gcc
CFLAGS=-Wall -fsanitize=address,undefined
LDFLAGS=-fsanitize=address,undefined
Aby używać szablonów kompilacji, trzeba podać nazwę pliku wykonywalnego jako parametr make. W tym przykładzie będzie to “$ make prog4”. Tym razem kompilacja jest z pożądaną flagą -Wall, gdyż podany Makefile modyfikuje standardowy szablon.
Aby parametrem uczynić dwa lub więcej słów trzeba ująć je w apostrofy albo wpisać znak \ przed każdą spacją (lub innym tzw. białym znakiem).
Argument o numerze zero to zawsze nazwa programu!
Skompiluj i uruchom program, przetestuj dla różnych parametrów.
Jak za pomocą programu xargs przekształcić zawartość pliku dane.txt na argumenty wywołania naszego programu?Odpowiedź 1
Odpowiedź 2
Używając powyższego dla większych plików, trzeba pamiętać, że długość linii poleceń jest limitowana (różnie w różnych
systemach). xargs może podzielić dane na więcej wywołań, gdy sobie tego zażyczymy.
Zadanie 5 - parametry wywołania programu 2 #
Cel: Napisać program przyjmujący 2 argumenty: imię i licznik n>0, podanie złej ilości parametrów lub niepoprawnego licznika ma zatrzymać program. Dla poprawnych argumentów program wypisuje “Hello IMIĘ” n - raz
Co student musi wiedzieć:
- man 3p exit
- man 3p atoi
- man 3p strtol
Funkcja pomocnicza w pliku prog5.c
void usage(char *pname)
{
fprintf(stderr, "USAGE:%s name times>0\n", pname);
exit(EXIT_FAILURE);
}
Zwróć uwagę, że funkcji exit możesz podawać takie same statusu jak zwracasz w main.
Dobrą praktyką jest wypisywanie tzw. usage przy braku wymaganych argumentów
Kod do pliku prog5.c:
#include <stdio.h>
#include <stdlib.h>
void usage(char *pname)
{
fprintf(stderr, "USAGE:%s name times>0\n", pname);
exit(EXIT_FAILURE);
}
int main(int argc, char **argv)
{
if (argc != 3)
usage(argv[0]);
int i, j = atoi(argv[2]);
if (0 == j)
usage(argv[0]);
for (i = 0; i < j; i++)
printf("Hello %s\n", argv[1]);
return EXIT_SUCCESS;
}
Skompiluj ten program, używając uniwersalnego Makefile z poprzedniego zadania.
Jak działa program dla wartości powtórzeń niepoprawnie podanych, czemu tak?Odpowiedź
Czemu argc ma być 3, mamy przecież 2 argumenty?Odpowiedź
Zwróć uwagę na obrócony zapis porównania (0==j), jaki z takiego pisania może być zysk? Jeśli pomyłkowo zapiszę (0=j) to się nie skompiluje i szybko dowiem się o błędzie. Gdybym napisał (j=0) to się skompiluje, a o miejscu wystąpienia błędu dowiem się dopiero po czasochłonnej analizie kodu. To jest dodatkowe zabezpieczenie.
Zwróć uwagę, że starsze standardy C nie dopuszczały deklarowania zmiennych w środku kodu, ale tak jest czytelniej, bo zmienne deklarujemy, gdy stają się potrzebne.
Co zwraca atoi dla nie liczb? W praktyce zero wg. POSIX - undefined. Czasem chcemy mieć nad tym kontrolę, umieć rozróżnić zero od błędu, wtedy używamy funkcji strtol.
Argumenty programu można nadpisać w trakcie jego działania, w tym i nazwę! Może to służyć próbie ukrycia procesu i/lub jego argumentów (np. hasła).
Zadanie 6 - zmienne środowiskowe 1 #
Cel: Napisać program wypisujący listę wszystkich zmiennych środowiskowych
Co student musi wiedzieć:
- man 3p environ
- man 7 environ
zawartość do pliku prog6.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
extern char **environ;
int main(int argc, char **argv)
{
int index = 0;
while (environ[index])
printf("%s\n", environ[index++]);
return EXIT_SUCCESS;
}
Własną zmienna mogę dodać np. tak: TVAR2="122345" ./prog6 , pojawi się na wypisie, ale nie zostanie zapamiętana w
powłoce, tzn. kolejne wywołania programu ./prog6 już jej nie pokażą.
Mogę też dodać zmienną trwale do środowiska powłoki export TVAR1='qwert' i teraz, ilekroć wywołam program ./prog6
ta zmienna wciąż tam będzie.
Czy jeśli uruchomię drugą powłokę z menu środowiska i w niej uruchomię program to zmienna TVAR1 nadal będzie widoczna?Odpowiedź
Czy jeśli uruchomię drugą powłokę z pierwszej i w niej uruchomię program to zmienna TVAR1 nadal będzie widoczna?Odpowiedź
Zadanie 7 - zmienne środowiskowe 2 #
Cel: Zmodyfikować prog3.c tak, aby każda linia tekstu “Hello NAME” była powielona tyle razy, ile nakazuje mnożnik przekazany przez zmienną środowiskową TIMES. Na zakończenie programu ustawić zmienną środowiskową RESULT na “Done”.
Co student musi wiedzieć:
- man 3p getenv
- man 3p putenv
- man 3p setenv
- man 3 system (3p) jest trochę mniej czytelny
kod do pliku prog7.c
#include <stdio.h>
#include <stdlib.h>
#define MAX_LINE 20
int main(int argc, char **argv)
{
int x, i;
char *env = getenv("TIMES");
if (env)
x = atoi(env);
else
x = 1;
char name[MAX_LINE + 2];
while (fgets(name, MAX_LINE + 2, stdin) != NULL)
for (i = 0; i < x; i++)
printf("Hello %s", name);
if (putenv("RESULT=Done") != 0)
{
fprintf(stderr, "putenv failed");
return EXIT_FAILURE;
}
printf("%s\n", getenv("RESULT"));
if (system("env|grep RESULT") != 0)
return EXIT_FAILURE;
return EXIT_SUCCESS;
}
Zwróć uwagę na możliwy błąd braku zmiennej środowiska i jego obsługę na początku kodu. Dobry programista nigdy nie pomija sprawdzania błędów, jeśli się śpieszysz i pomijasz to Twój kod jest gorszy - musisz być tego świadomy/a. Najgorzej, gdy programista pomija sprawdzanie błędów z niewiedzy lub ignorancji.
Drugi przykład sprawdzenia błędu jest po wywołaniu putenv. Skoro tak często mamy to robić to przydałoby się makro. Coś na wzór ERR, którego już raz użyliśmy.
Innym sposobem na manipulowanie środowiskiem jest funkcja setenv.
Funkcja system służy nam do sprawdzenia tego, co i tak wiemy. Skoro nie było błędu putenv to nie spodziewamy się, aby tutaj był. Jednak na potrzeby przykładu robimy takie sprawdzenie dwa razy, raz getenv(“RESULT”), a potem jeszcze system.
Użycie funkcji system jest równoważne z uruchomieniem powłoki jako procesu dziecka, komenda jest podana. Funkcja system zwróci to, co normalnie zwróciłby program, to co można w bash’u sprawdzić wywołaniem “$ $?” (pierwszy dolar nie jest częścią polecenia), czyli status zakończenia ostatniego polecenia/programu.
Co ciekawe niektóre systemy nie mają powłoki! Gdy brak shella funkcja system nie działa. Dostajemy błąd 127.
Wykonaj program z różnymi ustawieniami TIMES.
Jak po wykonaniu programu sprawdzić, czy zmienna RESULT jest ustawiona?Odpowiedź
Zadanie 8 - obsługa błędów #
Cel: zmodyfikować program prog6.c, aby dodał zmienne środowiskowe podane przez użytkownika i dopiero wtedy wypisał je wszystkie.
Co student musi wiedzieć:
- man 3p errno
kod do pliku prog8.c
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define ERR(source) (perror(source), fprintf(stderr, "%s:%d\n", __FILE__, __LINE__), exit(EXIT_FAILURE))
extern char **environ;
void usage(char *pname)
{
fprintf(stderr, "USAGE:%s [VARN_NAME VARN_VALUE] ... \n", pname);
exit(EXIT_FAILURE);
}
int main(int argc, char **argv)
{
if (argc % 2 != 1)
usage(argv[0]);
for (int i = argc - 1; i > 0; i -= 2)
{
if (setenv(argv[i - 1], argv[i], 1))
{
if (EINVAL == errno)
ERR("setenv - variable name contains '='");
ERR("setenv");
}
}
int index = 0;
while (environ[index])
printf("%s\n", environ[index++]);
return EXIT_SUCCESS;
}Skompiluj i wykonaj program na dwa sposoby:
$ ./prog8 VAR1 VAL1
$ ./prog8 VA=R1 VAL1
W drugim wypadku program powinien zakończyć się błędem.
Po zapoznaniu się z dokumentacją funkcji setenv dowiemy się, że zmienne środowiskowe nie mogą mieć w swojej nazwie znaku ‘=’.
Wtedy funkcja zwraca -1 i ustawia errno na kod błędu EINVAL.
Funkcje systemowe poza zwracaniem specjalnego wyniku, który oznacza błąd, często ustawiają specjalną zmienną errno z kodem błędu.
Pozwala to na ustalenie konkretnej przyczyny nieudanego wykonania funkcji.
Często wiedząc, co w przypadku danej funkcji oznacza kod błędu, możemy podjąć specjalne akcje.
W przykładzie jest to wypisanie innego kodu błędu.
Są jednak przypadki, gdzie zakończenie się funkcji z błędem nie powinno zakończyć działania programu.
W dokumentacji funkcji systemowych zawsze jest sekcja ERRORS. Zachęcam do zapoznania się z błędami poznanych już przez ciebie funkcji systemowych. Zastanów się, czy każde zakończenie się funkcji błędem musi oznaczać zakończenie programu.
Co się stanie, gdy wywołamy program tylko z jednym argumentem?Odpowiedź
Dlaczego dodane przez nas zmienne środowiskowe są wypisywane ostatnią pętlą w programie?Odpowiedź
Zadanie do sprawdzenia środowiska. #
Cel: Napisać trywialny program “hello world”, skompilować go i uruchomić
Co student musi wiedzieć:
- znać dowolne środowisko programisty w systemie Linux
- znać podstawowe flagi polecenia
gcc - umieć posługiwać się konsolą
- wiedza o programie
git
rozwiązanie prog1.c:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
printf("Hello world\n");
return EXIT_SUCCESS;
}
kompilacja:
gcc -Wall -fsanitize=address,undefined -o prog1 prog1.c
Użycie flagi -Wall jest obowiązkowe w trakcie laboratorium,
program nie powinien generować istotnych ostrzeżeń.
Każde zadanie wymaga również użycia odpowiedniego zestawu opcji -fsanitize.
Więcej informacji o sanitizerach.
UWAGA nie wolno dowolnie mieszać parametrów poleceniagcc, parametr-oma argument — ścieżkę do wynikowego programu.
uruchomienie:
./prog1