In che modo i tipi di dati C sono "supportati direttamente dalla maggior parte dei computer"?


114

Sto leggendo "The C Programming Language" di K&R e mi sono imbattuto in questa affermazione [Introduzione, p. 3]:

Poiché i tipi di dati e le strutture di controllo forniti da C sono supportati direttamente dalla maggior parte dei computer , la libreria di runtime richiesta per implementare programmi autonomi è minuscola.

Cosa significa la dichiarazione in grassetto? C'è un esempio di un tipo di dati o una struttura di controllo che non è supportata direttamente da un computer?


1
Oggigiorno, il linguaggio C supporta l'aritmetica complessa, ma originariamente non lo faceva perché i computer non supportano direttamente i numeri complessi come tipi di dati.
Jonathan Leffler

12
In realtà, storicamente era il contrario: C è stato progettato dalle operazioni e dai tipi hardware disponibili in quel momento.
Basile Starynkevitch

2
La maggior parte dei computer non ha un supporto hardware diretto per i decimali in virgola
mobile

3
@MSalters: stavo cercando di suggerire una direzione per la domanda "C'è un esempio di un tipo di dati o di una struttura di controllo che non è supportata direttamente da un computer?" che non ho interpretato come limitato a K&R
PlasmaHH

11
In che modo questo non è un duplicato più di 6 anni dopo il lancio di Stack Overflow?
Peter Mortensen

Risposte:


143

Sì, esistono tipi di dati non supportati direttamente.

In molti sistemi embedded, non esiste un'unità hardware a virgola mobile. Quindi, quando scrivi un codice come questo:

float x = 1.0f, y = 2.0f;
return x + y;

Viene tradotto in qualcosa del genere:

unsigned x = 0x3f800000, y = 0x40000000;
return _float_add(x, y);

Quindi il compilatore o la libreria standard deve fornire un'implementazione di _float_add(), che occupa memoria nel sistema embedded. Se stai contando i byte su un sistema molto piccolo, questo può sommarsi.

Un altro esempio comune sono gli interi a 64 bit ( long longnello standard C dal 1999), che non sono supportati direttamente dai sistemi a 32 bit. I vecchi sistemi SPARC non supportavano la moltiplicazione di numeri interi, quindi la moltiplicazione doveva essere fornita dal runtime. Ci sono altri esempi.

Altre lingue

In confronto, altri linguaggi hanno primitive più complicate.

Ad esempio, un simbolo Lisp richiede molto supporto di runtime, proprio come le tabelle in Lua, le stringhe in Python, gli array in Fortran, eccetera. I tipi equivalenti in C di solito non fanno parte della libreria standard (nessun simbolo o tabella standard) o sono molto più semplici e non richiedono molto supporto di runtime (gli array in C sono fondamentalmente solo puntatori, le stringhe con terminazione nul sono quasi altrettanto semplice).

Strutture di controllo

Una struttura di controllo notevole che manca da C è la gestione delle eccezioni. L'uscita non locale è limitata a setjmp()e longjmp(), che salva e ripristina solo alcune parti dello stato del processore. In confronto, il runtime C ++ deve camminare sullo stack e chiamare distruttori e gestori di eccezioni.


2
fondamentalmente solo puntatori ... piuttosto, fondamentalmente solo pezzi grezzi di memoria. Anche se è pignolo, e la risposta è comunque buona.
Deduplicatore

2
Si potrebbe sostenere che le stringhe con terminazione nulla hanno "supporto hardware" poiché il terminatore di stringa si adatta all'operazione "salta se zero" della maggior parte dei processori e quindi è leggermente più veloce di altre possibili implementazioni di stringhe.
Peteris

1
Ho pubblicato la mia risposta per espandere su come C è progettato per mappare semplicemente su asm.
Peter Cordes

1
Si prega di non utilizzare la collocazione "gli array sono fondamentalmente solo dei puntatori", può seriamente, gravemente fuorviare un principiante come OP. Qualcosa sulla falsariga di "gli array sono implementati direttamente utilizzando i puntatori a livello di hardware" sarebbe meglio IMO.
The Paramagnetic Croissant

