C/Powszechne praktyki: Różnice pomiędzy wersjami
Usunięta treść Dodana treść
Anulowanie wersji nr 171448 utworzonej przez 178.42.163.56 (dyskusja) |
Anulowanie wersji nr 171447 utworzonej przez 178.42.163.56 (dyskusja) |
||
Linia 4:
W większości obiektowych języków programowania obiekty nie mogą być tworzone bezpośrednio - obiekty otrzymuje się wywołując specjalną metodę danej klasy, zwaną konstruktorem. Konstruktory są ważne, ponieważ pozwalają zapewnić obiektowi odpowiedni stan początkowy. Destruktory, wywoływane na końcu czasu życia obiektu, są istotne, gdy obiekt ma wyłączny dostęp do pewnych zasobów i konieczne jest upewnienie się, czy te zasoby zostaną zwolnione.
Ponieważ C nie jest językiem obiektowym, nie ma wbudowanego wsparcia dla konstruktorów i destruktorów. Często programiści bezpośrednio modyfikują tworzone obiekty i struktury. Jednakże prowadzi to do potencjalnych błędów, ponieważ operacje na obiekcie mogą się nie powieść lub zachować się nieprzewidywalnie, jeśli obiekt nie został prawidłowo zainicjalizowany. Lepszym podejściem jest
<source lang="c">
struct string {
size_t size;
};
struct string *create_string(const char *initial) {
assert (initial != NULL);
struct string *new_string = malloc(sizeof(*new_string));
if (new_string != NULL) {
new_string->size = strlen(initial);
new_string->data = strdup(initial);
}
return new_string;
}
</source>
Podobnie, bezpośrednie usuwanie obiektów może nie do końca się udać, prowadząc do wycieku zasobów. Lepiej jest użyć destruktora:
<source lang="c">
{
assert
free(s->data); /* zwalniamy pamięć zajmowaną przez strukturę */
▲ char pole2;
free(s); /* usuwamy samą strukturę */
}
</source>
Często łączy się destruktory z [[#Zerowanie zwolnionych wskaźników|zerowaniem zwolnionych wskaźników]].
Czasami dobrze jest ukryć definicję obiektu, żeby mieć pewność, że użytkownicy nie utworzą go ręcznie. Aby to zapewnić struktura jest definiowana w pliku źródłowym (lub prywatnym nagłówku niedostępnym dla użytkowników) zamiast w pliku nagłówkowym, a deklaracja wyprzedzająca jest umieszczona w pliku nagłówkowym:
<source lang="c">
struct string;
struct string *create_string(const char *initial);
void free_string(struct string *s);
</source>
{{Porada|Jeśli znasz język [[C++]], mógłbyś użyć klas, a dzięki nim konstruktorów i destruktorów.}}
== Zerowanie zwolnionych wskaźników ==
Jak powiedziano już wcześniej, po wywołaniu <tt>free()</tt> dla wskaźnika, staje się on "wiszącym wskaźnikiem". Co gorsze, większość nowoczesnych platform nie potrafi wykryć, kiedy taki wskaźnik jest używany zanim zostanie ponownie przypisany.
Linia 27 ⟶ 58:
<source lang="c">
#define FREE(p) do { free(p); (p) = NULL; } while(0)
</source>
<small>(aby zobaczyć dlaczego makro jest napisane w ten sposób, zobacz [[#Konwencje pisania makr]])</small>
Przy wykorzystaniu tej techniki destruktory powinny zerować wskaźnik, który przekazuje się do nich, więc argument musi być do nich przekazywany przez referencję. Na przykład, oto zaktualizowany destruktor z sekcji [[#Konstruktory i destruktory|Konstruktory i destruktory]]:
<source lang="c">
void free_string(struct string **s)
{
assert(s != NULL && *s != NULL);
FREE((*s)->data); /* zwalniamy pamięć zajmowaną przez strukturę */
FREE(*s); /* usuwamy strukturę */
}
</source>
Niestety, ten idiom nie jest wstanie pomóc w wypadku wskazywania przez inne wskaźniki zwolnionej pamięci. Z tego powodu niektórzy eksperci C uważają go za niebezpieczny, jako kreujący fałszywe poczucie bezpieczeństwa.
== Konwencje pisania makr ==
Linia 36 ⟶ 80:
# Umieszczaj nawiasy dookoła argumentów makra kiedy to tylko możliwe. Zapewnia to, że gdy są wyrażeniami kolejność działań nie zostanie zmieniona. Na przykład:
#*Źle: <code>#define
#*Dobrze: <code>#define
#*'''Przykład:''' Załóżmy, że w programie makro kwadrat() zdefiniowane bez nawiasów zostało wywołane następująco: <code>kwadrat(a+b)</code>. Wtedy zostanie ono zamienione przez preprocesor na: <code>(a+b*a+b)</code>. Z kolejności działań wiemy, że najpierw zostanie wykonane mnożenie, więc wartość wyrażenia <code>kwadrat(a+b)</code> będzie różna od kwadratu wyrażenia <code>a+b</code>.
# Umieszczaj nawiasy dookoła całego makra, jeśli jest pojedynczym wyrażeniem. Ponownie, chroni to przed zaburzeniem kolejności działań.
#*Źle: <code>#define
#*Dobrze: <code>#define
#*'''Przykład:''' Definiujemy makro <code>#define
# Jeśli makro składa się z wielu instrukcji lub deklaruje zmienne, powinno być umieszczone w pętli <code>'''do''' { ... } '''while'''(0)</code>, bez kończącego średnika. Pozwala to na użycie makra jak pojedynczej instrukcji w każdym miejscu, jak ciało innego wyrażenia, pozwalając jednocześnie na umieszczenie średnika po makrze bez tworzenia zerowego wyrażenia. Należy uważać, by zmienne w makrze potencjalnie nie kolidowały z argumentami makra.
#*Źle: <code>#define FREE(p) free(p); p = NULL;</code>
#*Dobrze: <code>#define FREE(p) do { free(p); p = NULL; } while(0)</code>
# Unikaj używania argumentów makra więcej niż raz wewnątrz makra. Może to spowodować kłopoty, gdy argument makra ma efekty uboczne (np. zawiera operator inkrementacji).
#*'''Przykład:''' <code>#define
# Jeśli makro może być w przyszłości zastąpione przez funkcję, rozważ użycie w nazwie małych liter, jak w funkcji.
|