¿Cómo son los tipos de datos C "compatibles directamente con la mayoría de las computadoras"?

114

Estoy leyendo K & R's “El lenguaje de programación C” de y encontré esta declaración [Introducción, p. 3]:

Porque los tipos de datos y las estructuras de control proporcionados por C son compatibles directamente con la mayoría de las computadoras , la biblioteca en tiempo de ejecución necesaria para implementar programas autónomos es pequeña.

¿Qué significa la declaración en negrita? ¿Existe un ejemplo de un tipo de datos o una estructura de control que no sea compatible directamente con una computadora?

gwg
fuente
1
En estos días, el lenguaje C admite aritmética compleja, pero originalmente no lo hacía porque las computadoras no admiten directamente números complejos como tipos de datos.
Jonathan Leffler
12
En realidad, históricamente fue al revés: C fue diseñado a partir de las operaciones y tipos de hardware disponibles en ese momento.
Basile Starynkevitch
2
La mayoría de las computadoras no tienen soporte de hardware directo para flotantes decimales
PlasmaHH
3
@MSalters: Estaba tratando de dar pistas en alguna dirección para la pregunta de "¿Existe un ejemplo de un tipo de datos o una estructura de control que no sea compatible directamente con una computadora?" que no interpreté como limitado a K&R
PlasmaHH
11
¿Cómo no es esto un duplicado más de 6 años después del lanzamiento de Stack Overflow?
Peter Mortensen

Respuestas:

143

Sí, hay tipos de datos que no se admiten directamente.

En muchos sistemas integrados, no existe una unidad de punto flotante de hardware. Entonces, cuando escribe un código como este:

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

Se traduce a algo como esto:

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

Luego, el compilador o biblioteca estándar debe proporcionar una implementación de _float_add(), que ocupa memoria en su sistema integrado. Si está contando bytes en un sistema realmente pequeño, esto puede sumar.

Otro ejemplo común son los enteros de 64 bits ( long longen el estándar C desde 1999), que no son compatibles directamente con los sistemas de 32 bits. Los sistemas SPARC antiguos no admitían la multiplicación de enteros, por lo que el tiempo de ejecución tenía que proporcionar la multiplicación. Hay otros ejemplos.

Otros idiomas

En comparación, otros lenguajes tienen primitivas más complicadas.

Por ejemplo, un símbolo Lisp requiere mucho soporte en tiempo de ejecución, como tablas en Lua, cadenas en Python, matrices en Fortran, etcétera. Los tipos equivalentes en C generalmente no forman parte de la biblioteca estándar en absoluto (no hay símbolos o tablas estándar) o son mucho más simples y no requieren mucho soporte en tiempo de ejecución (las matrices en C son básicamente solo punteros, las cadenas terminadas en nulo son casi tan simple).

Estructuras de Control

Una estructura de control notable que falta en C es el manejo de excepciones. La salida no local está limitada a setjmp()y longjmp(), que solo guarda y restaura ciertas partes del estado del procesador. En comparación, el tiempo de ejecución de C ++ tiene que recorrer la pila y llamar a los destructores y controladores de excepciones.

Dietrich Epp
fuente
2
básicamente solo punteros ... más bien, básicamente solo trozos de memoria en bruto. Incluso si eso es puntilloso, y la respuesta es buena de todos modos.
Deduplicador
2
Se podría argumentar que las cadenas terminadas en nulo tienen "soporte de hardware", ya que el terminador de cadena se ajusta a la operación 'saltar si es cero' de la mayoría de los procesadores y, por lo tanto, es ligeramente más rápido que otras posibles implementaciones de cadenas.
Peteris
1
Publiqué mi propia respuesta para expandir sobre cómo C está diseñado para mapear simplemente a asm.
Peter Cordes
1
Por favor, no use la colocación "las matrices son básicamente solo punteros", puede engañar gravemente a un principiante como OP. Algo parecido a "las matrices se implementan directamente utilizando punteros a nivel de hardware" sería mejor en mi opinión.
Croissant paramagnético del
1
@TheParamagneticCroissant: Creo que en este contexto es apropiado ... la claridad viene a costa de la precisión.
Dietrich Epp
37

