2013-05-12 16 views
7

Załóżmy, że ma do pętli, która przechowuje zer w tablicy przy użyciu wskaźników tak:testowe Wskaźniki użyciem operatora porównania w pętli

int *vp, values[5]; 

for(vp = &values[5-1]; vp >= &values[0]; vp--) 
    *vp = 0; 

W Book- wskazówek, C, że istnieje problem z tą pętlą, ponieważ porównanie vp >= &values[0] jest niezdefiniowane, ponieważ przesunęło się poza granice tablicy. Ale jak?

+2

to „niezdefiniowane”, ale na 95% systemów będzie dobrze. Scenariusz * jedyny *, w którym porównanie nie powiedzie się, będzie miał wartość 'i wartości [0]' == 0x0. –

+0

Prawdopodobnie więcej niż 95%. =) –

+0

Założę się, że nie działałoby to w implementacji C na Symbolics Lisp Machines. Nie mam pojęcia, czy nadal są jakieś w użyciu; firma wyszła z biznesu prawie 20 lat temu. – Barmar

Odpowiedz

2

Zakładając, że wskaźnik jest równoważny z liczbą całkowitą bez znaku, możemy zauważyć, że problem występuje tylko wtedy, gdy values rozpoczęło się pod adresem 0, w którym to przypadku wskaźnik byłby zawijany po zmniejszeniu i stałby się UINT_MAX.

Aby zobrazować problem, niech krok po kroku, co się dzieje, zakładając values rozpoczyna się pod adresem 0x0:

iteration 1: 
vp = 0x4, *vp = 0; 

iteration 2: 
vp = 0x3, *vp = 0; 

iteration 3: 
vp = 0x2, *vp = 0; 

iteration 4: 
vp = 0x1, *vp = 0; 

iteration 5: 
vp = 0x0, *vp = 0; 

iteration 6: 
vp = 0xFFFFFFFF; *vp = ?? // uh oh! 

Zatem vp nigdy nie będzie mniejsza od minimalnej wartości wskaźnika (czyli 0), oraz spowodowałoby to nieskończoną pętlę (zakładając, że cała pamięć jest zapisywalna) lub błąd segmentacji.

Jest to również niezdefiniowane zachowanie zgodne ze standardem (ponieważ można zaadresować jeden element za tablicą, ale nie przed nim), ale w rzeczywistości nie powinno to nigdy zawieść w żadnym realistycznym systemie.

+0

Tak więc można z niego bezpiecznie korzystać lub mogę sprawdzić, czy jest większy niż tylko. –

+0

@ ashish2expert Tak, to moja ocena, że ​​jest bezpieczna, chyba że masz do czynienia z bardzo dziwnym systemem (którego prawdopodobnie nie ma tutaj). –

+3

Ten kod może się nie udać w implementacjach ze wskaźnikami segmentu i offsetu, ale może również zawieść, ponieważ optymalizator ma prawo przyjąć, że 'vp> = i wartości [0]' nigdy nie są wykonywane z 'vp' nie wskazując na element tablica lub poza nią (ponieważ byłoby to niezdefiniowane zachowanie). Dlatego optymalizator może zachowywać się tak, jakby to wyrażenie było zawsze prawdziwe i może je wyeliminować, powodując nieskończoną pętlę. –

4

Ten kod nie jest bezpieczny, nawet jeśli stare lub egzotyczne architektury procesorów są wykluczone.

Optymalizator w kompilatorze zawiera wiele reguł dotyczących tego języka. Kiedy widzi vp >= &values[0], gdzie values jest tablicą, optymalizator ma prawo przyjąć, że vp wskazuje na element tablicy lub poza tablicę, ponieważ w przeciwnym razie wyrażenie nie jest zdefiniowane przez język C.

Reguły i mechanizmy wbudowane w optymalizator mogą zatem zadecydować, że vp >= &values[0] jest zawsze prawdziwe, więc może wytworzyć kod tak, jakby zapisano for (vp = &values[5-1]; ; vp--). Powoduje to, że pętla bez warunku zakończenia i dalsze nieokreślone zachowanie są oceniane, gdy vp wskazuje poza tablicę.

+0

Tak, jest to możliwe, ale prawdopodobnie jest to mało prawdopodobne, chyba że ustawiono określone flagi optymalizacji. Jeśli dobrze pamiętam, GCC ma opcję, która łamie pętle w ten sposób, ale domyślnie jest wyłączona, a optymalizator klang nie dotknie nawet takich rzeczy. –

+0

@ RichardJ.RossIII: Czy twierdzisz, że ten kod jest bezpieczny zgodnie ze standardem C, lub po prostu bezpieczny, jeśli masz konkretną wiedzę o niektórych kompilatorach? Proszę przytoczyć dokumentację wspierającą to. –

0
int values[5]; 
size_t idx; 

for(idx=5; idx-- > 0;) { 
    values[idx] = 0; 
    } 

Może to wyglądać dziwnie od pierwszego wejrzenia, ale gdy już się przyzwyczaicie, może to być dobry wzór.

  • unikać (ewentualnego) niedomiar wartości wskaźnika (co jest ściśle zdefiniowana)
  • Nie można niedopełniony, typ bez znaku (np size_t) będzie owinąć dookoła, co z pewnością spowoduje nielegalne dostęp, który jest preferowany do podstępnego adresu: jeden pod niższym, który spowoduje uszkodzenie pamięci zamiast z powodu zbyt szybkiego braku sygnału.
  • WRT do prędkości/wydajności: z punktu widzenia kompilatorów, wskaźniki i indeksowanie są w zasadzie takie same. Kompilator może nawet wygenerować ten sam kod dla obu przypadków.

BTW: jeśli nalegać na wykorzystaniu wskaźników, co następuje zrobi:

int *vp, values[5]; 

for(vp = &values[5-1]; vp ; vp = (vp > &values[0]) ? vp-1 : NULL;) { 
    *vp = 0; 
    } 
Powiązane problemy