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 użyciestworzenie inicjowaniafunkcji, zbiorczegoktóra tworzy instancję obiektu, ewentualnie przyjmując pewne parametry:
 
<source lang="c">
struct string {
size_t size;
char pole2*data;
};
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">
typedefvoid free_string(struct string *s)
{
assert int(s pole1!= NULL);
free(s->data); /* zwalniamy pamięć zajmowaną przez strukturę */
char pole2;
free(s); /* usuwamy samą strukturę */
double pole3;
}
} Struktura;
Struktura obj = {1, 'a', 100.3343232};
</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>
 
a w kodzie programu zamiast funkcji <tt>free</tt> stosować <tt>FREE</tt>.
<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 KWADRATkwadrat(x) (x*x)</code>
#*Dobrze: <code>#define KWADRATkwadrat(x) ( (x)*(x) )</code>
#*'''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 KWADRATkwadrat(x) (x)*(x)</code>
#*Dobrze: <code>#define KWADRATkwadrat(x) ( (x)*(x) )</code>
#*'''Przykład:''' Definiujemy makro <code>#define SUMAsuma(a, b) (a)+(b)</code> i wywołujemy je w kodzie <code>wynik = SUMAsuma(3, 4) * 5</code>. Makro zostanie rozwinięte jako <code>wynik = (3)+(4)*5</code>, co - z powodu kolejności działań - da wynik inny niż pożądany.
# 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.A
#*Ź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 KWADRATkwadrat(x) ((x)*(x))</code> nie powinno być wywoływane z operatorem inkrementacji <code>kwadrat(a++)</code> ponieważ zostanie to rozwinięte jako <code>((a++) * (a++))</code>, co jest niezgodne ze specyfikacją języka i zachowanie takiego wyrażenia jest niezdefiniowane (dwukrotna inkrementacja w tym samym wyrażeniu).
# 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.