2016-03-16 17 views
17

Czy odejmowanie niepodzielnych adresów jest zdefiniowane w C? W C++?Odejmowanie niepodzielnych adresów adresów

Oto przykład:

void* p = malloc(64); 

int* one = (int*)((char*)p); 
int* two = (int*)((char*)p + 7); 

printf("%x %x %d %d\n", one, two, sizeof(int), two - one); 

Ideone link.

Otrzymuję wyjście 8a94008 8a9400f 4 1, więc wygląda na to, że robi podział i obcina pozostałą część. Czy zdefiniowano zachowanie?

+8

Wywołałeś * niezdefiniowane zachowanie * przekazując dane o błędnym typie do 'printf()'. Poprawną instrukcją do wydrukowania będzie 'printf ("% p% p% zu% td \ n ", (void *) jeden, (void *) dwa, sizeof (int), dwa - jeden);' – MikeCAT

+1

Nie mam cytuj. Arytmetyka wskaźnikowa opiera się na arytmetyce całkowitej i jest dobrze zdefiniowana. Myślę więc, że "dwa - jeden" jest dobrze zdefiniowany. nawet jeśli wskaźniki nie są zapakowane tak, jak można by oczekiwać rozsądnej osoby. Z drugiej strony - nie rób tego, nikt nie zauważy tego w twojej bazie kodów. – Johannes

+3

Nawet samo trzymanie wskaźnika "dwa" w twoim programie jest niepoprawne, ponieważ wskaźnik nie spełnia ograniczeń wyrównania dla 'int' (chociaż w praktyce na większości sprzętu nie wywoła błędu, dopóki faktycznie nie spróbujesz go usunąć procesor, który nie koryguje braku dostępu do pamięci jak ARM) – Thomas

Odpowiedz

20

ta jest zdefiniowana zachowanie według 5.7.6:

Gdy dwa wskaźniki do elementów tego samego obiektu tablicy są odejmowane, wynik jest różnicą indeksów dwóch elementów macierzy. [...] O ile oba wskaźniki nie wskazują na elementy tego samego obiektu macierzy, lub jeden za ostatnim elementem obiektu tablicy, zachowanie jest niezdefiniowane.

W kodzie, wskaźnik two nie jest skierowany do elementu tego samego int tablicy jako wskaźnik one. W rzeczywistości nie wskazuje na żaden element tablicy z p, ponieważ wskazuje na "środek" jednego z elementów (który sam w sobie jest niezdefiniowanym zachowaniem).

+4

To prawda, ale UB dzieje się po raz pierwszy w '(int *) ((char *) p + 7)'. – user694733

+0

@ user694733 Czy jesteś pewny, że nieprawidłowy rzut powoduje UB nawet bez dereferencji? Próbowałem znaleźć coś takiego w standardzie, ale nie znalazłem ostatecznej odpowiedzi. – dasblinkenlight

+0

Możesz konwertować wskaźnik z malloc na dowolny wbudowany typ wskaźnika. Jednak dla '(char *) p + 7' nie ma już dłużej, konwersja z' char * 'na' int * 'jest nieprawidłowa. – user694733

19

W pewnych założeniach w C Trzeci wiersz:

int* two = (int*)((char*)p + 7); 

już powoduje nieokreśloną problem, ponieważ wskaźnik p nie jest prawidłowo wyrównana w rodzaju nie odwołuje .


Zakłada się, że wymagania osiowania dla typu int są wyższe niż dla typu char. Dotyczy to większości współczesnych architektur. Ponieważ wszystkie linie trasowania muszą być potęgami dwóch: , a wartość 7 nie jest, dodanie tej wartości do wskaźnika p nie może wytworzyć wskaźnika z wyrównaniem, które jest tak ścisłe jak wymaganie wyrównania dla typu int.

(Cyt z ISO/IEC 9899: 201X 6.3.2.3 Wskaźniki 7)
wskaźnik do typu obiekt może być przekształcany do wskaźnika na inny typ obiektu. Jeśli wskaźnik wynikowy nie jest poprawnie wyrównany dla typu odniesienia, zachowanie jest nieokreślone jako .

(Cytat z: ISO/IEC 9899: 201x 6.2.8 Dostosowanie obiektów 4.)
Każda ważna wartość wyrównanie będzie nieujemną integralną potęgą dwójki.

+1

Nie mogę obecnie znaleźć rozdziału i wersetu, ale to samo dotyczy C++. Obliczenie "dwóch" będzie niezdefiniowanym zachowaniem. (Wyobraź sobie maszynę opartą na słowie ze specjalnymi rejestrami dla wskaźników. Obliczanie "dwójki" przejdzie przez jeden z tych rejestrów, a może barfować na niewyrównanym wskaźniku - takie maszyny istniały w przeszłości.) –

Powiązane problemy