I dichiaratori di tipi di dati come "int" e "char" sono memorizzati nella RAM quando viene eseguito un programma C?


74

Quando è in esecuzione un programma C, i dati vengono archiviati nell'heap o nello stack. I valori sono memorizzati in indirizzi RAM. Ma per quanto riguarda gli indicatori di tipo (ad es. intO char)? Sono anche memorizzati?

Considera il seguente codice:

char a = 'A';
int x = 4;

Ho letto che A e 4 sono memorizzati negli indirizzi RAM qui. Ma che dire di ae x? Più confusamente, come fa l'esecuzione a sapere che aè un carattere ed xè un int? Voglio dire, è il inte charmenzionato da qualche parte nella RAM?

Supponiamo che un valore sia memorizzato da qualche parte nella RAM come 10011001; se sono il programma che esegue il codice, come faccio a sapere se questo 10011001 è un charo un int?

Quello che non capisco è come lo sa il computer, quando legge il valore di una variabile da un indirizzo come 10001, sia che si tratti di un into char. Immagina di fare clic su un programma chiamato anyprog.exe. Immediatamente inizia l'esecuzione del codice. Questo file eseguibile include informazioni sul fatto che le variabili memorizzate siano del tipo into char?


24
Questa informazione è totalmente persa in fase di esecuzione. Tu (e il tuo compilatore) dovete assicurarvi in ​​anticipo che la memoria sia interpretata correttamente. È questa la risposta che stavi cercando?
5gon12eder,

4
Non Poiché presuppone che tu sappia cosa stai facendo, prende tutto ciò che trova all'indirizzo di memoria che hai fornito e lo scrive su stdout. Se tutto ciò che è stato scritto corrisponde a un personaggio leggibile, alla fine verrà visualizzato sulla console di qualcuno come personaggio leggibile. Se non corrisponde così, apparirà come incomprensibile, o possibilmente un personaggio leggibile a caso.
Robert Harvey,

22
@ user16307 La risposta breve è che nei linguaggi digitati staticamente, ogni volta che si stampa un carattere, il compilatore produrrà un codice diverso da quello che avrebbe per la stampa di un int. In fase di runtime non c'è più alcuna conoscenza che xsia un carattere, ma è il codice di stampa del carattere che viene eseguito, perché è quello che il compilatore ha selezionato.
Ixrec,

13
@ user16307 Viene sempre memorizzato come rappresentazione binaria del numero 65. Se viene stampato come 65 o come A dipende dal codice prodotto dal compilatore per stamparlo. Non ci sono metadati accanto al 65 che dicono che in realtà è un carattere o un int (almeno, non in linguaggi tipicamente statici come C).
Ixrec,

2
Comprendendo appieno i concetti che chiedi qui e li implementi da solo, potresti voler seguire un corso di compilazione, ad esempio quello di
Coursera

Risposte:


122

Per rispondere alla domanda che hai pubblicato in diversi commenti (che penso che dovresti modificare nel tuo post):

Quello che non capisco è come fa il computer a sapere quando legge il valore di una variabile e l'indirizzo come 10001 se è un int o char. Immagina di fare clic su un programma chiamato anyprog.exe. Immediatamente inizia l'esecuzione del codice. Questo file exe include informazioni su se le variabili sono memorizzate come in o char?

Quindi mettiamoci un po 'di codice. Diciamo che scrivi:

int x = 4;

E supponiamo che venga memorizzato nella RAM:

0x00010004: 0x00000004

La prima parte è l'indirizzo, la seconda parte è il valore. Quando il tuo programma (che viene eseguito come codice macchina) viene eseguito, tutto ciò che vede 0x00010004è il valore 0x000000004. Non "conosce" il tipo di questi dati e non sa come si debba "utilizzare".

Quindi, come fa il tuo programma a capire la cosa giusta da fare? Considera questo codice:

int x = 4;
x = x + 5;

Abbiamo una lettura e una scrittura qui. Quando il tuo programma legge xdalla memoria, trova 0x00000004lì. E il tuo programma sa aggiungere 0x00000005ad esso. E il motivo per cui il tuo programma "sa" che si tratta di un'operazione valida, è perché il compilatore garantisce che l'operazione sia valida attraverso la sicurezza del tipo. Il compilatore ha già verificato che è possibile aggiungere 4e 5insieme. Quindi quando viene eseguito il codice binario (exe), non è necessario effettuare tale verifica. Esegue semplicemente ogni passaggio alla cieca, supponendo che tutto sia OK (le cose brutte accadono quando in realtà non sono OK).

Un altro modo di pensarci è così. Ti do queste informazioni:

0x00000004: 0x12345678

Stesso formato di prima: indirizzo a sinistra, valore a destra. Di che tipo è il valore? A questo punto, conosci quante più informazioni su quel valore come il tuo computer quando esegue il codice. Se ti dicessi di aggiungere 12743 a quel valore, potresti farlo. Non hai idea di quello che le ripercussioni di tale operazione saranno in tutto il sistema, ma l'aggiunta di due numeri è qualcosa che sei veramente bravo, così si potrebbe farlo. Questo rende un valore un int? Non necessariamente: tutto ciò che vedi sono due valori a 32 bit e l'operatore addizione.

Forse un po 'di confusione sta quindi recuperando i dati. Se abbiamo:

char A = 'a';

Come fa il computer a essere visualizzato anella console? Bene, ci sono molti passaggi per farlo. Il primo è andare nella Aposizione in memoria e leggerlo:

0x00000004: 0x00000061

Il valore esadecimale per ain ASCII è 0x61, quindi quanto sopra potrebbe essere qualcosa che vedresti in memoria. Quindi ora il nostro codice macchina conosce il valore intero. Come fa a sapere di trasformare il valore intero in un carattere per visualizzarlo? In poche parole, il compilatore si è assicurato di inserire tutti i passaggi necessari per effettuare tale transizione. Ma il tuo stesso computer (o il programma / exe) non ha idea di quale sia il tipo di tali dati. Quel valore a 32 bit potrebbe essere qualsiasi cosa - int, charmetà di un doublepuntatore, parte di un array, parte di un string, parte di un'istruzione, ecc.


Ecco una breve interazione che il tuo programma (exe) potrebbe avere con il computer / sistema operativo.

Programma: voglio iniziare. Ho bisogno di 20 MB di memoria.

Sistema operativo: trova 20 MB di memoria libera che non sono in uso e li consegna

