2013-04-29 8 views
20

Posiadałem kod w mojej aplikacji, który wygląda następująco. Otrzymałem informacje zwrotne na temat błędu, kiedy ku mojemu przerażeniu umieściłem na nim debugger i odkryłem, że MAX pomiędzy -5 a 0 wynosi -5!Funkcja MAX/MIN w Celu C, która pozwala uniknąć problemów z odlewaniem.

NSString *test = @"short"; 
int calFailed = MAX(test.length - 10, 0);      // returns -5 

Po przejrzeniu makra MAX widzę, że oba parametry muszą być tego samego typu. W moim przypadku "test.length" to niepodpisana int, a 0 to podpisana int. Tak więc prosty rzut (dla dowolnego parametru) rozwiązuje problem.

NSString *test = @"short"; 
int calExpected = MAX((int)test.length - 10, 0);     // returns 0 

Wydaje się to nieprzyjemnym i nieoczekiwanym efektem ubocznym tego makra. Czy istnieje inna wbudowana metoda na iOS dla wykonywania MIN/MAX, gdzie kompilator ostrzegłby o rodzajach niedopasowanych? Wydaje się, że to jest problem z czasem kompilacji, a nie czymś, co wymagało od debuggera. Zawsze mogę napisać własne, ale chciałem sprawdzić, czy ktoś inny miał podobne problemy.

+0

Masz na myśli jak "fmax"? –

+0

Dziękuję. Pomogło mi to. – Raja

Odpowiedz

12

Prawdopodobnie nie ma wystarczającej ilości ostrzeżeń kompilatora włączonych. Jeśli włączysz -Wsign-compare (który można włączyć z -Wextra) będzie generować ostrzeżenie, że wygląda następująco

warning: signed and unsigned type in conditional expression [-Wsign-compare] 

Pozwala to na umieszczenie odlewane w odpowiednich miejscach jeśli to konieczne i nie ma potrzeby aby przepisać makra MAX lub MIN

+3

Tak, to załatwiło sprawę. Oczywiście teraz mój projekt zawiera 107 nowych problemów. Zastanawiam się, ile jest takich błędów. Dzięki za wskazówkę. – raider33

+1

@ raider33 Polecam również dodanie '-Wall', aby włączyć więcej ostrzeżenia kompilatora, jeśli jeszcze go nie masz – FDinoff

+1

Skoro mówimy o ObjC, prawdopodobnie użyjemy' clang' zamiast 'gcc'. Więc możesz zrobić to lepiej niż ['-Wall'] (http://assets.diylol.com/hfs/b1e/58b/811/resized/sad-all-the-things-meme-generator-turn-on-all -the-warnings-6e57ad.jpg): włącz ['-Weverything'] (http://assets.diylol.com/hfs/3a0/292/5f5/resized/all-the-things-meme-generator-turn -on-all-the-ostrzeżenia-5a92fa.jpg) za * wszystkie ostrzeżenia *. Następnie spróbuj '-Werror' dla osiągnięcia Hard Mode. – rickster

29

Włączenie , zgodnie z sugestią FDinoffa, jest dobrym pomysłem, ale pomyślałem, że warto wyjaśnić przyczynę tego bardziej szczegółowo, ponieważ jest to dość powszechna pułapka.

Problem nie dotyczy w szczególności makra MAX, ale z a) odejmowaniem od liczby całkowitej bez znaku w sposób, który prowadzi do przepełnienia, oraz b) (jak sugeruje ostrzeżenie) z tym, jak kompilator obsługuje porównanie ogólnie wartości podpisanych i niepodpisanych.

Pierwszy problem jest dość łatwy do wytłumaczenia: Gdy odejmiesz od liczby całkowitej bez znaku, a wynik będzie ujemny, wynik "przepełni się" do bardzo dużej wartości dodatniej, ponieważ liczba całkowita bez znaku nie może reprezentować wartości ujemnych. Tak więc [@"short" length] - 10 oceni na 4294967291.

Co może być bardziej zaskakujące jest to, że nawet bez odejmowania, coś MAX([@"short" length], -10) nie przyniesie poprawny wynik (to ocenić na -10, choć [@"short" length] byłoby 5, co jest oczywiście większe). To nie ma nic wspólnego z makrem, coś takiego jak if ([@"short" length] > -10) { ... } doprowadziłoby do tego samego problemu (kod w bloku if wykonałby się , a nie).

Tak więc ogólne pytanie brzmi: co dzieje się dokładnie podczas porównywania liczby całkowitej bez znaku z podpisaną (i dlaczego w ogóle jest to ostrzeżenie)? Kompilator skonwertuje obie wartości do wspólnego typu, zgodnie z pewnymi regułami, które mogą prowadzić do zaskakujących wyników.

Cytując Understand integer conversion rules [cert.org]:

  • Jeśli typ operandu z podpisanym typu całkowitego mogą reprezentować wszystkie wartości typu argumentu z unsigned integer, operand z unsigned integer przekonwertowany na typ operandu ze znakiem typu integer.
  • W przeciwnym razie oba operandy są konwertowane na liczbę całkowitą typu bez znaku, odpowiadającą typowi operandu ze znakiem ze znakiem liczby całkowitej.

(Kopalnia nacisk)

Rozważmy następujący przykład:

int s = -1; 
unsigned int u = 1; 
NSLog(@"%i", s < u); 
// -> 0 

wynik będzie 0 (fałsz), choć s (-1) jest wyraźnie mniejsza niż u (1). Dzieje się tak, ponieważ obie wartości są konwertowane na unsigned int, ponieważ int nie może reprezentować wszystkich wartości, które mogą być zawarte w unsigned int.

To staje się jeszcze bardziej zagmatwane, jeśli zmienisz typ s na long. Następnie uzyskasz taki sam (niepoprawny) wynik na platformie 32-bitowej (iOS), ale w 64-bitowej aplikacji Mac będzie działać dobrze! (Wyjaśnienie: long jest 64 bitowy typ tam, więc może reprezentować wszystkie 32 bitowe unsigned int wartości.)

Więc, krótko mówiąc: Nie porównuj niepodpisane i podpisane liczb całkowitych, zwłaszcza jeśli podpisane wartość jest potencjalnie negatywne.

+1

Bardzo dobre wytłumaczenie. –

Powiązane problemy