2014-06-23 28 views
59

Od odpowiedzi dostałam od this question, wydaje się, że C++ odziedziczyła ten wymóg przeliczenia short do int podczas wykonywania operacji arytmetycznych z C. Mogę odebrać swoje mózgi, aby dlaczego ten został wprowadzony w C w pierwszym miejsce? Dlaczego po prostu nie wykonywać tych operacji jako short?Dlaczego krótki musi być konwertowany na int przed operacjami arytmetycznymi w C i C++?

Na przykład (pobranej z sugestią DYP w komentarzach):

short s = 1, t = 2 ; 
auto x = s + t ; 

x będzie miał rodzaj int.

+7

@Jefffrey Integralna promocja jest częścią zwykłych konwersji arytmetycznych. 'krótki s = 1, t = 2; auto x = s + t; 'then' x' to 'int'. – dyp

+0

możliwy duplikat [Co to jest "Naturalny rozmiar" w języku C++?] (Http://stackoverflow.com/questions/24370732/what-does-natural-size-really-mean-in-c) –

+0

IMO: There było bardzo mało do zdobycia przez zobowiązanie zgodnego kompilatora do wykonywania matematyki całkowitej jako krótkiej (lub jako znaku). Bez żądania, dlaczego tego wymagać? – chux

Odpowiedz

35

Jeśli spojrzymy na Rationale for International Standard—Programming Languages—C w sekcji 6.3.1.8zwykle arytmetycznych konwersje mówi (podkr przyszłości):

zasady określone w normie dla tych konwersji są niewielkie modyfikacje te w K & R: modyfikacje uwzględniają dodane typy i zasady zachowania wartości. Wyraźna licencja została dodana do wykonywania obliczeń w trybie "szerszym" niż jest to absolutnie konieczne, , ponieważ może to czasami powodować tworzenie mniejszego i szybszego kodu, a nie do częstą wzmiankę o poprawnej odpowiedzi. Obliczenia mogą być również wykonywane w warstwie "węższej" według reguły tak długo, jak długo uzyskiwany jest ten sam końcowy wynik. odlew Explicit zawsze mogą być wykorzystywane w celu uzyskania wartość w pożądany typ

Sekcja 6.3.1.8 od draft C99 standard obejmuje zwykle arytmetycznych konwersje który jest stosowany do argumentów wyrażeń arytmetycznych na przykład sekcji 6.5.6addytywne operatorzy mówi :

Jeśli oba argumenty mają arytmetyczną typu, zwykłe arytmetyczne konwersje są wykonywane na nich .

Znajdziemy podobny tekst w sekcji 6.5.5multiplikatywne operatorzy również. W przypadku krótki argumentu, pierwszy promocje Integer są stosowane z sekcji 6.3.1.1logicznych, znaków i liczb całkowitych który mówi:

Jeśli int może reprezentować wszystkie wartości tego samego typu, wartość wynosi skonwertowana na int; w przeciwnym razie jest konwertowany na unsigned int. Są to tak zwane promocje całkowite. 48) Wszystkie inne typy są niezmienione przez promocje całkowite.

Dyskusja z sekcji 6.3.1.1 z Uzasadnieniem lub Międzynarodowy Standard-języków programowania-C na promocjami całkowitych jest rzeczywiście bardziej interesujący, idę do selektywnego zacytować b/c jest zbyt długi, aby w pełni cytat :

Implementacje wpadł dwóch głównych mieście, które mogą być scharakteryzowane jako unsigned zachowania i zachowania wartości.

[...]

W niepodpisane konserwujące podejście wzywa do promowania dwie mniejsze niepodpisanych typów do unsigned int. Jest to prosta reguła i daje typ , który jest niezależny od środowiska wykonawczego.

wartość zachowując podejście wzywa do promowania tych typów do podpisanego int jeśli tego typu może właściwie reprezentować wszystkie wartości typ oryginału, a inaczej dla promowania tych typów do unsigned int. Tak więc, jeśli środowisko wykonawcze jest krótkie, ponieważ coś jest mniejsze niż int, to unsigned short staje się int; w przeciwnym razie staje się unsigned int.

Może to mieć dość nieoczekiwane wyniki w niektórych przypadkach, jak pokazuje Inconsistent behaviour of implicit conversion between unsigned and bigger signed types, jest wiele takich przykładów. Chociaż w większości przypadków powoduje to, że operacje działają zgodnie z oczekiwaniami.

+2

Tak, czasami będzie mniejszy i szybszy, ponieważ nie potrzebujesz dodatkowych instrukcji do podpisywania/zerowania rozszerzania wartości do int lub maskowania wysokich bitów. W x86 nie potrzebujesz dodatkowych przedrostków instrukcji do zmiany wielkości argumentów: –

+0

Szkoda, że ​​racjonalne uzasadnienie nie dodało reguły dodatkowej, że jeśli wynik dodawania, mnożenia lub operacji bitowej jest wymuszany na typ bez znaku mniejszy niż 'int ', wyrażenie będzie zachowywać się tak, jakby jego operandy były podobnie wymuszone i operacja wykonywana na mniejszym typie. Nie ma zdefiniowanych przypadków, które byłyby sprzeczne z taką regułą, ale niektóre kompilatory mogą używać promocji jako pretekstu do wywnioskowania, że ​​wyrażenie takie jak 'x * = y;' (z obiema zmiennymi 'unsigned short') obiecuje, że' x' nie może przekracza 2147483648/y. – supercat

+0