(La nota importante è che questa potrebbe tornare ogni 20 MB liberi della memoria, che non hanno nemmeno bisogno di essere contigui. A questo punto, il programma può ora operare all'interno della memoria che ha senza parlare con il sistema operativo)

Programma: suppongo che il primo punto in memoria sia una variabile intera a 32 bit x.

(Il compilatore si assicura che gli accessi ad altre variabili non tocchino mai questo punto in memoria. Non c'è nulla nel sistema che dice che il primo byte è variabile xo che la variabile xè un numero intero. Un'analogia: hai una borsa. Dici alla gente che metterai solo palline di colore giallo in questa borsa. Quando in seguito qualcuno estrae qualcosa dalla borsa, sarebbe scioccante che tirasse fuori qualcosa di blu o un cubo - qualcosa è andato terribilmente storto. Lo stesso vale per i computer: il tuo il programma ora sta assumendo che il primo punto di memoria sia la variabile x e che sia un numero intero. Se qualcos'altro viene mai scritto su questo byte di memoria o si presume che sia qualcos'altro - è successo qualcosa di orribile. Il compilatore assicura che questo tipo di cose don non succede)

Programma: ora scriverò 2sui primi quattro byte in cui suppongo xsia.

Programma: voglio aggiungere 5 a x.

  • Legge il valore di X in un registro temporaneo

  • Aggiunge 5 al registro temporaneo

  • Memorizza il valore del registro temporaneo nel primo byte, che si presume sia ancora x.

Programma: suppongo che il prossimo byte disponibile sia la variabile char y.

Programma: scriverò asu variabile y.

  • Una libreria viene utilizzata per trovare il valore byte per a

  • Il byte viene scritto all'indirizzo che il programma presume sia y.

Programma: Voglio visualizzare il contenuto di y

  • Legge il valore nel secondo punto di memoria

  • Utilizza una libreria per convertire da byte a un carattere

  • Utilizza librerie grafiche per modificare la schermata della console (impostazione dei pixel da nero a bianco, scorrimento di una riga, ecc.)

(E continua da qui)

Ciò su cui probabilmente ti stai impiccando è: cosa succede quando il primo punto in memoria non è più x? o il secondo non è più y? Cosa succede quando qualcuno legge xcome charo ycome puntatore? In breve, succedono cose brutte. Alcune di queste cose hanno un comportamento ben definito e alcune hanno un comportamento indefinito. Il comportamento indefinito è esattamente questo: tutto può succedere, dal nulla, al crash del programma o del sistema operativo. Anche un comportamento ben definito può essere dannoso. Se posso passare xa un puntatore al mio programma e far sì che il tuo programma lo utilizzi come puntatore, allora posso fare in modo che il tuo programma inizi a eseguire il mio programma, che è esattamente ciò che fanno gli hacker. Il compilatore è lì per aiutare a assicurarsi che non usiamo int xcomestringe cose di quella natura. Il codice macchina stesso non è a conoscenza dei tipi e farà solo ciò che le istruzioni gli dicono di fare. Esiste anche una grande quantità di informazioni scoperte in fase di esecuzione: quali byte di memoria è autorizzato a utilizzare il programma? Non xha inizio dal primo byte o il 12?

Ma puoi immaginare quanto sarebbe orribile scrivere programmi come questo (e puoi, nel linguaggio assembly). Cominci 'dichiarando' le tue variabili - ti dico che il byte 1 è x, il byte 2 è ye mentre scrivi ogni riga di codice, caricando e memorizzando i registri, tu (come essere umano) devi ricordare quale è xe quale uno è y, perché il sistema non ha idea. E tu (come essere umano) devi ricordare di che tipo xe ysei, perché di nuovo - il sistema non ha idea.


Spiegazione incredibile. Solo la parte che hai scritto "Come fa a trasformare il valore intero in un carattere per visualizzarlo? In poche parole, il compilatore si è assicurato di inserire tutti i passaggi necessari per effettuare quella transizione." è ancora nebbioso per me. Supponiamo che la CPU abbia recuperato 0x00000061 dal registro RAM. Da questo punto stai dicendo che ci sono altre istruzioni (nel file exe) che rendono tale transizione a ciò che vediamo sullo schermo?
user16307

2
@ user16307 sì, ci sono ulteriori istruzioni. Ogni riga di codice che scrivi può potenzialmente essere trasformata in molte istruzioni. Ci sono istruzioni per capire quale personaggio usare, ci sono istruzioni per quali pixel modificare e in che colore cambiano, ecc. C'è anche un codice che non vedi davvero. Ad esempio, usando std :: cout significa che stai usando una libreria. Il tuo codice da scrivere sulla console può essere solo una linea, ma le funzioni che chiami saranno più linee e ogni linea può trasformarsi in molte istruzioni della macchina.
Shaz,

8
@ user16307 Otherwise how can console or text file outputs a character instead of int Perché esiste una sequenza diversa di istruzioni per l'output del contenuto di una posizione di memoria come numero intero o come caratteri alfanumerici. Il compilatore conosce i tipi di variabili e sceglie la sequenza appropriata di istruzioni in fase di compilazione e la registra in EXE.
Charles E. Grant,

2
Vorrei trovare una frase diversa per "Il codice byte stesso", in quanto il codice byte (o bytecode) di solito si riferisce a un linguaggio intermedio (come Java Bytecode o MSIL), che potrebbe effettivamente archiviare questi dati per il runtime da sfruttare. Inoltre non è del tutto chiaro a quale "codice byte" dovrebbe fare riferimento in quel contesto. Altrimenti, bella risposta.
jpmc26,

6
@ user16307 Cerca di non preoccuparti di C ++ e C #. Quello che stanno dicendo queste persone è molto al di sopra della tua attuale comprensione di come funzionano i computer e i compilatori. Ai fini di ciò che stai cercando di capire, l'hardware NON sa nulla di tipi, char o int o altro. Quando hai detto al compilatore che una variabile era un int, generava un codice eseguibile per gestire una posizione di memoria COME SE fosse un int. La posizione di memoria stessa non contiene informazioni sui tipi; è solo che il tuo programma ha deciso di trattarlo come un int. Dimentica tutto ciò che hai sentito sulle informazioni sul tipo di runtime.
Andres F.

43

Penso che la tua domanda principale sembra essere: "Se il tipo viene cancellato in fase di compilazione e non conservato in fase di esecuzione, come fa il computer a sapere se eseguire il codice che lo interpreta come into eseguire il codice che lo interpreta come char? "

E la risposta è ... il computer no. Tuttavia, il compilatore lo sa e avrà semplicemente messo il codice corretto nel binario in primo luogo. Se la variabile fosse digitata come char, allora il compilatore non inserirà il codice per trattarlo come un intnel programma, metterebbe il codice per trattarlo come a char.

Ci sono ragioni per mantenere il tipo in fase di esecuzione:

  • Digitazione dinamica: nella digitazione dinamica, il controllo del tipo avviene in fase di esecuzione, quindi, ovviamente, il tipo deve essere noto in fase di esecuzione. Ma C non viene digitato in modo dinamico, quindi i tipi possono essere cancellati in modo sicuro. (Si noti che questo è uno scenario molto diverso. I tipi dinamici e i tipi statici non sono davvero la stessa cosa, e in un linguaggio di tipizzazione mista, è ancora possibile cancellare i tipi statici e mantenere solo i tipi dinamici.)
  • Polimorfismo dinamico: se si esegue un codice diverso in base al tipo di runtime, è necessario mantenere il tipo di runtime in circolazione. C non ha un polimorfismo dinamico (in realtà non ha alcun polimorfismo, tranne che in alcuni casi speciali codificati, ad esempio l' +operatore), quindi non ha bisogno del tipo di runtime per quel motivo. Tuttavia, ancora una volta, il tipo di runtime è comunque diverso dal tipo statico, ad esempio in Java, si potrebbe teoricamente cancellare i tipi statici e mantenere comunque il tipo di runtime per il polimorfismo. Si noti inoltre che se si decentra e si specializza il codice di ricerca del tipo e lo si inserisce all'interno dell'oggetto (o della classe), non è necessario necessariamente il tipo di runtime, ad esempio v ++ di C ++.
  • Runtime Reflection: se si consente al programma di riflettere sui suoi tipi in fase di runtime, è ovviamente necessario mantenere i tipi in fase di runtime. Puoi facilmente vederlo con Java, che mantiene i tipi di primo ordine in fase di esecuzione, ma cancella gli argomenti di tipo su tipi generici al momento della compilazione, quindi puoi solo riflettere sul costruttore del tipo ("tipo grezzo") ma non sull'argomento tipo. Ancora una volta, C non ha una riflessione di runtime, quindi non è necessario mantenere il tipo in fase di runtime.

L'unico motivo per mantenere il tipo in fase di esecuzione in C sarebbe per il debug, tuttavia, il debug di solito viene eseguito con l'origine disponibile e quindi è possibile cercare semplicemente il tipo nel file di origine.

La cancellazione del tipo è abbastanza normale. Non influisce sulla sicurezza dei tipi: i tipi vengono controllati al momento della compilazione, una volta che il compilatore ha verificato che il programma è sicuro, i tipi non sono più necessari (per tale motivo). Non influisce sul polimorfismo statico (noto anche come sovraccarico): una volta completata la risoluzione del sovraccarico e il compilatore ha scelto il sovraccarico giusto, non ha più bisogno dei tipi. I tipi possono anche guidare l'ottimizzazione, ma ancora una volta, quando l'ottimizzatore ha scelto le sue ottimizzazioni in base ai tipi, non ne ha più bisogno.

Conservare i tipi in fase di esecuzione è necessario solo quando si desidera eseguire un'operazione con i tipi in fase di esecuzione.

Haskell è uno dei linguaggi tipicamente statici più severi, rigorosi e sicuri per i tipi, e i compilatori Haskell di solito cancellano tutti i tipi. (Credo che l'eccezione sia il passaggio dei dizionari dei metodi per le classi di tipi).


3
No! Perché? Per che cosa sarebbero necessarie tali informazioni? Il compilatore genera il codice per leggere a charnel file binario compilato. Esso non emette il codice di un int, esso non emette il codice per una byte, non uscita il codice di un puntatore, esso emette semplicemente solo il codice per un char. Non ci sono decisioni di runtime in base al tipo. Non è necessario il tipo. È completamente e assolutamente irrilevante. Tutte le decisioni pertinenti sono già state prese al momento della compilazione.
Jörg W Mittag,

2
Non c'è. Il compilatore inserisce semplicemente il codice per stampare un carattere nel file binario. Periodo. Il compilatore sa che a quell'indirizzo di memoria c'è char, quindi mette il codice per stampare un char nel binario. Se il valore a quell'indirizzo di memoria per qualche strana ragione sembra non essere un carattere, allora, l'inferno si scatena. In pratica funziona così un'intera classe di exploit di sicurezza.
Jörg W Mittag,

2
Pensaci: se la CPU in qualche modo fosse a conoscenza dei tipi di dati dei programmi, allora tutti sul pianeta dovrebbero acquistare una nuova CPU ogni volta che qualcuno inventa un nuovo tipo. public class JoergsAwesomeNewType {};Vedere? Ho appena inventato un nuovo tipo! Devi acquistare una nuova CPU!
Jörg W Mittag,

9
No. Non lo fa. Il compilatore sa quale codice deve inserire nel binario. Non ha senso conservare queste informazioni. Se stai stampando un int, il compilatore inserirà il codice per stampare un int. Se stai stampando un carattere, il compilatore inserirà il codice per stampare un carattere. Periodo. Ma è solo un po 'di modello. Il codice per stampare un carattere interpreterà il modello di bit in un certo modo, il codice per stampare un int interpreterà il bit in un modo diverso, ma non c'è modo di distinguere un modello di bit che è un int da un modello di bit che è un carattere, è una stringa di bit.
Jörg W Mittag,

2
@ user16307: "Il file exe non include informazioni su quale indirizzo è di che tipo di dati?" Può essere. Se si compila con dati di debug, i dati di debug includeranno informazioni su nomi, indirizzi e tipi di variabili. E a volte i dati di debug vengono archiviati nel file .exe (come flusso binario). Ma non fa parte del codice eseguibile e non viene utilizzato dall'applicazione stessa, ma solo da un debugger.
Ben Voigt,

12

Il computer non "conosce" quali indirizzi sono cosa, ma la conoscenza di ciò che è inserito nelle istruzioni del programma.

Quando si scrive un programma C che scrive e legge una variabile char, il compilatore crea il codice assembly che scrive quel pezzo di dati da qualche parte come char, e c'è qualche altro codice da qualche altra parte che legge un indirizzo di memoria e lo interpreta come char. L'unica cosa che unisce queste due operazioni è la posizione di quell'indirizzo di memoria.

Quando arriva il momento di leggere, le istruzioni non dicono "vedi che tipo di dati c'è", dice semplicemente qualcosa come "carica quella memoria come float". Se l'indirizzo da leggere è stato modificato o qualcosa ha sovrascritto quella memoria con qualcosa di diverso da un float, la CPU caricherà comunque felicemente quella memoria come float e, di conseguenza, possono accadere tutti i tipi di cose strane.

Cattivo tempo di analogia: immagina un complicato magazzino di spedizione, dove il magazzino è memoria e la gente che raccoglie le cose è la CPU. Una parte del "programma" di magazzino colloca vari oggetti sullo scaffale. Un altro programma va e prende gli articoli dal magazzino e li mette in scatole. Quando vengono estratti, non vengono controllati, ma vanno semplicemente nel cestino. L'intero magazzino funziona da tutto ciò che funziona in sincronia, con gli articoli giusti sempre al posto giusto al momento giusto, altrimenti tutto si blocca, proprio come in un programma reale.


come spiegheresti se la CPU trova 0x00000061 in un registro e lo recupera; e immagina che il programma della console dovrebbe produrre questo come un carattere non int. vuoi dire che in quel file exe ci sono alcuni codici di istruzione che sanno che l'indirizzo di 0x00000061 è un carattere e converte in un carattere usando la tabella ASCII?
user16307

7
Si noti che "tutto si blocca" è in realtà lo scenario migliore. "Le cose strane accadono" è il secondo miglior scenario, "le cose sottilmente-strane accadono" è ancora peggio, e il caso peggiore è "le cose accadono dietro la schiena che qualcuno ha intenzionalmente manipolato per accadere nel modo in cui vuole", aka un exploit di sicurezza.
Jörg W Mittag,

@ user16307: il codice nel programma dirà al computer di recuperare quell'indirizzo per visualizzarlo in base alla codifica utilizzata. Se i dati nella posizione di memoria sono un carattere ASCII o spazzatura completa, il computer non è preoccupato. Qualcos'altro era responsabile della configurazione di quell'indirizzo di memoria per contenere i valori previsti. Penso che potrebbe esserti utile provare qualche programmazione di assemblaggio.
whatsisname

1
@ JörgWMittag: davvero. Ho pensato di citare un buffer overflow come esempio, ma ho deciso che avrebbe reso le cose più confuse.
whatsisname

@ user16307: la cosa che visualizza i dati sullo schermo è un programma. Su unixen tradizionale è un terminale (un software che emula il terminale seriale DEC VT100 - un dispositivo hardware con un monitor e una tastiera che visualizza tutto ciò che entra nel suo modem al monitor e invia qualsiasi cosa digitato sulla sua tastiera al suo modem). Su DOS è DOS (in realtà la modalità di testo della tua scheda VGA ma lascia che lo ignori) e su Windows è command.com. Il tuo programma non sa che sta effettivamente stampando stringhe, sta solo stampando una sequenza di byte (numeri).
Slebetman,

8

Non Una volta compilato C in codice macchina, la macchina vede solo un mucchio di bit. Il modo in cui questi bit vengono interpretati dipende da quali operazioni vengono eseguite su di essi rispetto ad alcuni metadati aggiuntivi.

I tipi inseriti nel codice sorgente sono solo per il compilatore. Prende il tipo che dici che i dati dovrebbero essere e, al meglio delle sue capacità, cerca di assicurarsi che i dati vengano utilizzati solo in modo logico. Una volta che il compilatore ha svolto il miglior lavoro possibile nel controllare la logica del codice sorgente, lo converte in codice macchina e scarta i dati del tipo, poiché il codice macchina non ha modo di rappresentarlo (almeno sulla maggior parte dei computer) .


Quello che non capisco è come fa il computer a sapere quando legge il valore di una variabile e l'indirizzo come 10001 se è un int o char. Immagina di fare clic su un programma chiamato anyprog.exe. Immediatamente inizia l'esecuzione del codice. Questo file exe include informazioni su se le variabili sono memorizzate come in o char? -
user16307

@ user16307 No, non ci sono informazioni aggiuntive su se qualcosa è un int o un carattere. Aggiungerò alcune cose di esempio in seguito, supponendo che nessun altro mi picchi.
8bittree,

1
@ user16307: il file exe contiene tali informazioni indirettamente. Il processore che esegue il programma non si preoccupa dei tipi utilizzati durante la scrittura del programma, ma gran parte di esso può essere dedotto dalle istruzioni utilizzate per accedere alle varie posizioni di memoria.
Bart van Ingen Schenau,

@ user16307 ci sono in realtà alcune informazioni extra. I file exe sanno che un numero intero è 4 byte, quindi quando scrivi "int a" il compilatore riserva 4 byte per la variabile a e può quindi calcolare l'indirizzo di ae delle altre variabili dopo.
Esben Skov Pedersen,

1
@ user16307 non esiste alcuna differenza pratica (oltre alla dimensione del tipo) tra int a = 65e char b = 'A'una volta compilato il codice.

6

La maggior parte dei processori fornisce istruzioni diverse per lavorare con dati di tipi diversi, quindi le informazioni sui tipi vengono solitamente "inserite" nel codice macchina generato. Non è necessario archiviare metadati di tipo aggiuntivo.

Alcuni esempi concreti potrebbero essere d'aiuto. Il codice macchina seguente è stato generato utilizzando gcc 4.1.2 su un sistema x86_64 con SuSE Linux Enterprise Server (SLES) 10.

Supponi il seguente codice sorgente:

int main( void )
{
  int x, y, z;

  x = 1;
  y = 2;

  z = x + y;

  return 0;
}

Ecco la carne del codice assembly generato corrispondente alla fonte sopra (usando gcc -S), con i commenti aggiunti da me:

main:
.LFB2:
        pushq   %rbp               ;; save the current frame pointer value
.LCFI0:
        movq    %rsp, %rbp         ;; make the current stack pointer value the new frame pointer value
.LCFI1:                            
        movl    $1, -12(%rbp)      ;; x = 1
        movl    $2, -8(%rbp)       ;; y = 2
        movl    -8(%rbp), %eax     ;; copy the value of y to the eax register
        addl    -12(%rbp), %eax    ;; add the value of x to the eax register
        movl    %eax, -4(%rbp)     ;; copy the value in eax to z
        movl    $0, %eax           ;; eax gets the return value of the function
        leave                      ;; exit and restore the stack
        ret

Segue qualcosa in più che segue ret, ma non è rilevante per la discussione.

%eaxè un registro dati per scopi generici a 32 bit. %rspè un registro a 64 bit riservato al salvataggio del puntatore dello stack , che contiene l'indirizzo dell'ultima cosa inserita nello stack. %rbpè un registro a 64 bit riservato al salvataggio del puntatore del frame , che contiene l'indirizzo del frame dello stack corrente . Un frame dello stack viene creato nello stack quando si immette una funzione e riserva spazio per gli argomenti della funzione e le variabili locali. Argomenti e variabili sono accessibili usando gli offset dal puntatore del frame. In questo caso, la memoria per la variabile xè di 12 byte "sotto" l'indirizzo in cui si trova %rbp.

Nel codice sopra, copiamo il valore intero di x(1, memorizzato in -12(%rbp)) nel registro %eaxusando l' movlistruzione, che viene utilizzata per copiare parole a 32 bit da una posizione a un'altra. Chiamiamo quindi addl, che aggiunge il valore intero di y(memorizzato in -8(%rbp)) al valore già in %eax. Quindi salviamo il risultato in -4(%rbp), che è z.

Ora cambiamolo in modo da avere a che fare con doublevalori anziché con intvalori:

int main( void )
{
  double x, y, z;

  x = 1;
  y = 2;

  z = x + y;

  return 0;
}

Correre di gcc -Snuovo ci dà:

main:
.LFB2:
        pushq   %rbp                              
.LCFI0:
        movq    %rsp, %rbp
.LCFI1:
        movabsq $4607182418800017408, %rax ;; copy literal 64-bit floating-point representation of 1.00 to rax
        movq    %rax, -24(%rbp)            ;; save rax to x
        movabsq $4611686018427387904, %rax ;; copy literal 64-bit floating-point representation of 2.00 to rax
        movq    %rax, -16(%rbp)            ;; save rax to y
        movsd   -24(%rbp), %xmm0           ;; copy value of x to xmm0 register
        addsd   -16(%rbp), %xmm0           ;; add value of y to xmm0 register
        movsd   %xmm0, -8(%rbp)            ;; save result to z
        movl    $0, %eax                   ;; eax gets return value of function
        leave                              ;; exit and restore the stack
        ret

Diverse differenze Invece di movle addl, usiamo movsde addsd(assegnare e aggiungere float a doppia precisione). Invece di archiviare valori provvisori %eax, usiamo %xmm0.

Questo è ciò che intendo quando dico che il tipo è "inserito" nel codice macchina. Il compilatore genera semplicemente il codice macchina giusto per gestire quel particolare tipo.


4

Storicamente , C considerava la memoria come costituita da un numero di gruppi di slot numerati di tipounsigned char(chiamato anche "byte", anche se non deve sempre essere di 8 bit). Qualsiasi codice che utilizzava qualsiasi cosa archiviata in memoria avrebbe bisogno di sapere in quale slot o slot erano archiviate le informazioni e sapere cosa si dovrebbe fare con le informazioni lì [es. "Interpretare i quattro byte a partire dall'indirizzo 123: 456 come un 32-bit valore in virgola mobile "o" memorizza i 16 bit inferiori della quantità calcolata più di recente in due byte a partire dall'indirizzo 345: 678]. La memoria stessa non saprebbe né si preoccuperebbe di cosa significassero i valori memorizzati negli slot di memoria. Se il codice ha tentato di scrivere la memoria usando un tipo e di leggerlo come un altro, i pattern di bit memorizzati dalla scrittura sarebbero stati interpretati secondo le regole del secondo tipo, con qualunque conseguenza potesse derivarne.

Ad esempio, se il codice dovesse essere archiviato 0x12345678in un 32 bit unsigned inte quindi tentare di leggere due unsigned intvalori consecutivi a 16 bit dal suo indirizzo e quello sopra, quindi a seconda di quale metà del file unsigned intera memorizzata dove, il codice potrebbe leggere i valori 0x1234 e 0x5678 o 0x5678 e 0x1234.

Lo standard C99, tuttavia, non richiede più che la memoria si comporti come un gruppo di slot numerati che non sanno nulla di ciò che rappresentano i loro pattern di bit . Un compilatore è autorizzato a comportarsi come se gli slot di memoria fossero a conoscenza dei tipi di dati che sono memorizzati in essi e consentirà solo i dati scritti utilizzando qualsiasi tipo diverso da unsigned charessere letti utilizzando entrambi i tipi unsigned charo lo stesso tipo di quelli scritti con; i compilatori sono inoltre autorizzati a comportarsi come se gli slot di memoria avessero il potere e la propensione a corrompere arbitrariamente il comportamento di qualsiasi programma che tentasse di accedere alla memoria in modo contrario a tali regole.

Dato:

unsigned int a = 0x12345678;
unsigned short p = (unsigned short *)&a;
printf("0x%04X",*p);

alcune implementazioni potrebbero stampare 0x1234 e altre potrebbero stampare 0x5678, ma secondo lo standard C99 sarebbe legale per un'implementazione stampare "FRINK RULES!" o fare qualsiasi altra cosa, in base alla teoria che sarebbe legale che le posizioni di memoria trattenessero al'hardware che registra quale tipo è stato usato per scriverle, e che tale hardware risponda a un tentativo di lettura non valido in alcun modo, anche causando "FRINK RULES!" essere prodotto.

Si noti che non importa se esiste effettivamente tale hardware - il fatto che tale hardware possa esistere legalmente rende legale per i compilatori generare codice che si comporta come se fosse in esecuzione su un tale sistema. Se il compilatore è in grado di determinare che una determinata posizione di memoria verrà scritta come un tipo e letta come un altro, può far finta di essere in esecuzione su un sistema il cui hardware potrebbe prendere tale determinazione e rispondere con qualsiasi grado di capricciosità che l'autore del compilatore ritenga opportuno .

Lo scopo di questa regola era di consentire ai compilatori che sapevano che un gruppo di byte con un valore di un certo tipo possedeva un determinato valore in un determinato momento e che nessun valore di quello stesso tipo era stato scritto da allora, per dedurre quel gruppo di byte manterrebbe comunque quel valore. Ad esempio, un processore aveva letto un gruppo di byte in un registro, e in seguito voleva utilizzare nuovamente le stesse informazioni mentre era ancora nel registro, il compilatore poteva usare il contenuto del registro senza dover rileggere il valore dalla memoria. Un'ottimizzazione utile. Per circa i primi dieci anni della regola, la sua violazione significherebbe in genere che se una variabile viene scritta con un tipo diverso da quello utilizzato per leggerlo, la scrittura può o meno influire sul valore letto. Tale comportamento può in alcuni casi essere disastroso, ma in altri casi può essere innocuo,

Intorno al 2009, tuttavia, gli autori di alcuni compilatori come CLANG hanno stabilito che poiché lo Standard consente ai compilatori di fare tutto ciò che vogliono nei casi in cui la memoria è scritta usando un tipo e letto come un altro, i compilatori dovrebbero dedurre che i programmi non riceveranno mai input che potrebbero causare una cosa del genere. Poiché lo Standard afferma che al compilatore è consentito fare qualsiasi cosa gli piaccia quando viene ricevuto tale input non valido, il codice che avrebbe un effetto solo nei casi in cui lo Standard impone che nessun requisito (e secondo alcuni autori del compilatore dovrebbe essere omesso) dovrebbe essere omesso come irrilevante. Ciò modifica il comportamento delle alias delle violazioni dall'essere come la memoria che, data una richiesta di lettura, può restituire arbitrariamente l'ultimo valore scritto usando lo stesso tipo di una richiesta di lettura o qualsiasi valore più recente scritto usando un altro tipo,


1
Menzionare un comportamento indefinito quando si digita la potatura a qualcuno che non capisce come non c'è RTTI sembra controintuitivo
Cole Johnson

@ColeJohnson: Peccato che non ci siano nomi o standard formali per il dialetto di C supportato dal 99% dei compilatori pre-2009, dal momento che sia dal punto di vista dell'insegnamento che di quello pratico dovrebbero essere considerati lingue fondamentalmente diverse. Poiché lo stesso nome è dato sia al dialetto che ha evoluto un numero di comportamenti prevedibili e ottimizzabili nel corso dei 35 anni, dialetto che elimina tali comportamenti per il presunto scopo di ottimizzazione, è difficile evitare confusione quando si parla di cose che funzionano diversamente in essi .
supercat

Storicamente C correva su macchine Lisp che non permettevano di giocare così liberamente con i tipi. Sono abbastanza sicuro che molti dei "comportamenti prevedibili e ottimizzabili" visti 30 anni fa semplicemente non funzionassero altro che BSD Unix sul VAX.
prosfilaes,

@prosfilaes: Forse "il 99% dei compilatori utilizzati dal 1999 al 2009" sarebbe più preciso? Anche quando i compilatori avevano opzioni per alcune ottimizzazioni di interi piuttosto aggressivi, erano proprio queste: opzioni. Non so di aver mai visto un compilatore prima del 1999 che non avesse una modalità che non garantisse che, data int x,y,z;l'espressione x*y > z, non avrebbe mai fatto altro che restituire 1 o 0, o dove le violazioni dell'aliasing avrebbero avuto alcun effetto diverso da consentire al compilatore di restituire arbitrariamente un valore vecchio o nuovo.
supercat

1
... dove i unsigned charvalori utilizzati per costruire un tipo "provengono da". Se un programma dovesse scomporre un puntatore in un unsigned char[], mostrare brevemente il suo contenuto esadecimale sullo schermo, quindi cancellare il puntatore, il unsigned char[], e successivamente accettare alcuni numeri esadecimali dalla tastiera, copiarli di nuovo in un puntatore e quindi dedurre tale puntatore , il comportamento sarebbe ben definito nel caso in cui il numero digitato corrispondesse al numero visualizzato.
supercat

3

In C non lo è. Altre lingue (es. Lisp, Python) hanno tipi dinamici ma C è tipizzato staticamente. Ciò significa che il tuo programma deve sapere che tipo di dati devono interpretare correttamente è come un carattere, un numero intero, ecc.

Di solito il compilatore si occupa di questo per te, e se fai qualcosa di sbagliato, otterrai un errore di compilazione (o avviso).


Quello che non capisco è come fa il computer a sapere quando legge il valore di una variabile e l'indirizzo come 10001 se è un int o char. Immagina di fare clic su un programma chiamato anyprog.exe. Immediatamente inizia l'esecuzione del codice. Questo file exe include informazioni su se le variabili sono memorizzate come in o char? -
user16307

1
@ user16307 In sostanza no, tutte queste informazioni sono completamente perse. Spetta al codice della macchina essere progettato abbastanza bene da fare il suo lavoro anche senza quelle informazioni. Tutto ciò che interessa al computer è che ci sono otto bit di fila all'indirizzo 10001. O è il tuo lavoro o il lavoro del compilatore , a seconda dei casi, tenere il passo con cose del genere manualmente mentre scrivi la macchina o il codice assembly.
Panzercrisis,

1
Si noti che la digitazione dinamica non è l'unica ragione per conservare i tipi. Java è tipizzato staticamente, ma deve comunque conservare i tipi, poiché consente di riflettere dinamicamente sul tipo. Inoltre, ha un polimorfismo di runtime, ovvero l'invio di metodi basato sul tipo di runtime, per il quale ha anche bisogno del tipo. Il C ++ inserisce il codice di invio del metodo nell'oggetto (o piuttosto nella classe) stessa, quindi non ha bisogno del tipo in un certo senso (anche se ovviamente la vtable è in qualche modo parte del tipo, quindi, almeno in parte il tipo viene mantenuto), ma in Java il codice di invio del metodo è centralizzato.
Jörg W Mittag,

guarda la mia domanda che ho scritto "quando viene eseguito un programma C?" non sono archiviati indirettamente nel file exe tra i codici di istruzione e alla fine hanno luogo in memoria? Lo scrivo di nuovo per te: se la CPU trova 0x00000061 in un registro e lo recupera; e immagina che il programma della console dovrebbe produrre questo come un carattere non int. ci sono in quel file exe (codice macchina / codice binario) alcuni codici di istruzioni che conoscono l'indirizzo di 0x00000061 è un carattere e converte in un carattere usando la tabella ASCII? In tal caso significa che gli identificatori di caratteri int sono indirettamente nel binario ???
user16307

Se il valore è 0x61 ed è dichiarato come carattere (cioè, 'a') e si chiama una routine per visualizzarlo, ci sarà [eventualmente] una chiamata di sistema per visualizzare quel carattere. Se lo hai dichiarato come int e chiama la routine di visualizzazione, il compilatore saprà generare codice per convertire 0x61 (decimale 97) nella sequenza ASCII 0x39, 0x37 ('9', '7'). In conclusione: il codice che viene generato è diverso perché il compilatore sa come trattarli in modo diverso.
Mike Harris,

3

Devi distinguere tra compiletimee runtimeda un lato e codee datadall'altro.

Dal punto di vista della macchina non c'è differenza tra ciò che chiami codeo instructionsciò che chiami data. Tutto si riduce ai numeri. Ma alcune sequenze - ciò che chiameremmo code- fanno qualcosa che troviamo utile, altre semplicemente crashla macchina.

Il lavoro svolto dalla CPU è un semplice ciclo a 4 fasi:

  • Recupera "dati" da un determinato indirizzo
  • Decodifica l'istruzione (ovvero "interpreta" il numero come un instruction)
  • Leggi un indirizzo efficace
  • Esegui e archivia i risultati

Questo è chiamato ciclo di istruzioni .

Ho letto che A e 4 sono memorizzati negli indirizzi RAM qui. Ma che dire di a e x?

ae xsono variabili, che sono segnaposto per gli indirizzi, in cui il programma potrebbe trovare il "contenuto" delle variabili. Quindi, ogni volta che aviene utilizzata la variabile , esiste effettivamente l'indirizzo del contenuto di ausato.

Più confusamente, come fa l'esecuzione a sapere che a è un carattere e x è un int?

L'esecuzione non conosce nulla. Da quanto detto nell'introduzione, la CPU recupera solo i dati e li interpreta come istruzioni.

La funzione printf è progettata per "conoscere" il tipo di input che si sta inserendo, ovvero il codice risultante fornisce le giuste istruzioni su come gestire un segmento di memoria speciale. Ovviamente, è possibile generare output senza senso: usando un indirizzo, dove nessuna stringa è memorizzata insieme a "% s", si printf()otterrà un risultato senza senso arrestato solo da una posizione di memoria casuale, dove è uno 0 ( \0).

Lo stesso vale per il punto di ingresso di un programma. Sotto il C64 era possibile mettere i tuoi programmi in (quasi) tutti gli indirizzi conosciuti. I programmi di assemblaggio venivano avviati con un'istruzione chiamata sysseguita da un indirizzo: sys 49152era un luogo comune dove inserire il codice dell'assemblatore. Ma nulla ti ha impedito di caricare, ad esempio, dati grafici in 49152, causando un arresto anomalo della macchina dopo "l'avvio" da questo punto. In questo caso, il ciclo di istruzioni è iniziato con la lettura di "dati grafici" e il tentativo di interpretarlo come "codice" (che ovviamente non aveva senso); gli effetti erano a volte sorprendenti;)

Supponiamo che un valore sia memorizzato da qualche parte nella RAM come 10011001; se sono il programma che esegue il codice, come faccio a sapere se questo 10011001 è un carattere o un int?

Come detto: il "contesto" - ovvero le istruzioni precedenti e successive - aiutano a trattare i dati nel modo desiderato. Dal punto di vista della macchina, non vi è alcuna differenza in alcuna posizione di memoria. inted charè solo il vocabolario, che ha senso in compiletime; durante runtime(a livello di assieme), non c'è charo int.

Quello che non capisco è come lo sa il computer, quando legge il valore di una variabile da un indirizzo come 10001, che si tratti di un int o di un carattere.

Il computer non sa nulla. Lo fa il programmatore . Il codice compilato genera il contesto , necessario per generare risultati significativi per l'uomo.

Questo file eseguibile include informazioni sul fatto che le variabili memorizzate siano del tipo int o char?

e No . Le informazioni, siano esse an into a, charvanno perse. D'altra parte, il contesto (le istruzioni che dicono, come gestire le posizioni di memoria, dove sono archiviati i dati) è preservato; così implicitamente sì, l '"informazione" è implicitamente disponibile.


Bella distinzione tra tempo di compilazione e runtime.
Michael Blackburn,

2

Manteniamo questa discussione solo nel linguaggio C.

Il programma a cui ti riferisci è scritto in un linguaggio di alto livello come C. Il computer capisce solo il linguaggio macchina. Linguaggi di livello superiore offrono al programmatore la possibilità di esprimere la logica in un modo più umano che viene poi tradotto in codice macchina che il microprocessore può decodificare ed eseguire. Ora parliamo del codice che hai citato:

char a = 'A';
int x = 4;

Proviamo ad analizzare ogni parte:

char / int sono noti come tipi di dati. Questi dicono al compilatore di allocare memoria. In questo caso charsaranno 1 byte e int2 byte. (Notare che questa dimensione della memoria dipende nuovamente dal microprocessore).

a / x sono noti come identificatori. Ora puoi dire nomi "user friendly" dati alle posizioni di memoria nella RAM.

= indica al compilatore di memorizzare 'A' nella posizione di memoria di ae 4 nella posizione di memoria x.

Pertanto, gli identificatori del tipo di dati int / char vengono utilizzati solo dal compilatore e non dal microprocessore durante l'esecuzione del programma. Quindi non sono memorizzati.


ok gli identificatori di tipi di dati int / char non sono archiviati direttamente nella memoria come variabili, ma non sono archiviati indirettamente nel file exe tra i codici di istruzione e alla fine hanno luogo in memoria? Lo scrivo di nuovo per te: se la CPU trova 0x00000061 in un registro e lo recupera; e immagina che il programma della console dovrebbe produrre questo come un carattere non int. ci sono in quel file exe (codice macchina / codice binario) alcuni codici di istruzioni che conoscono l'indirizzo di 0x00000061 è un carattere e converte in un carattere usando la tabella ASCII? In tal caso significa che gli identificatori di caratteri int sono indirettamente nel binario ???
user16307

No per CPU ha tutti i numeri. Ad esempio, la stampa su console non dipende dal fatto che la variabile sia char o int. Aggiornerò la mia risposta con un flusso dettagliato di come un programma di alto livello viene convertito in linguaggio macchina fino all'esecuzione del programma.
prasad,

2

La mia risposta qui è in qualche modo semplificata e farà riferimento solo a C.

No, le informazioni sul tipo non vengono memorizzate nel programma.

into charnon sono indicatori di tipo per la CPU; solo al compilatore.

L'esempio creato dal compilatore avrà istruzioni per manipolare ints se la variabile è stata dichiarata come int. Allo stesso modo, se la variabile è stata dichiarata come a char, l'exe conterrà le istruzioni per manipolare a char.

In C:

int main()
{
    int a = 65;
    char b = 'A';
    if(a == b)
    {
        printf("Well, what do you know. A char can equal an int.\n");
    }
    return 0;
}

Questo programma stamperà il suo messaggio, dato che chare e inthanno gli stessi valori nella RAM.

Ora, se ti stai chiedendo come printfriesce a produrre l'output 65per an inte Aper a char, è perché devi specificare nella "stringa di formato" come printftrattare il valore .
(Ad esempio, %csignifica trattare il valore come un chare %dsignifica trattare il valore come un numero intero; lo stesso valore in entrambi i modi, comunque.)


2
Speravo che qualcuno avrebbe usato un esempio usando printf. @OP: int a = 65; printf("%c", a)verrà emesso 'A'. Perché? Perché al processore non importa. Ad esso, tutto ciò che vede sono frammenti. Il tuo programma ha detto al processore di memorizzare 65 (per coincidenza il valore di 'A'in ASCII) su ae quindi di emettere un carattere, cosa che fa volentieri. Perché? Perché non importa.
Cole Johnson,

ma perché alcuni dicono qui nel caso C #, non è la storia? ho letto alcuni altri commenti e dicono che in C # e C ++ la storia (informazioni sui tipi di dati) è diversa e anche la CPU non fa il calcolo. Qualche idea a riguardo?
user16307

@ user16307 Se la CPU non esegue l'elaborazione, il programma non è in esecuzione. :) Per quanto riguarda C #, non lo so, ma penso che la mia risposta si applichi anche lì. Per quanto riguarda il C ++, so che la mia risposta si applica lì.
BenjiWiebe,

0

Al livello più basso, nella CPU fisica effettiva non ci sono tipi (ignorando le unità a virgola mobile). Solo schemi di bit. Un computer funziona manipolando modelli di bit, molto, molto velocemente.

Questo è tutto ciò che la CPU fa mai, tutto ciò che può fare. Non esiste un int o un carattere.

x = 4 + 5

Verrà eseguito come:

  1. Caricare 00000100 nel registro 1
  2. Caricare 00000101 nel registro 2
  3. Aggiunta del registro 1 al registro 2 e memorizzazione nel registro 1

L'istruzione iadd attiva l'hardware che si comporta come se i registri 1 e 2 fossero numeri interi. Se in realtà non rappresentano numeri interi, ogni tipo di cose può andare storto in seguito. Il risultato migliore è di solito il crash.

È sul compilatore scegliere l'istruzione corretta in base ai tipi indicati nel sorgente, ma nel codice macchina effettivo eseguito dalla CPU, non ci sono tipi, ovunque.

modifica: si noti che il codice macchina effettivo non menziona in realtà 4, o 5 o numeri interi da nessuna parte. sono solo due schemi di bit e un'istruzione che accetta due schemi di bit, presuppone che siano integ e li somma insieme.


0

Risposta breve, il tipo è codificato nelle istruzioni della CPU generate dal compilatore.

Sebbene le informazioni sul tipo o sulla dimensione delle informazioni non vengano archiviate direttamente, il compilatore tiene traccia di tali informazioni quando accede, modifica e memorizza i valori in queste variabili.

come fa a sapere che a è un carattere e x è un int?

Non lo fa, ma quando il compilatore produce il codice macchina lo sa. An inte a charpossono avere dimensioni diverse. In un'architettura in cui un carattere ha le dimensioni di un byte e un int è di 4 byte, la variabile xnon è nell'indirizzo 10001, ma anche in 10002, 10003 e 10004. Quando il codice deve caricare il valore di xin un registro CPU, utilizza l'istruzione per caricare 4 byte. Quando si carica un carattere, utilizza l'istruzione per caricare 1 byte.

Come scegliere quale delle due istruzioni? Il compilatore decide durante la compilazione, non viene eseguito in fase di esecuzione dopo aver ispezionato i valori in memoria.

Si noti inoltre che i registri possono avere dimensioni diverse. Sulle CPU Intel x86, l'EAX ha una larghezza di 32 bit, metà di essa è AX, che è 16, e AX è suddiviso in AH e AL, entrambi a 8 bit.

Quindi, se si desidera caricare un numero intero (su CPU x86), si utilizza l'istruzione MOV per i numeri interi, per caricare un carattere si utilizza l'istruzione MOV per i caratteri. Entrambi sono chiamati MOV, ma hanno codici operativi diversi. Essendo effettivamente due diverse istruzioni. Il tipo di variabile è codificato nell'istruzione da utilizzare.

La stessa cosa accade con altre operazioni. Esistono molte istruzioni per eseguire l'aggiunta, a seconda delle dimensioni degli operandi e anche se sono firmate o non firmate. Vedi https://en.wikipedia.org/wiki/ADD_(x86_instruction) che elenca diverse possibili aggiunte.

Supponiamo che un valore sia memorizzato da qualche parte nella RAM come 10011001; se sono il programma che esegue il codice, come faccio a sapere se questo 10011001 è un carattere o un int

Innanzitutto, un carattere sarebbe 10011001, ma un int sarebbe 00000000 00000000 00000000 10011001, perché hanno dimensioni diverse (su un computer con le stesse dimensioni di cui sopra). Ma lascia prendere in considerazione il caso di signed charvs unsigned char.

Ciò che è memorizzato in una posizione di memoria può essere interpretato come desideri. Parte delle responsabilità del compilatore C consiste nell'assicurare che ciò che viene archiviato e letto da una variabile sia eseguito in modo coerente. Quindi non è che il programma sappia cosa è memorizzato in una posizione di memoria, ma che è d'accordo prima che leggerà e scriverà sempre lo stesso tipo di cose lì. (senza contare cose come i tipi di casting).


ma perché alcuni dicono qui nel caso C #, non è la storia? ho letto alcuni altri commenti e dicono che in C # e C ++ la storia (informazioni sui tipi di dati) è diversa e anche la CPU non fa il calcolo. Qualche idea a riguardo?
user16307

0

ma perché alcuni dicono qui nel caso C #, non è la storia? ho letto alcuni altri commenti e dicono che in C # e C ++ la storia (informazioni sui tipi di dati) è diversa e anche la CPU non fa il calcolo. Qualche idea a riguardo?

In linguaggi controllati dal tipo come C #, il controllo del tipo viene eseguito dal compilatore. Il codice benji ha scritto:

int main()
{
    int a = 65;
    char b = 'A';
    if(a == b)
    {
        printf("Well, what do you know. A char can equal an int.\n");
    }
    return 0;
}

Rifiuterebbe semplicemente di compilare. Allo stesso modo se provassi a moltiplicare una stringa e un numero intero (stavo per dire aggiungi, ma l'operatore '+' è sovraccarico di concatenazione di stringhe e potrebbe funzionare).

int a = 42;
string b = "Compilers are awesome.";
double[] c = a * b;

Il compilatore semplicemente rifiuta di generare codice macchina da questo C #, non importa quanto la tua stringa l'abbia baciata.


-4

Le altre risposte sono corrette in quanto essenzialmente ogni dispositivo consumer che incontrerai non memorizza informazioni sul tipo. Tuttavia, in passato (e oggi, in un contesto di ricerca) sono stati utilizzati diversi progetti hardware che utilizzano un'architettura con tag : memorizzano sia i dati che il tipo (e possibilmente anche altre informazioni). Questi includerebbero soprattutto le macchine Lisp .

Ricordo vagamente di aver sentito parlare di un'architettura hardware progettata per la programmazione orientata agli oggetti che aveva qualcosa di simile, ma non riesco a trovarla ora.


3
La domanda specifica che si riferisce al linguaggio C (non Lisp) e il linguaggio C non memorizza metadati variabili. Mentre è certamente possibile per un'implementazione in C fare questo, poiché lo standard non lo proibisce, in pratica non succede mai. Se si dispone di esempi relativi alla domanda, si prega di fornire le citazioni specifiche e di fornire i riferimenti che si riferiscono al linguaggio C .

Bene, potresti scrivere un compilatore C per una macchina Lisp, ma nessuno usa macchine Lisp al giorno d'oggi ed età in generale. A proposito , l' architettura orientata agli oggetti era Rekursiv .
Nathan Ringo,

2
Penso che questa risposta non sia utile. Ciò complica le cose ben oltre l'attuale livello di comprensione del PO. È chiaro che l'OP non capisce il modello di esecuzione di base di una CPU + RAM e come un compilatore traduca una sorgente simbolica di alto livello in un binario eseguibile. La memoria contrassegnata, RTTI, Lisp, ecc., È molto al di là di ciò che l'interlocutore deve sapere secondo me, e lo confonderà solo di più.
Andres F.

ma perché alcuni dicono qui nel caso C #, non è la storia? ho letto alcuni altri commenti e dicono che in C # e C ++ la storia (informazioni sui tipi di dati) è diversa e anche la CPU non fa il calcolo. Qualche idea a riguardo?
user16307
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.