2009-10-29 16 views
5
{ 
    char *a, *b; 

    printf("%lx\n",(b-a)); 
} 

Zwykle działa, w rzeczywistości, nie mogę sobie wyobrazić, że daje ostrzeżenie lub niepowodzenie na maszynie 32-bitowej lub 64-bitowej. Ale czy to jest właściwe dla ANSI C i świadomości rozmiaru? Chciałbym, aby ten kod działał na każdej możliwej platformie, włączając w to systemy inne niż Unix i systemy wbudowane.char * a, * b; jaki jest typ (b-a) i jak mogę go wydrukować?

Odpowiedz

22

to ptrdiff_t, który można wydrukować w formacie %td w swoim formacie printf. Z sekcji specyfikacji 6.5.6 addytywne operatorzy:

Gdy dwa wskaźniki są odejmowane, oba powinny wskazywać na elementy tego samego obiektu tablicy lub jeden obok ostatniego elementu obiektu tablicy; wynikiem jest różnica indeksu dolnego dwóch elementów tablicy. Rozmiar wyniku jest definiowany przez implementację, a jego typ (podpisany typ całkowity) to ptrdiff_t zdefiniowany w nagłówku <stddef.h>.

Na printf a funkcje, rozdział 7.19.6 sformatowane funkcji wejścia/wyjścia:

t Określa następnym d, i, o, u, x lub X konwersji specyfikator zastosowanie do ptrdiff_t lub odpowiedniego całkowita bez znaku typu parametru; lub że następujący specyfikator konwersji stosuje się do wskaźnika do argumentu ptrdiff_t.

I grzebali w specyfikacji kilku, i to zdaje się wskazywać, że różnica dwóch wskaźników może nawet nie pasuje w ptrdiff_t, w którym to przypadku zachowanie jest niezdefiniowane:

J. 2 Niezdefiniowane zachowanie - Wynik odjęcia dwóch wskaźników nie jest reprezentowany w obiekcie typu ptrdiff_t (6.5.6).

Chociaż nie mogę sobie wyobrazić żadnego wdrożenia, w którym mogłoby to wystąpić. Sądzę, że możesz sprawdzić PTRDIFF_MIN i PTRDIFF_MAX w <stdint.h>, aby być naprawdę pewnym.

+0

+1 Dla C typowych dziwactw, takich jak różnica wskaźnika nie pasująca do wnętrza 'ptrdiff_t' (mogę też wymyślić dużo obrzydliwsze słowa niż" dziwactwa "). –

+1

Tak, okazuje się, że 'PTRDIFF_MIN' /' PTRDIFF_MAX' może być tak mały jak +/- 65536 (zgodnie ze specyfikacją). Sprawdzone przeze mnie implementacje nie zrobiły jednak takich szalonych rzeczy - w praktyce wydaje się mało prawdopodobne, że coś dziwnego się pojawi. –

+0

Rozważałbym implementację, w której 'ptrdiff_t' jest mniejszy (szerokość w bitach) niż największy możliwy rozmiar tablicy, który jest nie do złamania, a nawet implementacje, w których liczba bitów * wartości * jest mniejsza (np. Tablice większe niż elementy' SIZE_MAX/2' dozwolone) raczej niebezpieczne ... –

2

Ponieważ nie zainicjowano zmiennych a i b, kod podaje niezdefiniowane zachowanie. Ale poza tym, typ b-a jest wystarczająco duży, aby zawrzeć wynik. Jeśli masz wystarczająco nowoczesną literę C, możesz wydrukować ją za pomocą % tx.

Jeśli nie chcesz używać % tx, należy przekonwertować wynik tak faktycznie odpowiada (i to nie tylko przez przypadek) format specyfikatora:

printf("%lx", (unsigned long)(a-b)); 

Nie jest wykluczone, że system może mieć na przykład 32-bitową przestrzeń adresową i 32-bitową ptrdiff_t, ale 64-bitową, a następnie twoja printf zawiedzie.

+3

Nie, size_t jest bez znaku. –

+0

@Nikolai: Dzięki. Muszę być pijana. –

+0

Jak nowoczesne byłoby moje C? Moje obecne C ma to, ale jakie są szanse, że trafię na C, który go nie ma? Kiedy dodano% tx do specyfikacji? –

5

To ptrdiff_t. Od man stddef.h:

 
ptrdiff_t 
       Signed integer type of the result of subtracting two pointers. 

wydrukować go z %td.

12