1
@TheParamagneticCroissant: penso che in questo contesto sia appropriato ... la chiarezza va a scapito della precisione.
Dietrich Epp

37

In realtà, scommetto che i contenuti di questa introduzione non sono cambiati molto dal 1978, quando Kernighan e Ritchie li scrissero per la prima volta nella prima edizione del libro, e si riferiscono alla storia e all'evoluzione del C in quel momento più che moderno implementazioni.

I computer sono fondamentalmente solo banchi di memoria e processori centrali, e ogni processore funziona utilizzando un codice macchina; parte del design di ogni processore è un'architettura del set di istruzioni, chiamata Assembly Language , che mappa uno a uno da un insieme di mnemonici leggibili dall'uomo al codice macchina, che è composto da tutti i numeri.

Gli autori del linguaggio C - e dei linguaggi B e BCPL che lo hanno immediatamente preceduto - erano intenzionati a definire costrutti nel linguaggio che fossero compilati nel modo più efficiente possibile in Assembly ... in effetti, erano costretti a farlo da limitazioni nel target hardware. Come altre risposte hanno sottolineato, questo ha coinvolto rami (GOTO e altri controlli di flusso in C), spostamenti (assegnazione), operazioni logiche (& | ^), aritmetica di base (addizione, sottrazione, incremento, decremento) e indirizzamento della memoria (puntatori ). Un buon esempio sono gli operatori pre / post-incremento e decremento in C, che presumibilmente sono stati aggiunti al linguaggio B da Ken Thompson specificamente perché erano in grado di tradurre direttamente in un singolo codice operativo una volta compilato.

Questo è ciò che intendevano gli autori quando dicevano "supportato direttamente dalla maggior parte dei computer". Non significavano che altri linguaggi contenessero tipi e strutture che non erano supportati direttamente - intendevano che in base alla progettazione i costrutti C tradotti più direttamente (a volte letteralmente direttamente) in Assembly.

Questa stretta relazione con l'assembly sottostante, pur fornendo tutti gli elementi necessari per la programmazione strutturata, è ciò che ha portato all'adozione anticipata del C e ciò che lo mantiene un linguaggio popolare oggi in ambienti in cui l'efficienza del codice compilato è ancora fondamentale.

Per un interessante articolo sulla storia della lingua, vedere The Development of the C Language - Dennis Ritchie


14

La risposta breve è che la maggior parte dei costrutti linguistici supportati da C sono supportati anche dal microprocessore del computer di destinazione, quindi il codice C compilato si traduce in modo molto piacevole ed efficiente nel linguaggio assembly del microprocessore, risultando così in codice più piccolo e un ingombro minore.

La risposta più lunga richiede un po 'di conoscenza del linguaggio assembly. In C, un'affermazione come questa:

int myInt = 10;

si tradurrebbe in qualcosa di simile in assembly:

myInt dw 1
mov myInt,10

Confronta questo con qualcosa come C ++:

MyClass myClass;
myClass.set_myInt(10);

Il codice in linguaggio assembly risultante (a seconda di quanto è grande MyClass ()), potrebbe aggiungere fino a centinaia di righe in linguaggio assembly.

Senza creare effettivamente programmi in linguaggio assembly, il C puro è probabilmente il codice più "scarno" e "stretto" in cui puoi creare un programma.

MODIFICARE

Dati i commenti sulla mia risposta, ho deciso di eseguire un test, solo per la mia sanità mentale. Ho creato un programma chiamato "test.c", che assomigliava a questo:

#include <stdio.h>

void main()
{
    int myInt=10;

    printf("%d\n", myInt);
}

L'ho compilato fino all'assemblaggio usando gcc. Ho usato la seguente riga di comando per compilarlo:

gcc -S -O2 test.c

Ecco il linguaggio assembly risultante:

    .file   "test.c"
    .section    .rodata.str1.1,"aMS",@progbits,1
.LC0:
    .string "%d\n"
    .section    .text.unlikely,"ax",@progbits
.LCOLDB1:
    .section    .text.startup,"ax",@progbits
.LHOTB1:
    .p2align 4,,15
    .globl  main
    .type   main, @function
