2013-03-01 6 views
12

Jeśli wszystkie wartości nie są niczym więcej niż jednym lub większą liczbą bajtów i żaden bajt nie może zawierać metadanych, w jaki sposób system śledzi, jaki numer reprezentuje bajt? Patrząc na Uzupełnienie Dwójki i pojedynczy punkt na Wikipedii ujawnia, w jaki sposób te liczby mogą być reprezentowane w dwóch podstawach, ale wciąż zastanawiam się, w jaki sposób kompilator lub procesor (nie jestem pewien, z czym naprawdę się tu zajmuję) określa, że ​​ten bajt musi być liczbą całkowitą ze znakiem.Skąd C wie, jakiego typu można się spodziewać?

To jest analogiczne do otrzymywania zaszyfrowanej litery i patrząc na moją półkę szyfrów, zastanawiasz się, który z nich złapać. Pewny wskaźnik jest konieczny.

Jeśli pomyślę o tym, co mogę zrobić, aby rozwiązać ten problem, przychodzą na myśl dwa rozwiązania. Albo zażądałbym dodatkowego bajtu i użyłbym go do zapisania opisu, albo przydzieliłbym sekcje pamięci specjalnie dla reprezentacji numerycznych; sekcja dla podpisanych liczb, sekcja dla pływaków, itp.

Mam do czynienia przede wszystkim z C na systemie Unix, ale może to być bardziej ogólne pytanie.

+5

zobacz: http://en.wikipedia.org/wiki/Symbol_table –

+3

Każda zmienna w C musi mieć typ jako część deklaracji zmiennej (być może definicja, nigdy nie ma pewności, która). Kompilator odczytuje typ i zapamiętuje go. Nie ma tajemnicy co do rodzaju zmiennej. – DwB

+0

Kompilator użyje prawidłowej instrukcji (ponieważ można podpisać niepodpisane i podpisane zgodnie z zasadami określonymi w normie). Istnieją różne instrukcje dotyczące podpisywania i niepodpisanych obliczeń, przynajmniej dla architektur, które widziałem. W przypadku języka C żadne dodatkowe informacje o typie nie są przechowywane w czasie wykonywania. – nhahtdh

Odpowiedz

9

w jaki sposób system monitoruje, jaki numer reprezentuje bajt?

"System" nie. Podczas tłumaczenia, kompilator zna typy obiektów, z którymi ma do czynienia i generuje odpowiednie instrukcje maszynowe do radzenia sobie z tymi wartościami.

+0

+1 krótki i wyraźny, na przykład komentarz http://stackoverflow.com/users/462113/hannesh. – Aubin

+0

Następnie kompilator musi zachować pewne metadane, które są używane podczas pisania kodu zespołu, a następnie wyrzucony. To odpowiada na moje pytanie. Dziękuję Ci. –

+3

@JackStout: Tak, bardzo. Większość kompilatorów utrzymuje tak zwaną tabelę symboli *, która zawiera informacje o typie obiektu, jego widoczności, czasie życia, powiązaniach itp.Podczas fazy tłumaczenia służy do wymuszania reguł semantycznych (takich jak dopasowywanie typów w zadaniach lub uniemożliwianie modyfikowania obiektu "const"). Podczas fazy generowania kodu jest używany do wybierania właściwych instrukcji maszyny dla operacji (np. Ten longword jest używany do obliczeń zmiennoprzecinkowych). –

1

Ooh, dobre pytanie. Zacznijmy od procesora - zakładając układ Intel x86.

Okazuje się, że procesor nie zna nie zna, czy bajt jest "podpisany" czy "niepodpisany". Dlatego po dodaniu dwóch liczb - lub wykonaniu dowolnej operacji - ustawiana jest flaga "status register".

Spójrz na "flagę z flagą". Po dodaniu dwóch liczb CPU robi to właśnie - dodaje liczby i zapisuje wynik w rejestrze. Ale procesor mówi: "jeśli zamiast tego zinterpretowaliśmy te liczby jako liczby całkowite ze znakiem" ", czy wynik jest ujemny?" Jeśli tak, to ta "flaga oznaczająca" jest ustawiona na 1.