En realidad, apuesto a que el contenido de esta introducción no ha cambiado mucho desde 1978, cuando Kernighan y Ritchie los escribieron por primera vez en la primera edición del libro, y se refieren a la historia y evolución de C en ese momento más que a la moderna. implementaciones.

Las computadoras son fundamentalmente bancos de memoria y procesadores centrales, y cada procesador opera usando un código de máquina; Parte del diseño de cada procesador es una arquitectura de conjunto de instrucciones, llamada Lenguaje Ensamblador , que mapea uno a uno desde un conjunto de mnemónicos legibles por humanos al código de máquina, que son todos números.

Los autores del lenguaje C, y los lenguajes B y BCPL que lo precedieron inmediatamente, tenían la intención de definir construcciones en el lenguaje que se compilaron en Assembly de la manera más eficiente posible ... de hecho, se vieron obligados a hacerlo por limitaciones en el objetivo. hardware. Como han señalado otras respuestas, esto involucró ramas (GOTO y otro control de flujo en C), movimientos (asignación), operaciones lógicas (& | ^), aritmética básica (sumar, restar, incrementar, disminuir) y direccionamiento de memoria (punteros ). Un buen ejemplo son los operadores de pre / post-incremento y decremento en C, que supuestamente fueron agregados al lenguaje B por Ken Thompson específicamente porque eran capaces de traducir directamente a un solo código de operación una vez compilados.

Esto es lo que los autores quisieron decir cuando dijeron "soportado directamente por la mayoría de las computadoras". No querían decir que otros lenguajes contenían tipos y estructuras que no eran compatibles directamente; querían decir que, mediante el diseño, las construcciones C se traducían más directamente (a veces literalmente directamente) a Ensamblaje.

Esta estrecha relación con el ensamblador subyacente, si bien aún proporciona todos los elementos necesarios para la programación estructurada, es lo que llevó a la adopción temprana de C y lo que lo mantiene como un lenguaje popular hoy en día en entornos donde la eficiencia del código compilado sigue siendo clave.

Para una interesante reseña de la historia del lenguaje, vea El desarrollo del lenguaje C - Dennis Ritchie

John Castleman
fuente
14

La respuesta corta es que la mayoría de las construcciones de lenguaje soportadas por C también son soportadas por el microprocesador de la computadora de destino, por lo tanto, el código C compilado se traduce muy bien y eficientemente al lenguaje ensamblador del microprocesador, lo que resulta en un código más pequeño y una huella más pequeña.

La respuesta más larga requiere un poco de conocimiento del lenguaje ensamblador. En C, una declaración como esta:

int myInt = 10;

se traduciría a algo como esto en ensamblaje:

myInt dw 1
mov myInt,10

Compare esto con algo como C ++:

MyClass myClass;
myClass.set_myInt(10);

El código de lenguaje ensamblador resultante (dependiendo de qué tan grande sea MyClass ()), podría sumar cientos de líneas de lenguaje ensamblador.

Sin realmente crear programas en lenguaje ensamblador, C puro es probablemente el código "más delgado" y "más ajustado" en el que puede hacer un programa.

EDITAR

Dados los comentarios sobre mi respuesta, decidí hacer una prueba, solo por mi propia cordura. Creé un programa llamado "test.c", que se veía así:

#include <stdio.h>

void main()
{
    int myInt=10;

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

Compilé esto hasta el ensamblaje usando gcc. Usé la siguiente línea de comando para compilarlo:

gcc -S -O2 test.c

Aquí está el lenguaje ensamblador resultante:

    .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

Luego creo un archivo llamado "test.cpp" que define una clase y genera lo mismo que "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;
}

Lo compilé de la misma manera, usando este comando:

g++ -O2 -S test.cpp

Aquí está el archivo de ensamblaje resultante:

    .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