Wynikiem b - a określa się tylko wówczas, gdy obie a i b wskazują elementy o tej samej tablicy char. To wymaganie może być również interpretowane jako a i b wskazujące na bajt należący do tego samego obiektu, ponieważ każdy obiekt może zostać ponownie zinterpretowany jako tablica znaków.

W przeciwnym razie wynik jest niezdefiniowany. To znaczy. próba odjęcia takich wskaźników skutkuje niezdefiniowanym zachowaniem.

Po zdefiniowaniu wyniku ma on typ ptrdiff_t. ptrdiff_t to nazwa typedef i ukryty za tą nazwą typedef jest zdefiniowany przez implementację. Typ jest znany z tego, że jest podpisany.

Należy również zauważyć, że język C nie gwarantuje, że ptrdiff_t jest wystarczająco duży, aby pomieścić wynik jakiegokolwiek odejmowania, nawet jeśli wskaźniki wskazują elementy tej samej tablicy. Jeśli wskaźniki są zbyt oddalone od siebie, aby typ ptrdiff_t mógł pomieścić wynik, zachowanie jest niezdefiniowane.

Nie ma specyficzny specifier Format printf dla ptrdiff_t nawet w C99, więc prawdopodobnie będziesz lepiej przekształcenie go do wystarczająco dużej podpisanej typu całkowitego i użyć formatu specyfikatora dla tego typu

printf("%ld\n", (long) (b - a)); 

Correction : C99 ma modyfikator długości dla ptrdiff_t. Właściwym sposobem, aby wydrukować wynik w C99 byłoby

printf("%td\n", b - a); 

Zauważ, że t jest modyfikator długości. Można go łączyć ze specyfikacjami konwersji d, o, u, x lub X, w zależności od tego, jaki format wyjściowy chcesz uzyskać. W C89/90 nadal będziesz musiał trzymać się za pomocą odpowiednio dużego podpisanego typu.

P.S. Powiedziałeś, że nie możesz sobie wyobrazić, że zawodzi na maszynie 32-bitowej lub 64-bitowej. W rzeczywistości bardzo łatwo jest sobie wyobrazić (lub faktycznie to zrobić) porażkę. Zobaczysz, że ptrdiff_t na komputerze 32-bitowym jest zwykle typu 32-bitowego. Ponieważ jest to typ podpisany, ma tylko 31 bitów dostępnych do reprezentowania wielkości wartości. Jeśli weźmiesz dwa wskaźniki, które są dalej od siebie (tj. Wymagają 32 bitów do reprezentowania "odległości"), wynik b - a przepełni się i będzie pozbawiony znaczenia.Aby zapobiec tej usterce, potrzebujesz co najmniej 33-bitowego podpisu ptrdiff_t na 32-bitowym komputerze i co najmniej 65-bitowego podpisu ptrdiff_t na maszynie 64-bitowej. Implementacje zwykle tego nie robią, po prostu używają "uprawnienia" standardu do tworzenia niezdefiniowanych zachowań w przepełnieniu.

+0

Co jest "wystarczająco duże"? Rzucanie na długo wydaje się "wystarczająco duże" na pierwszy rzut oka, czy istnieje system, w którym wskaźnik nie będzie pasował długo i będzie wymagał długiego czasu? Oczywiście w systemach 32-bitowych długa jest 32-bitowa, a więc jest wystarczająco duża, aw systemach 64-bitowych długa jest 64-bitowa, a więc jest wystarczająco duża. Ale czy naprawdę jestem objęty wszystkimi (ha!) Systemami? Dzięki za ostrzeżenie o znakach, nie martwię się, że te dwa wskaźniki rzeczywiście wskazują na tę samą tablicę i nie mogę sobie wyobrazić, że tablica będzie większa niż połowa rozmiaru przestrzeni adresowej. –

+0

"Wystarczająco duże" oznacza: przeanalizuj platformę (rozmiar wskaźnika, w szczególności) i spójrz na rozmiary różnych typów integralnych na platformie. Wybierz odpowiedni konkretny typ całki. Zwykle byłby to podpisany typ całkowy, którego rozmiar jest taki sam, jak rozmiar wskaźnika. – AnT

+0

Jeśli implementacja zapewnia, że ​​wskaźniki w tej samej tablicy nigdy nie różnią się więcej niż 2^31-1 lub 2_63-1 (tzn. Że tablice nigdy nie mają więcej niż te wiele elementów), wówczas możliwość przepełnienia nie jest problemem. Zasadniczo wszystko, co musi zrobić implementacja, musi uniemożliwić 'malloc' większe niż' SIZE_MAX/2'. –

Powiązane problemy