Wskazówki dotyczące programowania

Source: https://www.cs.uky.edu/~raphael/programming.html

Od uczniów młodszych i starszych klas CS oczekuje się znajomości podstawowych informacji na temat języków C , Unix i narzędzi do tworzenia oprogramowania . Na tej stronie szczegółowo opisano niektóre z tych zagadnień i zasugerowano kilka ćwiczeń .

C

  1. C pozwala deklarować zmienne poza jakąkolwiek procedurą. Zmienne te nazywane są zmiennymi globalnymi .
  • Zmienna globalna jest przydzielana jednorazowo podczas uruchamiania programu i pozostaje w pamięci aż do zakończenia programu.
  • Zmienna globalna jest widoczna dla wszystkich procedur w tym samym pliku.
  • Możesz sprawić, że zmienna globalna zadeklarowana w pliku Ac będzie widoczna dla wszystkich procedur w niektórych innych plikach Bc, Cc, Dc, ... deklarując ją z modyfikatorem extern w plikach Bc, Cc, Dc, ... jak w tym przykładzie:
    extern in theVariable
    Jeśli masz wiele plików współdzielących tę zmienną, powinieneś zadeklarować ją jako extern w pliku nagłówkowym foo.h (opisanym poniżej ) i użyć #include foo.h w plikach Bc, Cc, Dc, ... . Musisz zadeklarować zmienną w dokładnie jednym pliku bez modyfikatora extern , w przeciwnym razie w ogóle nie zostanie ona przydzielona.
  • Nie jest dobrym pomysłem używanie zbyt wielu zmiennych globalnych, ponieważ nie można zlokalizować miejsc, w których są one dostępne. Są jednak sytuacje, w których zmienne globalne pozwalają uniknąć przekazywania wielu parametrów do funkcji.
  1. Ciągi znaków są wskaźnikami do tablic znaków zakończonych znakiem null.
  • Deklarujesz ciąg jako char *variableName .
  • Jeśli Twój ciąg znaków jest stały, możesz po prostu przypisać do niego literał ciągu:
    char *myString = "To jest przykładowy ciąg znaków";
  • Pusty ciąg ma tylko terminator zerowy:
    mójCiąg = ""; // pusty ciąg znaków o długości 0, zawierający wartość null

Wskaźnik zerowy nie jest prawidłową wartością ciągu:
mójCiąg = NULL; // nieprawidłowy ciąg
Możesz użyć takiej wartości, aby wskazać koniec tablicy ciągów:
argv[0] = "nazwa programu";

argv[1] = "pierwszyparametr";

argv[2] = "drugiparametr";

  • argv[3] = NULL; //terminator

Jeśli ciąg znaków jest obliczany w czasie wykonywania, musisz zarezerwować wystarczająco dużo miejsca, aby go przechowywać. Pokój musi wystarczyć, aby pomieścić wartość null na końcu:
char *mójString;

mójString = (char *) malloc(strlen(jakiśCiąg) + 1); // przydziel miejsce

  • strcpy(myString, jakiśString); // skopiuj SomeString do myString
  • Aby uniknąć wycieków pamięci, powinieneś w końcu zwrócić miejsce przydzielone za pomocą malloc , używając free . Jego parametrem musi być początek spacji zwróconej przez malloc :
    free((void *) mójString);
    Aby zachować czystość i czytelność kodu, powinieneś wywołać free() w tej samej procedurze, w której wywołujesz malloc() ; możesz wywołać inne procedury między tymi dwoma punktami, aby manipulować ciągiem.
  • Jeśli kopiujesz ciągi znaków, powinieneś bardzo uważać, aby nigdy nie kopiować więcej bajtów, niż może pomieścić docelowa struktura danych. Przepełnienia bufora są najczęstszą przyczyną luk w zabezpieczeniach programów. W szczególności rozważ użycie strncpy() i strncat() zamiast strcpy() i strcat() .

Jeśli używasz C++, musisz przekonwertować obiekty łańcuchowe na ciągi w stylu C przed przekazaniem ich w wywołaniu systemowym.
string myString // Ta deklaracja działa tylko w C++