Como puede ver claramente, el archivo de ensamblaje resultante es mucho más grande en el archivo C ++ que en el archivo C. Incluso si elimina todas las demás cosas y simplemente compara el "principal" de C con el "principal" de C ++, hay muchas cosas adicionales.

Icemanind
fuente
14
Ese "código C ++" simplemente no es C ++. Y MyClass myClass { 10 }es muy probable que el código real, como en C ++, se compile exactamente en el mismo ensamblado. Los compiladores modernos de C ++ han eliminado la penalización por abstracción. Y como resultado, a menudo pueden vencer a los compiladores de C. Por ejemplo, la penalización por abstracción en C qsortes real, pero C ++ std::sortno tiene penalización por abstracción incluso después de la optimización básica.
MSalters
1
Se puede ver fácilmente utilizando IDA Pro que la mayor parte de C ++ construcciones de compilación a lo mismo que hacerlo manualmente en C, constructores y dtors consiguen entre líneas para objetos triviales, entonces futura optimización se aplica
paulm
7

K&R significa que la mayoría de las expresiones C (significado técnico) se asignan a una o algunas instrucciones de ensamblaje, no a una llamada de función a una biblioteca de soporte. Las excepciones habituales son la división de enteros en arquitecturas sin una instrucción div de hardware o el punto flotante en máquinas sin FPU.

Hay una cita:

C combina la flexibilidad y el poder del lenguaje ensamblador con la facilidad de uso del lenguaje ensamblador.

( Encontrado aquí . Pensé que recordaba una variación diferente, como "la velocidad del lenguaje ensamblador con la conveniencia y expresividad del lenguaje ensamblador".)

long int suele tener el mismo ancho que los registros de la máquina nativa.

Algunos lenguajes de nivel superior definen el ancho exacto de sus tipos de datos, y las implementaciones en todas las máquinas deben funcionar igual. Sin embargo, no C.

Si desea trabajar con entradas de 128 bits en x86-64, o en el caso general BigInteger de tamaño arbitrario, necesita una biblioteca de funciones para ello. Todas las CPU ahora usan el complemento 2s como representación binaria de enteros negativos, pero incluso ese no era el caso cuando se diseñó C. (Es por eso que algunas cosas que darían resultados diferentes en máquinas sin complemento a 2 no están técnicamente definidas en los estándares C.)

Los punteros de C a datos o funciones funcionan de la misma manera que las direcciones de ensamblado.

Si desea referencias contadas por ref, debe hacerlo usted mismo. Si desea funciones miembro virtuales de c ++ que llamen a una función diferente según el tipo de objeto al que apunta su puntero, el compilador de C ++ tiene que generar mucho más que una simple callinstrucción con una dirección fija.

Las cadenas son solo matrices

Fuera de las funciones de la biblioteca, las únicas operaciones de cadena proporcionadas son leer / escribir un carácter. Sin concat, sin subcadena, sin búsqueda. (Las cadenas se almacenan como '\0'matrices terminadas en nulo ( ) de enteros de 8 bits, no puntero + longitud, por lo que para obtener una subcadena tendrías que escribir un nulo en la cadena original).

Las CPU a veces tienen instrucciones diseñadas para ser utilizadas por una función de búsqueda de cadenas, pero aún así procesan un byte por instrucción ejecutada, en un bucle. (o con el prefijo rep x86. Tal vez si C se diseñó en x86, la búsqueda o comparación de cadenas sería una operación nativa, en lugar de una llamada a la función de biblioteca).

Muchas otras respuestas dan ejemplos de cosas que no son compatibles de forma nativa, como el manejo de excepciones, tablas hash, listas. La filosofía de diseño de K&R es la razón por la que C no tiene ninguno de estos de forma nativa.