main:
.LFB24:
    .cfi_startproc
    movl    $10, %edx
    movl    $.LC0, %esi
    movl    $1, %edi
    xorl    %eax, %eax
    jmp __printf_chk
    .cfi_endproc
.LFE24:
    .size   main, .-main
    .section    .text.unlikely
.LCOLDE1:
    .section    .text.startup
.LHOTE1:
    .ident  "GCC: (Ubuntu 4.9.1-16ubuntu6) 4.9.1"
    .section    .note.GNU-stack,"",@progbits

Quindi creo un file chiamato "test.cpp" che definisce una classe e restituisce la stessa cosa di "test.c":

#include <iostream>
using namespace std;

class MyClass {
    int myVar;
public:
    void set_myVar(int);
    int get_myVar(void);
};

void MyClass::set_myVar(int val)
{
    myVar = val;
}

int MyClass::get_myVar(void)
{
    return myVar;
}

int main()
{
    MyClass myClass;
    myClass.set_myVar(10);

    cout << myClass.get_myVar() << endl;

    return 0;
}

L'ho compilato allo stesso modo, usando questo comando:

g++ -O2 -S test.cpp

Ecco il file di assieme risultante:

    .file   "test.cpp"
    .section    .text.unlikely,"ax",@progbits
    .align 2
.LCOLDB0:
    .text
.LHOTB0:
    .align 2
    .p2align 4,,15
    .globl  _ZN7MyClass9set_myVarEi
    .type   _ZN7MyClass9set_myVarEi, @function
_ZN7MyClass9set_myVarEi:
.LFB1047:
    .cfi_startproc
    movl    %esi, (%rdi)
    ret
    .cfi_endproc
.LFE1047:
    .size   _ZN7MyClass9set_myVarEi, .-_ZN7MyClass9set_myVarEi
    .section    .text.unlikely
.LCOLDE0:
    .text
.LHOTE0:
    .section    .text.unlikely
    .align 2
.LCOLDB1:
    .text
.LHOTB1:
    .align 2
    .p2align 4,,15
    .globl  _ZN7MyClass9get_myVarEv
    .type   _ZN7MyClass9get_myVarEv, @function
_ZN7MyClass9get_myVarEv:
.LFB1048:
    .cfi_startproc
    movl    (%rdi), %eax
    ret
    .cfi_endproc
.LFE1048:
    .size   _ZN7MyClass9get_myVarEv, .-_ZN7MyClass9get_myVarEv
    .section    .text.unlikely
.LCOLDE1:
    .text
.LHOTE1:
    .section    .text.unlikely
.LCOLDB2:
    .section    .text.startup,"ax",@progbits
.LHOTB2:
    .p2align 4,,15
    .globl  main
    .type   main, @function
main:
.LFB1049:
    .cfi_startproc
    subq    $8, %rsp
    .cfi_def_cfa_offset 16
    movl    $10, %esi
    movl    $_ZSt4cout, %edi
    call    _ZNSolsEi
    movq    %rax, %rdi
    call    _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
    xorl    %eax, %eax
    addq    $8, %rsp
    .cfi_def_cfa_offset 8
    ret
    .cfi_endproc
.LFE1049:
    .size   main, .-main
    .section    .text.unlikely
.LCOLDE2:
    .section    .text.startup
.LHOTE2:
    .section    .text.unlikely
.LCOLDB3:
    .section    .text.startup
.LHOTB3:
    .p2align 4,,15
    .type   _GLOBAL__sub_I__ZN7MyClass9set_myVarEi, @function
_GLOBAL__sub_I__ZN7MyClass9set_myVarEi:
.LFB1056:
    .cfi_startproc
    subq    $8, %rsp
    .cfi_def_cfa_offset 16
    movl    $_ZStL8__ioinit, %edi
    call    _ZNSt8ios_base4InitC1Ev
    movl    $__dso_handle, %edx
    movl    $_ZStL8__ioinit, %esi
    movl    $_ZNSt8ios_base4InitD1Ev, %edi
    addq    $8, %rsp
    .cfi_def_cfa_offset 8
    jmp __cxa_atexit
    .cfi_endproc