...

  • SomeCall(myString.c_str())
    Niestety, c_str() zwraca niezmienny ciąg znaków. Jeśli potrzebujesz modyfikowalnego ciągu, możesz skopiować dane za pomocą strcpy() (jak powyżej) lub możesz rzutować typ:
    SomeCall(const_cast(myString.c_str()))
    Rzutowanie nie jest tak bezpieczne jak kopiowanie, ponieważ funkcja SomeCall() może w rzeczywistości zmodyfikować ciąg znaków, co mogłoby zmylić każdą część programu, która zakłada, że ​​myString jest stała, co jest typowym zachowaniem ciągów C++.
  1. Bufor to obszar pamięci pełniący rolę pojemnika na dane . Mimo że dane mogą mieć interpretację (taką jak tablica struktur z wieloma polami), programy, które buforują odczyt i zapis, często traktują je jako tablice bajtów. Tablica bajtów to nie to samo, co ciąg znaków, mimo że oba są zadeklarowane jako char * lub char [] .
  • Mogą nie zawierać znaków ASCII i nie mogą być zakończone znakiem null.
  • Nie można użyć funkcji strlen() do znalezienia długości danych w buforze (ponieważ bufor może zawierać bajty zerowe). Zamiast tego musisz obliczyć długość danych na podstawie wartości zwracanej przez wywołanie systemowe (zwykle read ), które wygenerowało dane.
  • Nie możesz używać strcpy() , strcat() ani podobnych procedur w buforach bajtowych; zamiast tego musisz użyć memcpy() lub bcopy() .

Zapisujesz bufor o wielkości 123 bajtów do pliku, używając kodu takiego jak ten:
char *nazwa pliku = "/tmp/foo"

#zdefiniuj ROZMIAR BUF 4096

char buf[BUFROZMIAR]; // bufor zawierający maksymalnie bajtów BUFSIZE

...

int plik wyjściowy; // deskryptor pliku, mała liczba całkowita

int bajtówToWrite; // liczba bajtów pozostałych do zapisania

char *outPtr = buf;

...