Peter Cordes
fuente
"K&R significa que la mayoría de las expresiones C (significado técnico) se asignan a una o algunas instrucciones de ensamblaje, no a una llamada de función a una biblioteca de soporte". Esta es una explicación muy intuitiva. Gracias.
gwg
1
Me encontré con el término "idioma von Neumann" ( en.wikipedia.org/wiki/Von_Neumann_programming_languages ). Es EXACTAMENTE lo que es C.
Peter Cordes
1
Ésta es exactamente la razón por la que utilizo C. Pero lo que me ha sorprendido al aprender C es que, al intentar ser eficiente para una amplia gama de hardware, a veces es inepto e ineficiente en la mayoría del hardware moderno. Me refiero, por ejemplo, a ninguna forma útil y confiable de detectar el desbordamiento de enteros en c y la adición de varias palabras usando la bandera de acarreo .
Bosón Z
6

El lenguaje ensamblador de un proceso generalmente se ocupa de saltar (ir a), declaraciones, declaraciones de movimiento, artríticos binarios (XOR, NAND, AND OR, etc.), campos de memoria (o direcciones). Categoriza la memoria en dos tipos, instrucción y datos. Eso es todo lo que es un lenguaje ensamblador (estoy seguro de que los programadores ensambladores argumentarán que hay más que eso, pero se reduce a esto en general). C se parece mucho a esta simplicidad.

C es ensamblar lo que el álgebra es para la aritmética.

C encapsula los conceptos básicos del ensamblaje (el lenguaje del procesador). Es probablemente una afirmación más cierta que "Porque los tipos de datos y las estructuras de control proporcionados por C son compatibles directamente con la mayoría de las computadoras".

terario
fuente
5

Tenga cuidado con las comparaciones engañosas

  1. La declaración se basa en la noción de una "biblioteca en tiempo de ejecución" , que en su mayoría ha pasado de moda desde entonces, al menos para los lenguajes convencionales de alto nivel. (Sigue siendo relevante para los sistemas embebidos más pequeños). El tiempo de ejecución es el soporte mínimo que un programa en ese lenguaje requiere para ejecutarse cuando solo usa construcciones integradas en el lenguaje (en lugar de llamar explícitamente a una función proporcionada por una biblioteca) .
  2. Por el contrario, los lenguajes modernos tienden a no discriminar entre el tiempo de ejecución y la biblioteca estándar. , siendo esta última a menudo bastante extensa.
  3. En el momento del libro de K&R, C ni siquiera tenía una biblioteca estándar . Más bien, las bibliotecas C disponibles diferían bastante entre los diferentes sabores de Unix.
  4. Para comprender la declaración, no debe comparar con lenguajes con una biblioteca estándar (como Lua y Python mencionados en otras respuestas), sino con lenguajes con construcciones más integradas (como LISP antiguo y FORTRAN antiguo mencionado en otras respuestas). Otros ejemplos serían BASIC (interactivo, como LISP) o PASCAL (compilado, como FORTRAN) que tienen (entre otras cosas) características de entrada / salida integradas en el lenguaje mismo.
  5. Por el contrario, no existe una forma estándar de obtener los resultados del cálculo de un programa en C que utiliza solo el tiempo de ejecución, no una biblioteca.
Lutz Prechelt
fuente
Por otro lado, la mayoría de los lenguajes modernos se ejecutan dentro de entornos de ejecución dedicados que brindan servicios como la recolección de basura.
Nate CK
5

¿Existe un ejemplo de un tipo de datos o una estructura de control que no sea compatible directamente con una computadora?

Todos los tipos de datos fundamentales y sus operaciones en el lenguaje C se pueden implementar mediante una o unas pocas instrucciones en lenguaje de máquina sin bucles; son compatibles directamente con la CPU (prácticamente todas).

Varios tipos de datos populares y sus operaciones requieren docenas de instrucciones en lenguaje de máquina, o requieren la iteración de algún ciclo de tiempo de ejecución, o ambos.

Muchos lenguajes tienen una sintaxis abreviada especial para tales tipos y sus operaciones; el uso de tales tipos de datos en C generalmente requiere escribir mucho más código.