.LFE1056:
    .size   _GLOBAL__sub_I__ZN7MyClass9set_myVarEi, .-_GLOBAL__sub_I__ZN7MyClass9set_myVarEi
    .section    .text.unlikely
.LCOLDE3:
    .section    .text.startup
.LHOTE3:
    .section    .init_array,"aw"
    .align 8
    .quad   _GLOBAL__sub_I__ZN7MyClass9set_myVarEi
    .local  _ZStL8__ioinit
    .comm   _ZStL8__ioinit,1,1
    .hidden __dso_handle
    .ident  "GCC: (Ubuntu 4.9.1-16ubuntu6) 4.9.1"
    .section    .note.GNU-stack,"",@progbits

Come puoi vedere chiaramente, il file assembly risultante è molto più grande nel file C ++ che nel file C. Anche se si elimina tutto il resto e si confronta il C "main" con il C ++ "main", ci sono molte cose extra.


14
Quel "codice C ++" semplicemente non è C ++. MyClass myClass { 10 }È molto probabile che il codice reale come in C ++ venga compilato esattamente nello stesso assembly. I moderni compilatori C ++ hanno eliminato la penalità di astrazione. Di conseguenza, spesso possono battere i compilatori C. Ad esempio, la penalità di astrazione in C qsortè reale, ma C ++ std::sortnon ha alcuna penalità di astrazione anche dopo l'ottimizzazione di base.
MSalters

1
Puoi facilmente vedere usando IDA Pro che la maggior parte dei costrutti C ++ si compila alla stessa cosa come farlo manualmente in C, i costruttori e i dtor vengono inline per oggetti banali, quindi viene applicata l'ottimizzazione futura
paulm

7

K&R significa che la maggior parte delle espressioni C (significato tecnico) si associa a una o poche istruzioni di assembly, non a una chiamata di funzione a una libreria di supporto. Le solite eccezioni sono la divisione intera su architetture senza un'istruzione div hardware, o virgola mobile su macchine senza FPU.

C'è una citazione:

C combina la flessibilità e la potenza del linguaggio assembly con la facilità d'uso del linguaggio assembly.

( trovato qui . Pensavo di ricordare una variazione diversa, come "velocità del linguaggio assembly con la comodità e l'espressività del linguaggio assembly".)

long int ha solitamente la stessa larghezza dei registri della macchina nativa.

Alcuni linguaggi di livello superiore definiscono la larghezza esatta dei loro tipi di dati e le implementazioni su tutte le macchine devono funzionare allo stesso modo. Non C, però.

Se vuoi lavorare con int a 128 bit su x86-64, o nel caso generale BigInteger di dimensioni arbitrarie, hai bisogno di una libreria di funzioni per questo. Tutte le CPU ora usano il complemento a 2 come rappresentazione binaria di numeri interi negativi, ma anche questo non era il caso quando C è stato progettato. (Ecco perché alcune cose che darebbero risultati diversi su macchine senza complemento 2s sono tecnicamente indefinite negli standard C.)

I puntatori C ai dati o alle funzioni funzionano allo stesso modo degli indirizzi dell'assembly.

Se vuoi riferimenti con conteggio ref, devi farlo da solo. Se desideri funzioni membro virtuali c ++ che chiamano una funzione diversa a seconda del tipo di oggetto a cui punta il puntatore, il compilatore C ++ deve generare molto di più di un semplicecall istruzione con un indirizzo fisso.

Le stringhe sono solo array

Al di fuori delle funzioni di libreria, le uniche operazioni sulle stringhe fornite sono leggere / scrivere un carattere. Nessun concatenamento, nessuna sottostringa, nessuna ricerca. (Le stringhe sono memorizzate come '\0'array con terminazione nul ( ) di interi a 8 bit, non come puntatore + lunghezza, quindi per ottenere una sottostringa dovresti scrivere un nul nella stringa originale.)