jeśli mam coś takiego: int x = 1234' i 'char * y = & x'. Binarna reprezentacja '1234' to '00000000 00000000 00000100 11010010'. Moja maszyna jest mała, więc odwraca ją i przechowuje w pamięci '11010010 00000100 00000000 00000000' LSB jest na pierwszym miejscu. Teraz główna część. jeśli używam 'printf ("% d ", * p)'. 'printf' odczyta pierwszy bajt' 11010010 'tylko dane wyjściowe to' -46', ale '11010010' to' 210', więc dlaczego wypisuje '-46'. Jestem bardzo zdezorientowany, myślę, że niektóre char na integer promocji robi coś, ale nie wiem. –

20

Nie jest to funkcja języka, ponieważ jest ograniczeniem fizycznych architektur procesorów, na których działa kod. Typer int w C jest zwykle wielkością twojego standardowego rejestru CPU. Więcej krzemu zajmuje więcej miejsca i więcej mocy, więc w wielu przypadkach arytmetykę można wykonywać tylko w przypadku typów danych "naturalnej wielkości". Nie jest to prawdą uniwersalną, ale większość architektur nadal ma to ograniczenie. Innymi słowy, podczas dodawania dwóch liczb 8-bitowych, to, co faktycznie dzieje się w procesorze, to rodzaj 32-bitowej arytmetyki, po której następuje zwykła maska ​​bitowa lub inna odpowiednia konwersja typu.

+3

Nie jestem pewien, czy koniecznie musi być trochę maska. Procesor wykonuje arytmetykę w oryginalnym rozmiarze słowa, a następnie przechowuje tylko niższe bity z powrotem do pamięci. (Ponadto, jeśli masz rację, że większość architektury wykonuje tylko arytmetyczne słowo, jeden znaczący wyjątek, Intel, jest dość szeroko rozprzestrzeniony.) –

+0

@JamesKanze Masz rację. Edytowałem przez odpowiedź. I tak, Intel jest daleko, jeśli chodzi o zoptymalizowaną arytmetykę, szczególnie z tymi bibliotekami IPP. – Phonon

+6

Nie zgadzam się z "nie jest to cechą języka"; to jest cecha języka. Jest tak zdefiniowany, ponieważ ... ale jest definiowany przez język, a nie przez procesor. –

6

Połączone pytanie wydaje się dobrze to wyjaśniać: procesor po prostu tego nie robi. 32-bitowy procesor ma swoje natywne operacje arytmetyczne skonfigurowane dla rejestrów 32-bitowych. Procesor woli pracować w swoim ulubionym rozmiarze, a przy takich operacjach kopiowanie niewielkiej wartości do rejestru o oryginalnym rozmiarze jest tanie. (Dla architektury x86 rejestry 32-bitowe są nazwane tak, jakby były rozszerzonymi wersjami 16-bitowych rejestrów (eax do ax, ebx do bx itd.), Patrz x86 integer instructions).

W przypadku niektórych bardzo często wykonywanych operacji, w szczególności arytmetyki wektorowych/ruchomych, mogą występować specjalne instrukcje dotyczące innego rodzaju lub rozmiaru rejestru. W przypadku czegoś podobnego krótko, wypełnienie (do) 16 bitów zer ma bardzo mały koszt wydajności, a dodanie specjalistycznych instrukcji prawdopodobnie nie jest warte czasu ani miejsca na kości (jeśli chcesz się naprawdę fizycznie zastanowić, dlaczego. nie jestem pewny, czy zajmą rzeczywistą przestrzeń, ale stanie się to bardziej skomplikowane).

+2

To nie jest kwestia czysto sprzętowa, ale podczas tworzenia standardu C99 dokonano świadomego wyboru, aby promocje w liczbach całkowitych działały w określony sposób. –

+4

"Zauważ, że rejestry 32-bitowe są również nazwane tak, jakby były rozszerzonymi wersjami 16-bitowych rejestrów (eax do ax, ebx do bx, itp.)" Jest to prawda dla x86, ale ** nie jest poprawne ** dla większości inne architektury.Rejestry MIPS mają tę samą nazwę niezależnie od trybu 32- lub 64-bitowego i zawsze działają w rozmiarze natywnym, więc nie można wykonywać arytmetyki w 8 lub 16 bitach, tak czy inaczej, –

15

float, short i char typy są uważane za pomocą standardowego rodzaju „typów pamięci” czyli podzakresów, których można użyć, aby zaoszczędzić trochę miejsca, ale to nie będzie ci kupić dowolną prędkością, ponieważ ich rozmiar jest „nienaturalne” dla procesora.

W przypadku niektórych procesorów nie jest to prawdą, ale dobre kompilatory są wystarczająco inteligentne, aby zauważyć, że jeśli np. dodaj stałą do znaku bez znaku i zapisz wynik w postaci unsigned char, a następnie nie musisz przechodzić przez konwersję unsigned char -> int. Na przykład z g ++ kodu wygenerowanego dla wewnętrznej pętli

void incbuf(unsigned char *buf, int size) { 
    for (int i=0; i<size; i++) { 
     buf[i] = buf[i] + 1; 
    } 
} 

tylko

.L3: 
    addb $1, (%rdi,%rax) 
    addq $1, %rax 
    cmpl %eax, %esi 
    jg .L3 
.L1: 

, gdzie można zobaczyć, że niepodpisany instrukcja dodatek char (addb) jest używany.

To samo dzieje się, jeśli robisz swoje obliczenia pomiędzy krótkimi intami i zapisujesz wynik w krótkich intach.

+3

+1 w przypadku problemu z typem pamięci masowej. – chux