Dichos tipos de datos y operaciones incluyen:

  • manipulación de cadenas de texto de longitud arbitraria: concatenación, subcadena, asignación de una nueva cadena a una variable inicializada con alguna otra cadena, etc. ('s = "¡Hola mundo!"; s = (s + s) [2: -2] 'en Python)
  • conjuntos
  • objetos con destructores virtuales anidados, como en C ++ y cualquier otro lenguaje de programación orientado a objetos
  • Multiplicación y división de matrices 2D; resolución de sistemas lineales ("C = B / A; x = A \ b" en MATLAB y muchos lenguajes de programación de matrices)
  • expresiones regulares
  • matrices de longitud variable, en particular, agregar un elemento al final de la matriz, lo que (a veces) requiere asignar más memoria.
  • leer el valor de las variables que cambian de tipo en tiempo de ejecución; a veces es un flotante, otras veces es una cadena
  • matrices asociativas (a menudo llamadas "mapas" o "diccionarios")
  • liza
  • ratios ("(+ 1/3 2/7)" da "13/21" en Lisp )
  • aritmética de precisión arbitraria (a menudo llamada "bignums")
  • convertir datos en una representación imprimible (el método ".tostring" en JavaScript)
  • saturación de números de punto fijo (a menudo se usa en programas C integrados)
  • evaluar una cadena tecleada en tiempo de ejecución como si fuera una expresión ("eval ()" en muchos lenguajes de programación).

Todas estas operaciones requieren docenas de instrucciones en lenguaje de máquina o requieren iterar algún ciclo de tiempo de ejecución en casi todos los procesadores.

Algunas estructuras de control populares que también requieren docenas de instrucciones en lenguaje de máquina o bucles incluyen:

  • cierres
  • continuaciones
  • excepciones
  • evaluación perezosa

Ya sea que esté escrito en C o en algún otro lenguaje, cuando un programa manipula tales tipos de datos, la CPU debe finalmente ejecutar las instrucciones necesarias para manipular esos tipos de datos. A menudo, esas instrucciones se encuentran en una "biblioteca". Cada lenguaje de programación, incluso C, tiene una "biblioteca en tiempo de ejecución" para cada plataforma que se incluye de forma predeterminada en cada ejecutable.

La mayoría de las personas que escriben compiladores colocan las instrucciones para manipular todos los tipos de datos que están "integrados en el lenguaje" en su biblioteca de tiempo de ejecución. Debido a que C no tiene ninguno de los tipos de datos y operaciones y estructuras de control anteriores integrados en el lenguaje, ninguno de ellos está incluido en la biblioteca de tiempo de ejecución de C, lo que hace que la biblioteca de tiempo de ejecución de C sea más pequeña que la de ejecución. biblioteca de tiempo de otros lenguajes de programación que tienen más de las cosas anteriores integradas en el lenguaje.

Cuando un programador quiere que un programa, en C o en cualquier otro lenguaje de su elección, manipule otros tipos de datos que no están "integrados en el lenguaje", ese programador generalmente le dice al compilador que incluya bibliotecas adicionales con ese programa, o algunas veces (para "evitar dependencias") escribe otra implementación de esas operaciones directamente en el programa.

David Cary
fuente
Si su implementación de Lisp se evalúa (+ 1/3 2/7) como 3/21, creo que debe tener una implementación particularmente creativa ...
RobertB
4

¿Cuáles son los tipos de datos integrados C? Son cosas como int, char, * int, float, matrices, etc ... Estos tipos de datos son entendidos por la CPU. La CPU sabe cómo trabajar con matrices, cómo desreferenciar punteros y cómo realizar operaciones aritméticas en punteros, enteros y números de coma flotante.

Pero cuando pasa a lenguajes de programación de nivel superior, ha incorporado tipos de datos abstractos y construcciones más complejas. Por ejemplo, observe la amplia gama de clases integradas en el lenguaje de programación C ++. La CPU no comprende clases, objetos o tipos de datos abstractos, por lo que el tiempo de ejecución de C ++ cierra la brecha entre la CPU y el lenguaje. Estos son ejemplos de tipos de datos que no son compatibles directamente con la mayoría de las computadoras.