Le CPU a volte hanno istruzioni progettate per essere utilizzate da una funzione di ricerca di stringhe, ma di solito elaborano ancora un byte per istruzione eseguita, in un ciclo. (o con il prefisso x86 rep. Forse se C fosse stato progettato su x86, la ricerca o il confronto di stringhe sarebbe un'operazione nativa, piuttosto che una chiamata a una funzione di libreria.)

Molte altre risposte forniscono esempi di cose che non sono supportate in modo nativo, come la gestione delle eccezioni, tabelle hash, elenchi. La filosofia di design di K&R è la ragione per cui C non ha nessuno di questi in modo nativo.


"K&R significa che la maggior parte delle espressioni C (significato tecnico) si associa a una o poche istruzioni assembly, non una chiamata di funzione a una libreria di supporto." Questa è una spiegazione molto intuitiva. Grazie.
gwg

1
Mi sono appena imbattuto nel termine "linguaggio di von Neumann" ( en.wikipedia.org/wiki/Von_Neumann_programming_languages ). È ESATTAMENTE quello che è C.
Peter Cordes

1
Questo è esattamente il motivo per cui uso C. Ma quello che mi ha sorpreso quando ho imparato che C è che, tentando di essere efficiente per un'ampia gamma di hardware, a volte è inetto e inefficiente sulla maggior parte dell'hardware moderno. Intendo per esempio un modo-non-utile-e-affidabile-per-rilevare-l'overflow-intero-in-c e l' aggiunta di più parole usando-il-carry-flag .
Bosone Z

6

Il linguaggio assembly di un processo generalmente si occupa di jump (go to), istruzioni, istruzioni di spostamento, artritiche binarie (XOR, NAND, AND OR, ecc.), Campi di memoria (o indirizzo). Classifica la memoria in due tipi, istruzione e dati. Questo è tutto ciò che è un linguaggio assembly (sono sicuro che i programmatori di assembly sosterranno che c'è di più, ma si riduce a questo in generale). C assomiglia molto a questa semplicità.

C sta per assemblare ciò che l'algebra sta all'aritmetica.

C racchiude le basi dell'assembly (il linguaggio del processore). Probabilmente è un'affermazione più vera di "Perché i tipi di dati e le strutture di controllo forniti da C sono supportati direttamente dalla maggior parte dei computer"


5

Attenzione ai confronti fuorvianti

  1. L'affermazione si basa sulla nozione di "libreria run-time" , che da allora è per lo più passata di moda, almeno per le lingue tradizionali di alto livello. (È ancora rilevante per i sistemi embedded più piccoli.) Il tempo di esecuzione è il supporto minimo che un programma in quel linguaggio richiede per eseguire quando si utilizzano solo costrutti incorporati nel linguaggio (al contrario di chiamare esplicitamente una funzione fornita da una libreria) .
  2. Al contrario, i linguaggi moderni tendono a non discriminare tra il runtime e la libreria standard , quest'ultima spesso piuttosto ampia.
  3. Al tempo del libro K&R, C non aveva nemmeno una libreria standard . Piuttosto, le librerie C disponibili differivano un po 'tra i diversi gusti di Unix.
  4. Per comprendere l'affermazione non dovresti confrontare i linguaggi con una libreria standard (come Lua e Python menzionati in altre risposte), ma i linguaggi con più costrutti incorporati (come il vecchio LISP e il vecchio FORTRAN menzionati in altre risposte). Altri esempi potrebbero essere BASIC (interattivo, come LISP) o PASCAL (compilato, come FORTRAN) che hanno entrambi (tra le altre cose) funzionalità di input / output incorporate direttamente nel linguaggio stesso.
  5. Al contrario, non esiste un modo standard per ottenere i risultati del calcolo da un programma C che utilizza solo il runtime, non alcuna libreria.

D'altra parte, la maggior parte dei linguaggi moderni viene eseguita all'interno di ambienti di runtime dedicati che forniscono servizi come la garbage collection.
Nate CK

5

C'è un esempio di un tipo di dati o una struttura di controllo che non è supportata direttamente da un computer?

Tutti i tipi di dati fondamentali e le loro operazioni nel linguaggio C possono essere implementati da una o poche istruzioni in linguaggio macchina senza loop: sono supportati direttamente dalla (praticamente ogni) CPU.

Diversi tipi di dati popolari e le loro operazioni richiedono dozzine di istruzioni in linguaggio macchina o richiedono l'iterazione di un ciclo di runtime o entrambi.

Molti linguaggi hanno una sintassi abbreviata speciale per tali tipi e le loro operazioni: l'utilizzo di tali tipi di dati in C generalmente richiede la digitazione di molto più codice.

Tali tipi di dati e operazioni includono:

  • manipolazione di stringhe di testo di lunghezza arbitraria: concatenazione, sottostringa, assegnazione di una nuova stringa a una variabile inizializzata con un'altra stringa, ecc. ('s = "Hello World!"; s = (s + s) [2: -2] 'in Python)
  • imposta
  • oggetti con distruttori virtuali annidati, come in C ++ e in ogni altro linguaggio di programmazione orientato agli oggetti
  • Moltiplicazione e divisione di matrici 2D; risoluzione di sistemi lineari ("C = B / A; x = A \ b" in MATLAB e molti linguaggi di programmazione di array)
  • espressioni regolari
  • array di lunghezza variabile, in particolare l'aggiunta di un elemento alla fine dell'array, che (a volte) richiede l'allocazione di più memoria.
  • leggere il valore delle variabili che cambiano tipo in fase di esecuzione - a volte è un float, altre volte è una stringa
  • array associativi (spesso chiamati "mappe" o "dizionari")
  • liste
  • rapporti ("(+ 1/3 2/7)" restituisce "13/21" in Lisp )
  • aritmetica a precisione arbitraria (spesso chiamata "bignum")
  • convertire i dati in una rappresentazione stampabile (il metodo ".tostring" in JavaScript)
  • saturazione dei numeri in virgola fissa (spesso usati nei programmi C incorporati)
  • valutare una stringa digitata in fase di esecuzione come se fosse un'espressione ("eval ()" in molti linguaggi di programmazione).

Tutte queste operazioni richiedono dozzine di istruzioni in linguaggio macchina o richiedono l'iterazione di un ciclo di runtime su quasi tutti i processori.

Alcune strutture di controllo popolari che richiedono anche dozzine di istruzioni in linguaggio macchina o loop includono:

  • chiusure
  • continuazioni
  • eccezioni
  • valutazione pigra

Sia che sia scritto in C o in qualche altro linguaggio, quando un programma manipola tali tipi di dati, la CPU deve eventualmente eseguire tutte le istruzioni necessarie per manipolare quei tipi di dati. Queste istruzioni sono spesso contenute in una "libreria". Ogni linguaggio di programmazione, anche C, ha una "libreria run-time" per ogni piattaforma che è inclusa di default in ogni eseguibile.

La maggior parte delle persone che scrivono compilatori inseriscono le istruzioni per manipolare tutti i tipi di dati "incorporati nel linguaggio" nella loro libreria di runtime. Poiché C non ha nessuno dei tipi di dati e delle operazioni e delle strutture di controllo di cui sopra incorporati nel linguaggio, nessuno di essi è incluso nella libreria di runtime C, il che rende la libreria di runtime C più piccola della libreria runtime libreria time di altri linguaggi di programmazione che hanno più di quanto sopra integrato nel linguaggio.

Quando un programmatore vuole che un programma - in C o qualsiasi altro linguaggio di sua scelta - manipoli altri tipi di dati che non sono "incorporati nel linguaggio", quel programmatore generalmente dice al compilatore di includere librerie aggiuntive con quel programma, o talvolta (per "evitare dipendenze") scrive ancora un'altra implementazione di quelle operazioni direttamente nel programma.


Se la tua implementazione di Lisp valuta (+ 1/3 2/7) come 3/21, penso che tu debba avere un'implementazione particolarmente creativa ...
RobertB

4

Quali sono i tipi di dati incorporati in C ? Sono cose come int, char, * int,float , array ecc ... Questi tipi di dati sono comprese dalla CPU. La CPU sa come lavorare con gli array, come dereferenziare i puntatori e come eseguire operazioni aritmetiche su puntatori, interi e numeri in virgola mobile.

Ma quando si passa a linguaggi di programmazione di livello superiore, si sono costruiti tipi di dati astratti e costrutti più complessi. Ad esempio, guarda la vasta gamma di classi incorporate nel linguaggio di programmazione C ++. La CPU non comprende classi, oggetti o tipi di dati astratti, quindi il runtime C ++ colma il divario tra la CPU e il linguaggio. Questi sono esempi di tipi di dati non supportati direttamente dalla maggior parte dei computer.


2
x86 sa di funzionare con alcuni array, ma non tutti. Per dimensioni degli elementi grandi o insolite, sarà necessario eseguire operazioni aritmetiche su interi per convertire un indice di matrice in un offset del puntatore. E su altre piattaforme, questo è sempre necessario. E l'idea che la CPU non capisca le classi C ++ è ridicola. Sono solo offset del puntatore, come le strutture C. Non hai bisogno di un runtime per questo.
MSalters

@MSalters sì, ma i metodi effettivi delle classi di libreria standard come iostreams ecc. Sono funzioni di libreria piuttosto che essere direttamente supportati dal compilatore. Tuttavia, i linguaggi di livello superiore con cui lo stavano probabilmente confrontando non erano C ++, ma linguaggi contemporanei come FORTRAN e PL / I.
Casuale 832

1
Le classi C ++ con funzioni membro virtuali si traducono in molto più di un semplice offset in una struttura.
Peter Cordes

4

Dipende dal computer. Sul PDP-11, dove è stato inventato C, longera scarsamente supportato (c'era un modulo aggiuntivo opzionale che si poteva acquistare che supportava alcune, ma non tutte, le operazioni a 32 bit). Lo stesso vale a vari livelli su qualsiasi sistema a 16 bit, incluso il PC IBM originale. E allo stesso modo per le operazioni a 64 bit su macchine a 32 bit o in programmi a 32 bit, sebbene il linguaggio C al momento del libro K&R non avesse affatto operazioni a 64 bit. E naturalmente ci sono stati molti sistemi negli anni '80 e '90 [inclusi i processori 386 e circa 486], e persino alcuni sistemi embedded oggi, che non supportavano direttamente l'aritmetica in virgola mobile ( floato double).

Per un esempio più esotico, alcune architetture di computer supportano solo puntatori "orientati alla parola" (che puntano a un numero intero a due o quattro byte in memoria), e i puntatori a byte ( char *o void *) dovevano essere implementati aggiungendo un campo di offset extra. Questa domanda entra in alcuni dettagli su tali sistemi.

Le funzioni della "libreria run-time" a cui si riferisce non sono quelle che vedrai nel manuale, ma funzioni come queste, nella libreria runtime di un compilatore moderno , che servono per implementare le operazioni di tipo base che non sono supportate dalla macchina . La libreria runtime a cui si riferivano gli stessi K&R può essere trovata sul sito web di The Unix Heritage Society - puoi vedere funzioni come ldiv(distinte dalla funzione C con lo stesso nome, che all'epoca non esisteva) che viene utilizzata per implementare la divisione di Valori a 32 bit, che il PDP-11 non supportava nemmeno con l'add-on, e csv(e cretanche in csv.c) che salvano e ripristinano i registri sullo stack per gestire chiamate e ritorni dalle funzioni.

Probabilmente si riferivano anche alla loro scelta di non supportare molti tipi di dati che non sono direttamente supportati dalla macchina sottostante, a differenza di altri linguaggi contemporanei come FORTRAN, che aveva la semantica di array che non si mappava bene al supporto del puntatore sottostante della CPU come Array di C. Il fatto che gli array C siano sempre con indicizzazione zero e sempre di dimensioni note in tutti i ranghi, ma il primo significa che non è necessario memorizzare gli intervalli di indice o le dimensioni degli array e non è necessario disporre di funzioni di libreria di runtime per accedervi - il compilatore può semplicemente codificare in modo rigido l'aritmetica del puntatore necessaria.


3

L'affermazione significa semplicemente che i dati e le strutture di controllo in C sono orientati alla macchina.

Ci sono due aspetti da considerare qui. Uno è che il linguaggio C ha una definizione (standard ISO) che consente la latitudine nel modo in cui vengono definiti i tipi di dati. Ciò significa che le implementazioni in linguaggio C sono adattate alla macchina . I tipi di dati di un compilatore C corrispondono a ciò che è disponibile nella macchina a cui il compilatore si rivolge, perché il linguaggio ha latitudine per questo. Se una macchina ha una dimensione di parola insolita, come 36 bit, allora il tipo into longpuò essere reso conforme a quello. I programmi che presumono che intsia esattamente 32 bit verranno interrotti.

In secondo luogo, a causa di tali problemi di portabilità, c'è un secondo effetto. In un certo senso, l'affermazione nel K&R è diventata una specie di esattamente 8 bit di larghezza. Solo alcuni programmi scritti da esperti di portabilità continueranno a funzionare: probabilmente non abbastanza per mettere insieme un sistema completo con toolchain, kernel, spazio utente e applicazioni utili, con uno sforzo ragionevole. In altre parole, i tipi C assomigliano a ciò che è disponibile dall'hardware perché l'hardware è stato creato per assomigliare a qualche altro hardware per il quale sono stati scritti molti programmi C non portatili. profezia che autoavvera , o forse al contrario. Vale a dire, gli implementatori di nuovi processori sono consapevoli della forte necessità di supportare i compilatori C e sanno che esiste molto codice C che presuppone che "ogni processore assomigli a un 80386". Le architetture sono progettate con C in mente: e non solo C in mente, ma anche con idee sbagliate comuni sulla portabilità del C in mente. Semplicemente non puoi più introdurre una macchina con 9 bit byte o qualsiasi altra cosa per uso generale. Programmi che assumono che il tipochar

C'è un esempio di un tipo di dati o una struttura di controllo che non è supportata direttamente da un computer?

Tipi di dati non supportati direttamente in molti linguaggi macchina: numero intero multi-precisione; lista collegata; tabella hash; stringa di caratteri.

Strutture di controllo non direttamente supportate nella maggior parte dei linguaggi macchina: continuazione di prima classe; coroutine / filo; Generatore; la gestione delle eccezioni.

Tutti questi richiedono un considerevole codice di supporto in fase di esecuzione creato utilizzando numerose istruzioni per scopi generali e tipi di dati più elementari.

C ha alcuni tipi di dati standard che non sono supportati da alcune macchine. Dal C99, C ha numeri complessi. Sono costituiti da due valori in virgola mobile e fatti per funzionare con le routine della libreria. Alcune macchine non hanno affatto unità a virgola mobile.

Per quanto riguarda alcuni tipi di dati, non è chiaro. Se una macchina ha il supporto per indirizzare la memoria usando un registro come indirizzo di base e un altro come spostamento in scala, significa che gli array sono un tipo di dati direttamente supportato?

Inoltre, parlando di virgola mobile, c'è la standardizzazione: virgola mobile IEEE 754. Perché il tuo compilatore C ha un doubleche concorda con il formato a virgola mobile supportato dal processore non è solo perché i due sono stati fatti per concordare, ma perché esiste uno standard indipendente per quella rappresentazione.


2

Cose come

  • Elenchi Utilizzati in quasi tutti i linguaggi funzionali.

  • Eccezioni .

  • Array associativi (mappe) - inclusi ad esempio in PHP e Perl.

  • Raccolta dei rifiuti .

  • Tipi di dati / strutture di controllo inclusi in molte lingue, ma non direttamente supportati dalla CPU.


2

Supportato direttamente dovrebbe essere inteso come mappatura efficiente al set di istruzioni del processore.

  • Il supporto diretto per i tipi interi è la regola, eccetto per le dimensioni lunghe (possono richiedere routine aritmetiche estese) e corte (possono richiedere il mascheramento).

  • Il supporto diretto per i tipi a virgola mobile richiede la disponibilità di una FPU.

  • Il supporto diretto per i campi di bit è eccezionale.

  • Le strutture e gli array richiedono il calcolo dell'indirizzo, supportato direttamente in una certa misura.

  • I puntatori sono sempre supportati direttamente tramite indirizzamento indiretto.

  • goto / if / while / for / do sono direttamente supportati da rami incondizionati / condizionali.

  • switch può essere supportato direttamente quando si applica una tabella di salto.

  • Le chiamate di funzione sono direttamente supportate tramite le funzionalità dello stack.

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.