if ((outFile = creat(fileName, 0660)) < 0) [ // błąd

// zobaczuprawnienia do plików , aby zrozumieć 0660

perror(nazwa pliku); // wypisz przyczynę

wyjście(1); // i wyjdź

[

bajtówToWrite = 123; // inicjalizacja; 123 to tylko przykład

while ((bytesWritten = write(outFile, outPtr, bytesToWrite)) < bytesToWrite) [

// nie wszystkie bajty zostały jeszcze zapisane

if (bytesWritten < 0) [ // błąd

perror("zapisz");

wyjście(1);

[

outPtr += bajtyZapisane;

bytesToWrite -= bajty zapisane;

  • [

Aby kompilator przydzielił miejsce na bufory, musisz zadeklarować bufor o rozmiarze, który kompilator może obliczyć, jak w
#zdefiniuj ROZMIAR BUF 1024

  • char buf[BUFROZMIAR];
    Jeśli po prostu zadeklarujesz bufor bez rozmiaru:
    char buf[];
    wtedy ma nieznany rozmiar i C nie przydziela żadnego miejsca. Jest to dopuszczalne, jeśli buf jest parametrem formalnym (to znaczy pojawia się w nagłówku procedury); rzeczywisty parametr (dostarczony przez osobę wywołującą) ma rozmiar. Ale jest to niedopuszczalne, jeśli buf jest zmienną. Jeśli nie znasz rozmiaru bufora w czasie kompilacji, powinieneś użyć takiego kodu:
    char *buf = (char *) malloc(rozmiarbufora);
    gdzie buforSize jest wynikiem niektórych obliczeń w czasie wykonywania.
  1. Można dynamicznie przydzielać i zwalniać pamięć.

Poszczególne instancje dowolnego typu:
typedef ... mójTyp;

mójTyp *mojaZmienna = (mójTyp *) malloc(rozmiar(myTyp));

// możesz teraz uzyskać dostęp do *myVariable.

...

  • free((void *) mojaZmienna);
    Ponownie, dobrą praktyką programistyczną jest wywoływanie free() w tej samej procedurze, w której wywołujesz malloc() .

Tablice jednowymiarowe dowolnego typu:
mójTyp *myArray = (myTyp *) malloc(Długość tablicy * rozmiar(myTyp));

// myArray[0] .. myArray[arrayLength - 1] są teraz przydzielone.

...

  • free((void *) mojaArray);

Tablice dwuwymiarowe są reprezentowane przez tablicę wskaźników, z których każdy wskazuje na tablicę:
myType **myArray = (myType **) malloc(numRows * sizeof(myType *));

int indeks wiersza;

for (rowIndex = 0; rowIndex < numRows; rowIndex += 1) [

myArray[rowIndex] = (myType *) malloc(numColumns * sizeof(myType));

[

// myArray[0][0] .. myArray[0][numColumns-1] .. myArray[numRows-1][numColumns-1]

// są teraz przydzielone. Być może zechcesz je zainicjować.

...

for (rowIndex = 0; rowIndex < numRows; rowIndex += 1) [

free((void *) mojaArray[rowIndex]);

[

free((void *) mojaArray);

  • Jeśli używasz C++, nie mieszaj new/delete z malloc/free dla tej samej struktury danych. Zaletą new/delete dla instancji klas jest to, że automatycznie wywołują konstruktory, które mogą inicjować dane, oraz destruktory, które mogą finalizować dane. Kiedy używasz malloc/free , musisz jawnie zainicjować i sfinalizować.
  1. Liczby całkowite
  • C zwykle reprezentuje liczby całkowite w 4 bajtach. Na przykład liczba 254235 jest reprezentowana jako liczba binarna 00000000,00000011,11100001,00011011.
  • Z drugiej strony tekst ASCII reprezentuje liczby jak każdy inny znak, z jednym bajtem na cyfrę przy standardowym kodowaniu. W kodzie ASCII liczba 254235 jest reprezentowana jako 00110010, 00110101, 00110110, 00110010, 00110011, 00110101.
  • Jeśli chcesz napisać plik liczb całkowitych, ogólnie rzecz biorąc, zarówno pod względem przestrzennym, jak i czasowym, bardziej efektywne jest zapisanie wersji 4-bajtowych niż konwersja ich na ciągi ASCII i zapisanie ich. Oto jak zapisać pojedynczą liczbę całkowitą do otwartego pliku:
    write(outFile, &myInteger, sizeof(moja liczba całkowita))

Możesz przyjrzeć się poszczególnym bajtom liczby całkowitej, rzucając ją jako strukturę czterech bajtów:
int adres IP; // przechowywana jako liczba całkowita, rozumiana jako 4 bajty

struktura typedef [

char bajt1, bajt2, bajt3, bajt4;

[ IPSzczegóły_t;

IPDetails_t *details = (IPDetails_t *) (&Adres IP);

printf("bajt 1 to %o, bajt 2 to %o, bajt 3 to %o, bajt 4 to %o\n",

  • szczegóły->bajt1, szczegóły->bajt2, szczegóły->bajt3, szczegóły->bajt4);
  • Wielobajtowe liczby całkowite mogą być reprezentowane w różny sposób na różnych maszynach. Niektóre (jak Sun SparcStation) umieszczają najbardziej znaczący bajt na pierwszym miejscu; inne (takie jak Intel i80x86 i jego potomkowie) umieszczają najmniej znaczący bajt na pierwszym miejscu. Jeśli piszesz dane w postaci liczb całkowitych, które mogą być odczytywane na innych komputerach, przekonwertuj dane na „sieciową” kolejność bajtów za pomocą funkcji htons() lub htonl() . Jeśli czytasz dane w postaci liczb całkowitych, które mogły zostać zapisane na innych komputerach, przekonwertuj dane z kolejności „sieciowej” na lokalną kolejność bajtów za pomocą ntohs() lub ntohl() .

Możesz przewidzieć układ pamięci struktur i wartość zwróconą przez sizeof() . Na przykład,
struktura foo [

znak a; // wykorzystuje 1 bajt

// C wstawia tutaj 3-bajtową dokładkę, więc b może zaczynać się od 4-bajtowej granicy

int b; // wykorzystuje 4 bajty

bez znaku krótkie c; // wykorzystuje 2 bajty

bez znaku znak d[2]; // wykorzystuje 2 bajty

  1. [;
    Dlatego sizeof(struct foo ) zwraca 12. Ta przewidywalność (dla danej architektury) jest powodem, dla którego niektórzy nazywają C „przenośnym językiem asemblera”. Należy przewidzieć układ struktury podczas generowania danych, które muszą mieć określony format, taki jak nagłówek pakietu sieciowego.
  2. Możesz zadeklarować wskaźniki w C do dowolnego typu i przypisać im wartości wskazujące na obiekty tego typu.

W szczególności C pozwala budować wskaźniki do liczb całkowitych:
int pewna liczba całkowita;

int *intPtr = &pewna liczba całkowita; // deklaruje zmienną o wartości wskaźnika i przypisuje odpowiednią wartość wskaźnika

jakieśWywołanie(intPtr); // przekazuje wskaźnik jako rzeczywisty parametr

  • SomeCall(&someInteger); // ma taki sam efekt jak powyżej
  • Procedura biblioteki AC, która pobiera wskaźnik do wartości, najprawdopodobniej modyfikuje tę wartość (staje się parametrem „out” lub „in out”). W powyższym przykładzie jest bardzo prawdopodobne, że SomeCall modyfikuje wartość liczby całkowitej SomeInteger .

Można zbudować wskaźnik do tablicy liczb całkowitych i użyć go do przechodzenia przez tę tablicę.
#zdefiniuj ARRAY_LENGTH 100

int intArray[ARRAY_LENGTH];

int *intArrayPtr;

...

suma całkowita = 0;

for (intArrayPtr = intArray; intArrayPtr < intArray+ARRAY_LENGTH; intArrayPtr += 1) [

suma += *intArrayPtr;

  • [

Można zbudować wskaźnik do tablicy struktur i użyć go do przechodzenia przez tę tablicę.
#zdefiniuj ARRAY_LENGTH 100

typedef struct [int foo, bar;[ pair_t; // pair_t to nowy typ

para_t strukturaArray[ARRAY_LENGTH]; // structArray jest tablicą elementów ARRAY_LENGTH pair_t

para_t *structArrayPtr; // structArrayPtr wskazuje na element pair_t

...

suma całkowita = 0;

for (structArrayPtr = structArray; structArrayPtr < structArray+ARRAY_LENGTH; structArrayPtr += 1) [

suma += structArrayPtr->foo + structArrayPtr->bar;

  • [
  • Po dodaniu liczby całkowitej do wskaźnika wskaźnik zostanie przesunięty o określoną liczbę elementów, niezależnie od tego, jak duże są te elementy. Kompilator zna rozmiar i postępuje właściwie.
  1. Wyjście
  • Formatujesz dane wyjściowe za pomocą printf lub jego wariantu, fprintf .
  • Ciąg formatujący używa %d , %s , %f do wskazania, że ​​na wyjściu ma zostać umieszczona liczba całkowita, ciąg znaków lub wartość rzeczywista.
  • Ciąg formatujący używa \t i \n do wskazania tabulacji i nowej linii.
  • Przykład:
    printf("Myślę, że liczba %d to %s\n", 13, "szczęśliwa");
  • Mieszanie printf() , fprintf() i cout może nie spowodować wydrukowania elementów w oczekiwanej kolejności. Używają niezależnych obszarów przejściowych („buforów”), które drukują, gdy są pełne.
  1. Procedura main() pobiera parametry funkcji, które reprezentują parametry wiersza poleceń .
  • Jednym z powszechnych sposobów pisania głównej procedury jest:
    int main(int argc; char *argv[]);
    Tutaj argc to liczba parametrów, a argv to tablica ciągów znaków, czyli tablica wskaźników do tablic znaków zakończonych znakiem null.

Zgodnie z konwencją pierwszym elementem argv jest nazwa samego programu.
int main(int argc; char *argv[]);

printf("Mam %d parametrów; nazywam się %s, a mój pierwszy parametr to %s\n",

  • argc, argv[0], argv[1]);
  1. Przydatne funkcje językowe
  • Możesz zwiększyć liczbę całkowitą lub ustawić wskaźnik na następny obiekt, używając operatora ++ . Zwykle najlepiej jest umieścić ten operator po zmiennej: myInt++ . Jeśli umieścisz ++ przed zmienną, zmienna będzie zwiększana przed jej oceną, a rzadko jest to, czego chcesz.

Możesz zbudować przypisanie, w którym zmienna po lewej stronie będzie pierwszą częścią wyrażenia po prawej stronie:
mojaInt -= 3; // odpowiednik myInt = myInt - 3

mojaInt *= 42; // odpowiednik myInt = myInt * 42

  • mojaInt += 1; // odpowiednik i być może lepszy niż myInt++
  • Liczby można wyrażać w systemie dziesiętnym, ósemkowym (poprzedzając cyfrą 0 , jak w przypadku 0453 ) lub szesnastkowo (poprzedzając cyfrą 0x , jak w przypadku 0xffaa ).

Możesz traktować liczbę całkowitą jako zbiór bitów i wykonywać operacje bitowe:
mojaInt = mojaInt | 0444; // bitowy LUB; 0444 jest w formacie ósemkowym

mojaInt &= 0444; // bitowe AND ze skrótem przypisania

  • myInt = coś ^ cokolwiek; //bitowy XOR

C i C++ mają wyrażenia warunkowe. Zamiast pisać
jeśli (a < 7)

a = pewna wartość

w przeciwnym razie

  • a = inna wartość;
    Możesz pisać
    a = a < 7? jakaśWartość: jakaśInnaWartość;
  • Przypisania zwracają wartość po lewej stronie, dzięki czemu można uwzględnić przypisanie w większych wyrażeniach, takich jak wyrażenia warunkowe. Powinieneś jednak przestrzegać konwencji, zgodnie z którą takie przypisania są zawsze otoczone nawiasami, aby wskazać zarówno osobie czytającej Twój kod, jak i kompilatorowi, że tak naprawdę masz na myśli przypisanie, a nie test równości. Na przykład napisz
    if ((s = gniazdo(...)) == -1)
    nie
    if (s = gniazdo(...) == -1)
    Druga wersja jest zarówno trudniejsza do odczytania, jak i w tym przypadku niepoprawna, ponieważ operator równości == ma wyższy priorytet niż operator przypisania = .
  1. Programy, które nie są trywialnie krótkie, powinny być zwykle rozłożone na wiele plików źródłowych , każdy o nazwie kończącej się na .c (w przypadku programów w języku C) lub .cpp (w przypadku programów w języku C++).
  • Spróbuj zgrupować funkcje, które manipulują tymi samymi strukturami danych lub mają powiązane cele, w tym samym pliku.
  • Wszystkie typy, funkcje, zmienne globalne i stałe manifestu, które są potrzebne więcej niż jednemu plikowi źródłowemu, należy również zadeklarować w pliku nagłówkowym , którego nazwa kończy się na .h .
  • Z wyjątkiem funkcji wbudowanych, nie deklaruj treści funkcji (ani czegokolwiek, co powoduje, że kompilator generuje kod lub przydziela miejsce) w pliku nagłówkowym.
  • Każdy plik źródłowy powinien odnosić się do potrzebnych plików nagłówkowych za pomocą linii #include .
  • Nigdy #nie dołączaj pliku .c .
  1. Jeśli masz wiele plików źródłowych, musisz połączyć wszystkie skompilowane pliki obiektowe wraz z bibliotekami, których potrzebuje Twój program.
  • Najłatwiejszą metodą jest użycie kompilatora C, który zna biblioteki C:
    gcc *.o -o mójProgram
    To polecenie prosi kompilator o połączenie wszystkich plików obiektowych z biblioteką C (która jest domyślnie dołączona) i umieszczenie wyniku w pliku myProgram , który staje się wykonywalny.
  • Jeśli Twój program potrzebuje innych bibliotek, powinieneś podać je po plikach obiektowych, ponieważ linker zbiera tylko procedury z bibliotek, o których już wie, że są potrzebne, i łączy pliki w określonej przez Ciebie kolejności. Jeśli więc potrzebujesz biblioteki takiej jak libxml2 , polecenie łączenia powinno wyglądać mniej więcej tak:
    gcc *.o -lxml2 -o mójProgram
    Kompilator wie, jak przeszukiwać różne standardowe katalogi w poszukiwaniu bieżącej wersji libxml2 .
  1. Debugowanie programów w języku C
  • Jeśli wystąpi błąd segmentacji, najprawdopodobniej masz indeks poza zakresem, niezainicjowany wskaźnik lub wskaźnik zerowy.
  • Możesz umieścić instrukcje print w swoim programie, aby pomóc Ci zlokalizować błąd.
  • Debugowanie będzie prawdopodobnie najskuteczniejsze, jeśli użyjesz gdb (opisanego poniżej ), aby dowiedzieć się, gdzie leży błąd.
  • Programy działające przez długi czas muszą uważać, aby zwolnić całą przydzieloną pamięć, w przeciwnym razie w przeciwnym razie zabraknie im pamięci. Aby debugować wycieki pamięci, możesz zapoznać się z tymi artykułami na temat debugowania wycieków pamięci w C i C++ .

Uniksa

pliki standardowe , polecenia , wywołania systemowe , uprawnienia do plików

  1. Zgodnie z konwencją każdy proces rozpoczyna się od otwarcia trzech standardowych plików : standardowego wejścia, standardowego wyjścia i standardowego błędu, powiązanych z deskryptorami plików 0, 1 i 2.
  • Standardowe wejście jest zwykle podłączone do klawiatury. Cokolwiek wpiszesz, trafia do programu.
  • Standardowe wyjście jest zwykle podłączone do ekranu. Niezależnie od tego, co wyjdzie z programu, stanie się widoczne.
  • Błąd standardowy jest również zwykle podłączony do ekranu.
  • Możesz użyć powłoki do wywoływania programów, tak aby standardowe wyjście jednego programu było bezpośrednio połączone („potokowane”) ze standardowym wejściem innego programu:
    jest | toaleta

Możesz użyć powłoki do wywoływania programów, aby standardowe wejście i/lub wyjście zostało połączone z plikiem:
ls > lsOutFile

wc < lsOutFile

  • sort -u posortowanyPlik
  • Ogólnie rzecz biorąc, programy nie wiedzą lub nie przejmują się tym, czy powłoka zmieniła znaczenie ich standardowych plików.
  1. Polecenia Uniksa
  • Polecenia to po prostu nazwy plików wykonywalnych. Zmienna środowiskowa PATH mówi powłoce , gdzie ich szukać. Zazwyczaj ta zmienna ma wartość taką jak /bin:/usr/bin:/usr/local/bin:. .
  • Aby zobaczyć, gdzie powłoka znajduje konkretny program, na przykład vim , powiedz, gdzie vim .
  1. Wywołania systemowe i wywołania bibliotek podlegają pewnym ważnym konwencjom.
  • Wartość zwracana przez wywołanie zwykle wskazuje, czy wywołanie powiodło się (zwykle wartość wynosi 0 lub jest dodatnia), czy też nie powiodło się (zwykle wartość wynosi -1).

Zawsze sprawdzaj wartość zwracaną przez wywołania biblioteki. Gdy wywołanie systemowe nie powiedzie się, funkcja perror() może wyświetlić informację o błędzie (do standardowego błędu):
int fd;

char *nazwa pliku = "mójplik";

if ((fd = open(nazwa pliku, O_RDONLY)) < 0) [

perror(nazwa pliku); // może wyświetlić „mójplik: Nie ma takiego pliku ani katalogu”

  • [
  • Strona podręcznika dotycząca wywołania systemowego lub procedury bibliotecznej może zawierać listę typów danych, których nie definiuje, na przykład size_t lub time_t lub O_RDONLY . Typy te są zazwyczaj zdefiniowane w plikach nagłówkowych wymienionych na stronie podręcznika; musisz dołączyć wszystkie te pliki nagłówkowe do swojego programu C.
  1. Uprawnienia do plików w systemie Unix są zwykle wyrażane liczbami ósemkowymi.
  • W powyższym przykładzie funkcji creat() 0660 jest liczbą ósemkową (to właśnie oznacza początkowe 0), reprezentującą binarną liczbę 110 110 000. Ta liczba ósemkowa przyznaje uprawnienia do odczytu i zapisu, ale nie uprawnienia do wykonywania, właścicielowi pliku i grupie pliku, ale nie ma uprawnień innym użytkownikom.
  • Uprawnienia ustawiasz podczas tworzenia pliku za pomocą parametru wywołania creat() .
  • Polecenie ls -l pokazuje uprawnienia do plików.
  • Możesz zmienić uprawnienia do posiadanego pliku za pomocą programu chmod .
  • Wszystkie Twoje procesy mają cechę zwaną umask, zwykle przedstawianą jako liczba ósemkowa. Kiedy proces tworzy plik, bity umaski są usuwane z uprawnień określonych w wywołaniu creat() . Jeśli więc Twój umask ma wartość 066, inni nie będą mogli czytać ani zapisywać utworzonych przez Ciebie plików, ponieważ 066 reprezentuje uprawnienia do odczytu i zapisu dla Twojej grupy i innych osób. Możesz sprawdzić i zmodyfikować swój umask za pomocą programu umask , który zwykle wywołujesz w skrypcie startowym powłoki (w zależności od powłoki, ~/.login lub ~/.profile ).

Narzędzia do tworzenia oprogramowania

edytor tekstu , debugger , kompilator , strony podręcznika , tworzenie , wyszukiwanie ,

  1. Użyj edytora tekstu, aby tworzyć, modyfikować i sprawdzać swój program. Dostępnych jest kilka rozsądnych edytorów tekstu.
  • Nauczenie się edytora vima i jego interfejsu graficznego gvim wymaga trochę wysiłku, ale zapewnia bardzo wysokiej jakości zestaw narzędzi do edycji plików programów, w tym podświetlanie składni, dopasowywanie nawiasów, uzupełnianie słów, automatyczne wcięcia, wyszukiwanie według znaczników (które przesuwają szybkie przejście z miejsca, w którym program wywołuje funkcję do miejsca, w którym funkcja jest zdefiniowana) oraz zintegrowane wyszukiwanie stron podręcznika. Vim jest przeznaczony do obsługi klawiatury; nie musisz nigdy używać myszy, jeśli nie chcesz. Jest swobodnie dostępny dla systemów operacyjnych Unix, Win32 i Microsoft. Jest to najbardziej rozwinięta wersja serii edytorów, która obejmuje ed , ex , vi iElvisa . Możesz przeczytać dokumentację online vima i uzyskać natychmiastową pomoc za pomocą polecenia : help vima .
  • Edytor emacsa jest, jeśli w ogóle, bardziej funkcjonalny niż vim . Nauka wymaga również dużego wysiłku. Jest również swobodnie dostępny dla systemów operacyjnych Unix i Microsoft. Dokumentację znajdziesz tutaj .
  • Dostępnych jest wiele innych edytorów tekstu, ale generalnie nie zapewniają one dwóch najbardziej przydatnych funkcji potrzebnych do tworzenia programów: automatycznego wcięcia i podświetlania składni. Jednak te edytory tekstu często mają tę zaletę, że są łatwiejsze do nauczenia, zgodnie z ich ograniczonymi możliwościami. Do tych edytorów tekstu o niższej jakości należą (dla Uniksa) pico , gedit i joe oraz (dla Microsoftu) notatnik i word .
  • Być może znasz zintegrowane środowisko programistyczne (IDE), takie jak Eclipse, Code Warrior lub .NET. Środowiska te zazwyczaj mają edytory tekstu zintegrowane z debugerami i kompilatorami. Jeśli używasz takiego IDE, warto skorzystać z powiązanych edytorów tekstu.
  1. gdb to debuger , który rozumie zmienne i strukturę programu.
  • Dokumentację znajdziesz tutaj .
  • Aby efektywnie używać gdb , musisz przekazać flagę -g do kompilatora C lub C++.
  • Jeśli Twój program myProgram nie pozostawił pliku o nazwie core , spróbuj gdb myProgram core .
  • Możesz także uruchomić swój program od początku pod kontrolą gdb : gdb myProgram .
  • Wszystkie polecenia gdb można skrócić do unikalnego przedrostka.
  • Polecenie pomocy jest bardzo przydatne.
  • Polecenie Where pokazuje stos wywołań, w tym numery linii pokazujące, gdzie znajduje się każda procedura. Jest to pierwsze polecenie, które powinieneś wypróbować podczas debugowania pliku podstawowego.
  • Aby wydrukować wartość jakiegoś wyrażenia (możesz dołączyć swoje zmienne i zwykłe operatory C), wpisz print wyrażenie , jak w
    drukuj (myInt + 59) & 0444;
  • Aby zobaczyć swój program, spróbuj wyświetlić listę myFunction lub listę myFile.c:38 .
  • Aby ustawić inny rekord aktywacji jako bieżący, użyj polecenia w górę (dla nowszego) lub w dół (dla nowszego).
  • Możesz ustawić punkt przerwania w dowolnej linii dowolnego pliku. Na przykład możesz powiedzieć break foo.p:38 , aby ustawić punkt przerwania w linii 38 w pliku foo.p. Za każdym razem, gdy program podczas wykonywania natrafi na tę linię, zatrzyma się, a gdb wyświetli monit o podanie poleceń. Możesz na przykład przyjrzeć się zmiennym lub przejść dalej przez program.
  • Następne polecenie powoduje przejście o jedną instrukcję do przodu (wywołanie i powrót z dowolnej procedury, jeśli to konieczne) .
  • Polecenie step powoduje przejście do przodu o jedną instrukcję, ale jeśli instrukcja zawiera wywołanie procedury, wchodzi do procedury i zatrzymuje się na pierwszej instrukcji.
  • Jeśli wpiszesz polecenie set follow-fork-mode child , to kiedy program wykona wywołanie fork() , gdb będzie kontynuował debugowanie dziecka, a nie rodzica.
  • Opuść gdb , wprowadzając polecenie Quit .
  • Możesz preferować użycie graficznego interfejsu ddd do gdb .
  1. Zawsze dawaj programom kompilacyjnym gcc lub g++ flagę -Wall , aby włączyć wysoki poziom ostrzeżeń. Podobnie nadaj javacowi flagę -Xlint:all . Nie włączaj programu, który generuje ostrzeżenia w czasie kompilacji.
  2. Możesz przeczytać podręcznik , aby uzyskać szczegółowe informacje o programach, procedurach biblioteki C i wywołaniach systemowych Uniksa, używając programu man , na przykład man printf lub man gcc .
  • Czasami żądana funkcja znajduje się w określonej sekcji podręcznika Uniksa i musisz wyraźnie o nią poprosić: man 2 open lub man 3 printf . Sekcja 1 dotyczy programów, sekcja 2 dotyczy wywołań systemowych, sekcja 3 dotyczy biblioteki C, a sekcja 8 dotyczy administracji systemem. Najprawdopodobniej nie potrzebujesz innych sekcji.
  • Możesz sprawdzić, czy jakiś program, procedura biblioteki C lub wywołanie systemowe Uniksa jest istotne dla jakiegoś tematu, używając flagi -k , jak w przypadku man -k print .
  1. Użyj programu make , aby uporządkować przepisy dotyczące rekompilacji i ponownego łączenia programu po zmianie pliku źródłowego.

Jeśli Twój program składa się z kilku plików, możesz skompilować je osobno, a następnie połączyć ze sobą. Kompilujesz z flagą -c i używasz flagi -o do wskazania pliku wyjściowego. Rozsądny plik makefile może wyglądać następująco:
ŹRÓDŁA = sterownik.c wejście.c wyjście.c

OBIEKTÓW = sterownik.o wejście.o wyjście.o

NAGŁÓWKI = wspólne.h

CFLAGS = -g -Ściana

program: $(OBIEKTÓW)

$(CC) $(CFLAGS) $(OBIEKTÓW) -o program

$(OBIEKTÓW): $(NAGŁÓWKI)

testRun: program

  • program Ten plik makefile wykorzystuje wbudowaną definicję CC i wbudowaną regułę do konwersji plików źródłowych C, takich jak sterownik.c, na ich plik obiektowy. Jeśli zmodyfikujesz tylko input.c , to make testRun spowoduje, że kompilator odbuduje input.o , następnie kompilator ponownie połączy obiekty, utworzy program , a następnie uruchomi program ze standardowym wejściem przekierowanym z pliku testData .
  • Jeśli masz wiele plików źródłowych i wiele plików nagłówkowych, możesz użyć programu makedependent do automatycznego zbudowania reguł Makefile , które określają, w jaki sposób pliki źródłowe zależą od plików nagłówkowych. W powyższym przykładzie założono, że wszystkie pliki źródłowe zależą od wszystkich plików nagłówkowych, co często nie ma miejsca.
  1. Program grep może szybko wyszukać definicję lub zmienną, szczególnie w plikach dołączanych:
    grep "struct timeval [" /usr/include/*/*.h

Ćwiczenia

Wykonaj te ćwiczenia w języku C.

  1. Napisz program o nazwie atoi , który otwiera plik danych nazwany w wierszu poleceń i wczytuje z niego pojedynczy wiersz wejściowy, który powinien zawierać liczbę całkowitą przedstawioną w znakach ASCII. Program konwertuje ten ciąg na liczbę całkowitą, mnoży liczbę całkowitą przez 3 i wypisuje wynik w formacie standardowym. Program nie może używać funkcji atoi() . Powinieneś użyć programu make . Twój plik Makefile powinien mieć trzy reguły: atoi , run (który uruchamia program na standardowych danych testowych i przekierowuje dane wyjściowe do nowego pliku) i clean(który usuwa pliki tymczasowe). Upewnij się, że program działa poprawnie na złych danych i kończy działanie z pomocnym komunikatem, jeśli brakuje pliku danych lub jest on nieczytelny. Przejdź przez program, uruchamiając go za pomocą gdb , umieszczając punkt przerwania w funkcji main() i wielokrotnie używając polecenia step .
  2. Wyszukaj stronę podręcznika programu cat . Zakoduj własną wersję cat . Twoja wersja musi akceptować wiele parametrów nazw plików (lub nie akceptować ich wcale). Nie musi akceptować żadnych parametrów opcji.
  3. Napisz program usuwaniaSuffix , który przyjmuje pojedynczy parametr: nazwę pliku sufiksu. Plik przyrostków zawiera jedną linię na wpis. Wpis to niepusty ciąg znaków, który nazywamy przyrostkiem , po którym następuje znak >, po którym następuje inny (prawdopodobnie pusty) ciąg znaków, który nazywamy zamianą . Twój program powinien przechowywać wszystkie przyrostki i ich zamienniki w tablicy mieszającej. Użyj zewnętrznego łańcucha. Twój program powinien następnie czytać standardowe wejście. Dla każdego słowa w oddzielonego spacjami w danych wejściowych znajdź najdłuższy przyrostek s występujący w w i zmodyfikuj w , usuwając słowo s i wstawiajączamiana s , utworzenie w' . Wypisz jedną linię na każde zmodyfikowane słowo w postaci w>w' . Nie pisz żadnych słów, które nie zostały zmodyfikowane.