C/Powszechne praktyki: Różnice pomiędzy wersjami

Usunięta treść Dodana treść
Kj (dyskusja | edycje)
mNie podano opisu zmian
Kj (dyskusja | edycje)
mNie podano opisu zmian
Linia 8:
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 stworzenie funkcji, która tworzy instancję obiektu, ewentualnie przyjmując pewne parametry:
 
<pre> struct string {
size_t size;
char *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;
}
}</pre>
 
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:
 
void free_string(struct string *s) {
<pre>
{
void free_string(struct string *s) {
assert (s != NULL);
free(s->data); /* zwalniamy pamiec zajmowana przez strukture */
free(s); /* usuwamy sama strukture */
}
</pre>
 
Często łączy się destruktory z [[#Zerowanie zwolnionych wskaźników|zerowaniem zwolnionych wskaźników]].
Linia 37 ⟶ 36:
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:
 
<pre> struct string;
struct string *create_string(const char *initial);
void free_string(struct string *s);</pre>
 
== Zerowanie zwolnionych wskaźników ==
Linia 46 ⟶ 45:
Jednym z prostych rozwiązań tego problemu jest zapewnienie, że każdy wskaźnik jest zerowany natychmiast po zwolnieniu:
 
<pre> free(p);
p = NULL;</pre>
 
Inaczej niż w przypadku "wiszących wskaźników", na wielu nowoczesnych architekturach przy próbie użycia wyzerowanego wskaźnika pojawi się sprzętowy wyjątek. Dodatkowo, programy mogą zawierać sprawdzanie błędów dla zerowych wartości, ale nie dla "wiszących wskaźników". Aby zapewnić, że jest to wykonywane dla każdego wskaźnika, możemy użyć makra:
 
<pre> #define FREE(p) do { free(p); (p) = NULL; } while(0)</pre>
 
<small>(aby zobaczyć dlaczego makro jest napisane w ten sposób, zobacz [[#Konwencje pisania makr]])</small>
Linia 57 ⟶ 56:
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]]:
 
<pre> void free_string(struct string **s) {
{
assert(s != NULL && *s != NULL);
assert(s != NULL && *s != NULL);
FREE((*s)->data); /* zwalniamy pamiec zajmowana przez strukture */
FREE(*s); /* usuwamy strukture */
}
}</pre>
 
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.
Linia 90:
Oprócz tego są także przesunięcia ('''<nowiki><<</nowiki>''' oraz '''<nowiki>>></nowiki>'''). Zastanówmy się teraz, jak je wykorzystać w praktyce. Załóżmy, że zajmujemy się jednobajtową zmienną.
 
unsigned char i = 2;
 
Z matematyki wiemy, że zapis binarny tej liczby wygląda tak (w ośmiobitowej zmiennej):
00000010. Jeśli teraz np. chcielibyśmy "zapalić" drugi bit od lewej (tj. bit, którego zapalenie niejako "doda" do liczby wartość 2<sup>6</sup>) powinniśmy użyć logicznego lub:
 
unsigned char i = 2;
i |= 64;
 
Gdzie 64=2<sup>6</sup>. Odczytywanie wykonuje się za pomocą tzw. maski bitowej. Polega to na:
Linia 103:
Do "wyłuskania" odpowiedniego bitu możemy posłużyć się operacją "i" - czyli operatorem '''&'''. Wygląda to analogicznie do posługiwania się operatorem "lub":
 
unsigned char i = 3; /* bitowo: ''00000011'' */
unsigned char temp = 0;
temp = i & 1; /* sprawdzamy najmniej znaczący bit - czyli pierwszy z prawej */
if (temp) {
printf ("bit zapalony");
else {
printf ("bit zgaszony");
}
 
Jeśli nie władasz biegle kodem binarnym, tworzenie masek bitowych ułatwią ci przesunięcia bitowe. Aby uzyskać liczbę która ma zapalony bit o numerze <code>n</code> (bity są liczone od zera), przesuwamy bitowo w lewo jedynkę o <code>n</code> pozycji:
 
1 << n
 
Jeśli chcemy uzyskać liczbę, w której zapalone są bity na pozycjach <code>l, m, n</code> - używamy sumy logicznej ("lub"):
 
(1 << l) | (1 << m) | (1 << n)
 
Jeśli z kolei chcemy uzyskać liczbę gdzie zapalone są wszystkie bity poza <code>n</code>, odwracamy ją za pomocą operatora logicznej negacji <code>~</code>
 
~(1 << n)
 
Warto władać biegle operacjami na bitach, ale początkujący mogą (po uprzednim przeanalizowaniu) zdefiniować następujące makra i ich używać:
 
/* Sprawdzenie czy w liczbie k jest zapalony bit n */
#define IS_BIT_SET(k, n) ((k) & (1 << (n)))
 
/* Zapalenie bitu n w zmiennej k */
#define SET_BIT(k, n) (k |= (1 << (n)))
 
/* Zgaszenie bitu n w zmiennej k */
#define RESET_BIT(k, n) (k &= ~(1 << (n)))