Więc jeśli twój program troszczy się o podpisane i niepodpisane, pisząc w asemblerze, sprawdzisz status tej flagi, a reszta twojego programu wykona inny zadanie oparte na tej banderą.

Więc kiedy używasz signed int versus unsigned int w C, zasadniczo mówisz kompilatorowi jak (lub czy) użyć tej flagi znacznika.

+2

Jeśli dobrze pamiętam, zaletą używania uzupełnienia dwóch jest to, że po prostu wykonujesz normalne dodawanie liczb. CPU nie musi dbać o to, czy liczba jest podpisana/unsigned/negative, po prostu dodaje bity. Jest to kod wyższego poziomu, który interpretuje wartość jako dodatnią lub ujemną. –

+0

@SamDufel to jest dokładnie to. flagi zapewniają wygodę w tym przypadku - zamiast zapisywać rutynę, aby sprawdzić bit wyższego rzędu na numerze, można "przeskoczyć" w zależności od tego rejestru. (Cóż, istnieją inne sztuczki, których używa się w tym celu, ale rzeczywiście masz rację, że w tym celu flaga nie jest bezwzględnie konieczna). – poundifdef

1

Należy pamiętać, że C i C++ są językami wysokiego poziomu. Zadaniem kompilatora jest pobranie reprezentacji tekstowej kodu i zbudowanie go zgodnie z instrukcjami specyficznymi dla platformy, które docelowa platforma zamierza wykonać. Dla większości osób używających komputerów PC to zazwyczaj x86 assembly.

To dlatego C i C++ są tak luźne, jak definiują podstawowe typy danych. Na przykład większość ludzi mówi, że w bajcie jest 8 bitów. Nie jest to zdefiniowane przez standard i nie ma nic przeciwko jakiejś maszynie, która ma 7 bitów na bajt, jako natywną interpretację danych. Standard rozpoznaje tylko, że bajt jest najmniejszą adresowalną jednostką danych.

Zatem interpretacja danych zależy od zestawu instrukcji procesora. W wielu współczesnych językach jest jeszcze jedna abstrakcja, Virtual Machine.

Jeśli piszesz własny język skryptowy, musisz zdefiniować sposób interpretacji danych w oprogramowaniu.

+0

C jest językiem wysokiego poziomu? naprawdę? To żart, C jest tylko o 3 mm wyższy niż makro-asembler! ADA to język wysokiego poziomu. – Aubin

+4

Tak, technicznie, C jest językiem wysokiego poziomu. Podobnie jak GLSL jest językiem cieniowania wysokiego poziomu, który buduje się na poziomie zespołu dla GPU. Zgromadzenie jest językiem docelowym, w którym wbudowany jest język C, a język programowania technicznego lub zwykły zbiór danych binarnych jest najniższy. Ludzie nie myślą już o C ani C++ jako o wysokim poziomie, ponieważ zawsze myślimy o językach skryptowych jako takich. Był czas, kiedy ludzie kodowali binarnie. –

+3

@Aubin: C jest tak samo "wysoki poziom" jak Ada; po prostu nie dostarcza tylu * abstrakcji * co Ady. –

1

Kod, który jest wykonywany, nie zawiera informacji o typach. Jedynym narzędziem, które zna typy, jest kompilator w momencie kompilowania kodu.Typy w C są wyłącznie ograniczeniem w czasie kompilacji, aby uniemożliwić ci użycie niewłaściwego typu. Podczas kompilacji kompilator C śledzi typ każdej zmiennej i dlatego wie, który typ należy do której zmiennej.

Jest to powód, dla którego musisz używać ciągów formatów w printf, na przykład. printf nie ma szansy dowiedzieć się, jaki typ otrzyma na liście parametrów, ponieważ te informacje zostały utracone. W językach takich jak go lub java masz środowisko uruchomieniowe z funkcjami refleksyjnymi, które umożliwiają uzyskanie tego typu.

Załóżmy, że Twój skompilowany kod C nadal zawierałby informacje o typie, w związku z czym istnieje potrzeba wygenerowania powstałego języka asemblera w celu sprawdzenia typów. Okazuje się, że jedyną rzeczą zbliżoną do typów w zestawie jest rozmiar operandów dla instrukcji określonej przez suffixes (in GAS). To, co pozostało z informacji o twoim typie, to rozmiar i nic więcej.