hhafez
fuente
2
x86 sabe trabajar con algunas matrices, pero no con todas. Para tamaños de elementos grandes o inusuales, deberá realizar aritmética de números enteros para convertir un índice de matriz en un desplazamiento de puntero. Y en otras plataformas, esto siempre es necesario. Y la idea de que la CPU no entienda las clases de C ++ es ridícula. Eso es solo compensaciones de puntero, como estructuras C. No necesitas un tiempo de ejecución para eso.
MSalters
@MSalters sí, pero los métodos reales de las clases de biblioteca estándar como iostreams, etc.son funciones de biblioteca en lugar de ser compatibles directamente con el compilador. Sin embargo, los lenguajes de nivel superior con los que probablemente lo estaban comparando no eran C ++, sino lenguajes contemporáneos como FORTRAN y PL / I.
Random832
1
Las clases de C ++ con funciones miembro virtuales se traducen en mucho más que un desplazamiento en una estructura.
Peter Cordes
4

Depende de la computadora. El PDP-11, donde se inventó C, longtenía un soporte deficiente (había un módulo adicional opcional que se podía comprar y que admitía algunas operaciones de 32 bits, pero no todas). Lo mismo es cierto en varios grados en cualquier sistema de 16 bits, incluido el IBM PC original. Y lo mismo ocurre con las operaciones de 64 bits en máquinas de 32 bits o en programas de 32 bits, aunque el lenguaje C en el momento del libro de K&R no tenía ninguna operación de 64 bits. Y, por supuesto, ha habido muchos sistemas a lo largo de los años 80 y 90 [incluidos los procesadores 386 y algunos 486], e incluso algunos sistemas integrados en la actualidad, que no admitían directamente la aritmética de punto flotante ( floato double).

Para un ejemplo más exótico, algunas arquitecturas de computadora solo admiten punteros "orientados a palabras" (apuntando a un número entero de dos o cuatro bytes en la memoria), y los punteros de bytes ( char *o void *) debían implementarse agregando un campo de desplazamiento adicional. Esta pregunta entra en algunos detalles sobre tales sistemas.

Las funciones de la "biblioteca en tiempo de ejecución" a las que se refiere no son las que verá en el manual, sino funciones como estas, en una biblioteca en tiempo de ejecución de un compilador moderno , que se utilizan para implementar las operaciones de tipo básico que no son compatibles con la máquina. . La biblioteca en tiempo de ejecución a la que se referían los propios K&R se puede encontrar en el sitio web de The Unix Heritage Society ; puede ver funciones como ldiv(distintas de la función C del mismo nombre, que no existía en ese momento) que se usa para implementar la división de Valores de 32 bits, que el PDP-11 no admitía ni siquiera con el complemento, y csv(y crettambién en csv.c) que guardan y restauran registros en la pila para administrar llamadas y devoluciones de funciones.

Es probable que también se refirieran a su elección de no admitir muchos tipos de datos que no son compatibles directamente con la máquina subyacente, a diferencia de otros lenguajes contemporáneos como FORTRAN, que tenían una semántica de matriz que no se asignaba tan bien al soporte de puntero subyacente de la CPU como Matrices de C. El hecho de que las matrices C siempre tienen un índice cero y siempre tienen un tamaño conocido en todos los rangos, pero el primero significa que no hay necesidad de almacenar los rangos de índice o tamaños de las matrices, y no es necesario tener funciones de biblioteca en tiempo de ejecución para acceder a ellas. el compilador puede simplemente codificar la aritmética de puntero necesaria.

Aleatorio832
fuente
3

La declaración simplemente significa que las estructuras de control y datos en C están orientadas a la máquina.

Hay dos aspectos a considerar aquí. Una es que el lenguaje C tiene una definición (estándar ISO) que permite la latitud en cómo se definen los tipos de datos. Esto significa que las implementaciones del lenguaje C se adaptan a la máquina . Los tipos de datos de un compilador de C coinciden con lo que está disponible en la máquina a la que apunta el compilador, porque el lenguaje tiene latitud para eso. Si una máquina tiene un tamaño de palabra inusual, como 36 bits, entonces el tipo into longpuede ajustarse a eso. Los programas que asumen que intson exactamente 32 bits se romperán.