Jednym z przykładów dla zespołu, który obsługuje typ, jest kod bajtowy maszyny wirtualnej Java, który ma przyrostki typu dla operands for primitives.

0

Korzystanie C oprócz kompilatora, że ​​doskonale wie o rodzaju podanych wartości nie ma systemu że wie o rodzaju danej wartości.

Należy pamiętać, że C sam z siebie nie wprowadza żadnego systemu informacji o typie środowiska wykonawczego.

Spójrz na poniższy przykład:

int i_var; 
double d_var; 

int main() { 

    i_var = -23; 
    d_var = 0.1; 

    return 0; 
} 

w kodzie są dwa różne typy wartości zaangażowanych jeden być przechowywane jako liczba całkowita i jeden być przechowywane w postaci podwójnej wartości.

Kompilator, który dokładnie analizuje kod, zna dokładne typy obu z nich. Oto zrzut z krótkiego fragmentu gcc informacyjnego typu odbyła natomiast kod pokolenie generowane przez przepuszczenie -fdump-tree-all do gcc:

@1  type_decl  name: @2  type: @3  srcp: <built-in>:0  
         chan: @4  
@2  identifier_node strg: int  lngt: 3  
@3  integer_type  name: @1  size: @5  algn: 32  
         prec: 32  sign: signed min : @6  
         max : @7  
... 
@5  integer_cst  type: @11  low : 32  
@6  integer_cst  type: @3  high: -1  low : -2147483648 
@7  integer_cst  type: @3  low : 2147483647 
... 

@3805 var_decl   name: @3810 type: @3  srcp: main.c:3  
         chan: @3811 size: @5  algn: 32  
         used: 1  
... 
@3810 identifier_node strg: i_var lngt: 5  

Polowanie dół @links należy wyraźnie zobaczyć, że tam naprawdę jest dużo informacji przechowywanych o rozmiar pamięci, ograniczenia wyrównania i dozwolone wartości min i max dla typu "int" zapisane w węzłach @ 1-3 i @ 5-7. (I pominięte węzeł @ 4 jak wspomniany „chan” wejście jest tylko używana do cha ja n się żadnych definicji typu w wygenerowanym drzewa)

Reagarding zmienna zadeklarowana w linii main.c 3 to jest znany, że trzyma wartość typu int widzianą przez odniesienie do typu do węzła @ 3.

Na pewno będziesz w stanie sam polować na podwójne wpisy i te dla d_var w swoim własnym eksperymencie, jeśli nie ufasz mi, że one również tam będą.

Przyjrzeniu generowanego asemblerze (stosując GCC przechodzą przełącznik -S) wymienionego można spojrzeć na drodze kompilator używane te informacje w generacji kodu:

.file "main.c" 
    .comm i_var,4,4 
    .comm d_var,8,8 
    .text 
.globl main 
    .type main, @function 
main: 
    pushl %ebp 
    movl %esp, %ebp 
    movl $-23, i_var 
    fldl .LC0 
    fstpl d_var 
    movl $0, %eax 
    popl %ebp 
    ret 
    .size main, .-main 
    .section .rodata 
    .align 8 
.LC0: 
    .long -1717986918 
    .long 1069128089 
    .ident "GCC: (Debian 4.4.5-8) 4.4.5" 
    .section .note.GNU-stack,"",@progbits 

przyjrzeniu instrukcje przypisania zobaczysz, że kompilator wymyślił odpowiednie instrukcje "mov", aby przypisać naszą wartość int i "fstp", aby przypisać naszą "podwójną" wartość.

Niemniej jednak oprócz instrukcji wybranych na poziomie maszyny nie ma informacji o typie tych wartości. Patrząc na wartość przechowywaną w .LC0, typ "double" o wartości 0,1 był nawet dzielony w dwóch kolejnych lokalizacjach magazynowych, każdy przez długi czas, aby spełnić znane "typy" asemblera.

W rzeczywistości przełamanie wartości w ten sposób było tylko jednym z innych możliwości, używając 8 kolejnych wartości "typu" .byte zrobiłoby równie dobrze.

Powiązane problemy