En segundo lugar, debido a estos problemas de portabilidad, existe un segundo efecto. En cierto modo, la declaración de K&R se ha convertido en una especie de profecía autocumplida , o quizás al revés. Es decir, los implementadores de nuevos procesadores son conscientes de la gran necesidad de soportar compiladores de C y saben que existe una gran cantidad de código C que asume que "cada procesador parece un 80386". Las arquitecturas se diseñan teniendo en cuenta C: y no solo C, sino también los conceptos erróneos comunes sobre la portabilidad de C. Simplemente ya no puede introducir una máquina con bytes de 9 bits o lo que sea para uso general. Programas que asumen que el tipochartiene exactamente 8 bits de ancho se romperá. Solo algunos programas escritos por expertos en portabilidad continuarán funcionando: probablemente no lo suficiente como para armar un sistema completo con una cadena de herramientas, kernel, espacio de usuario y aplicaciones útiles, con un esfuerzo razonable. En otras palabras, los tipos C se parecen a lo que está disponible en el hardware porque el hardware se hizo para parecerse a algún otro hardware para el que se escribieron muchos programas C no portátiles.

¿Existe un ejemplo de un tipo de datos o una estructura de control que no sea compatible directamente con una computadora?

Tipos de datos que no se admiten directamente en muchos lenguajes de máquina: entero de precisión múltiple; lista enlazada; tabla de picadillo; cadena de caracteres.

Estructuras de control no soportadas directamente en la mayoría de los lenguajes de máquina: continuación de primera clase; corutina / hilo; generador; manejo de excepciones.

Todos estos requieren un código de soporte de tiempo de ejecución considerable creado utilizando numerosas instrucciones de propósito general y tipos de datos más elementales.

C tiene algunos tipos de datos estándar que no son compatibles con algunas máquinas. Desde C99, C tiene números complejos. Están hechos de dos valores de punto flotante y diseñados para trabajar con rutinas de biblioteca. Algunas máquinas no tienen ninguna unidad de punto flotante.

Con respecto a algunos tipos de datos, no está claro. Si una máquina tiene soporte para direccionar la memoria usando un registro como dirección base y otro como un desplazamiento escalado, ¿significa eso que las matrices son un tipo de datos directamente soportado?

Además, hablando de punto flotante, existe una estandarización: IEEE 754 de punto flotante. La doublerazón por la que su compilador de C tiene un formato que concuerda con el formato de punto flotante admitido por el procesador no es solo porque los dos se hicieron para estar de acuerdo, sino porque existe un estándar independiente para esa representación.

Kaz
fuente
2

Cosas como

  • Listas Se utilizan en casi todos los lenguajes funcionales.

  • Excepciones .

  • Matrices asociativas (mapas): incluidas, por ejemplo, en PHP y Perl.

  • Recolección de basura .

  • Tipos de datos / estructuras de control incluidos en muchos lenguajes, pero no soportados directamente por la CPU.

MTilsted
fuente
2

Soportado directamente debe entenderse como mapeo eficientemente al conjunto de instrucciones del procesador.

  • El soporte directo para tipos enteros es la regla, excepto para los tamaños largos (puede requerir rutinas aritméticas extendidas) y cortos (puede requerir enmascaramiento).

  • El soporte directo para tipos de punto flotante requiere que haya una FPU disponible.

  • El soporte directo para campos de bits es excepcional.

  • Las estructuras y las matrices requieren el cálculo de direcciones, con soporte directo hasta cierto punto.

  • Los punteros siempre se admiten directamente mediante direccionamiento indirecto.

  • goto / if / while / for / do son directamente compatibles con ramas incondicionales / condicionales.

  • El interruptor se puede admitir directamente cuando se aplica una tabla de salto.

  • Las llamadas a funciones se admiten directamente mediante las características de la pila.

Yves Daoust
fuente