¿Por qué tenemos que mencionar el tipo de datos de la variable en C

19

Por lo general, en C, tenemos que decirle a la computadora el tipo de datos en la declaración de variables. Por ejemplo, en el siguiente programa, quiero imprimir la suma de dos números de coma flotante X e Y.

#include<stdio.h>
main()
{
  float X=5.2;
  float Y=5.1;
  float Z;
  Z=Y+X;
  printf("%f",Z);

}

Tuve que decirle al compilador el tipo de variable X.

  • ¿No puede el compilador determinar el tipo de Xpor sí mismo?

Sí, puede hacerlo si hago esto:

#define X 5.2

Ahora puedo escribir mi programa sin decirle al compilador el tipo de Xcomo:

#include<stdio.h>
#define X 5.2
main()
{
  float Y=5.1;
  float Z;
  Z=Y+X;
  printf("%f",Z);

}  

Entonces, vemos que el lenguaje C tiene algún tipo de característica, mediante la cual puede determinar el tipo de datos por sí mismo. En mi caso se determinó que Xes de tipo float.

  • ¿Por qué tenemos que mencionar el tipo de datos cuando declaramos algo en main ()? ¿Por qué el compilador no puede determinar el tipo de datos de una variable por sí solo main()como lo hace en #define?
usuario106313
fuente
14
En realidad, esos dos programas no son equivalentes, ya que pueden dar resultados ligeramente diferentes. 5.2es un double, entonces el primer programa redondea los literales dobles a floatprecisión, luego los agrega como flotantes, mientras que el segundo redondea la representación doble de 5.1 doubley lo agrega al doublevalor 5.2 usando la doublesuma, luego redondea el resultado de ese cálculo a floatprecisión . Debido a que el redondeo ocurre en diferentes lugares, el resultado puede disminuir. Este es solo un ejemplo para los tipos de variables que afectan el comportamiento de un programa idéntico.
12
Cuando lo hace #define X 5.2, Xno es una variable, sino una constante, por lo que literalmente se reemplaza por preprocesador con 5.2cualquier lugar que haya mencionado X. No puedes reasignar X.
scriptin
16
Solo como una nota: esto es una bendición y una maldición. Por un lado, debe escribir algunos caracteres cuando el compilador realmente podría haberlo hecho por usted (en autorealidad, C ++ hace lo que quiere). Por otro lado, si crees que sabes lo que está haciendo tu código y escribiste algo más, la escritura estática como esta detectará un error antes, antes de que se convierta en un gran problema. Cada idioma logra un equilibrio: escritura estática, inferencia de tipos, escritura dinámica. Para algunas tareas, la escritura adicional realmente vale la pena. Para otros, es un desperdicio.
Cort Ammon - Restablece a Mónica el
Aprende Ocaml y / o Haskell ... estarás contento con sus habilidades de inferencia de tipos.
Basile Starynkevitch

Respuestas:

46

Está comparando declaraciones de variables con #defines, lo cual es incorrecto. Con a #define, crea una asignación entre un identificador y un fragmento de código fuente. El preprocesador C sustituirá literalmente cualquier aparición de ese identificador con el fragmento proporcionado. Escritura

#define FOO 40 + 2
int foos = FOO + FOO * FOO;

termina siendo lo mismo para el compilador que escribir

int foos = 40 + 2 + 40 + 2 * 40 + 2;

Piense en ello como copiar y pegar automatizado.

Además, las variables normales se pueden reasignar, mientras que una macro creada con #defineno se puede (aunque se puede reasignar #define). La expresión FOO = 7sería un error del compilador, ya que no podemos asignar "valores": 40 + 2 = 7es ilegal.

Entonces, ¿por qué necesitamos tipos? Aparentemente, algunos idiomas eliminan los tipos, esto es especialmente común en los lenguajes de secuencias de comandos. Sin embargo, generalmente tienen algo llamado "tipeo dinámico" donde las variables no tienen tipos fijos, pero los valores sí. Si bien esto es mucho más flexible, también es menos eficiente. A C le gusta el rendimiento, por lo que tiene un concepto de variables muy simple y eficiente:

Hay un tramo de memoria llamado "pila". Cada variable local corresponde a un área en la pila. Ahora la pregunta es ¿cuántos bytes tiene esta área? En C, cada tipo tiene un tamaño bien definido a través del cual puede consultar sizeof(type). El compilador necesita saber el tipo de cada variable para poder reservar la cantidad correcta de espacio en la pila.

¿Por qué las constantes creadas con #definenecesitan una anotación de tipo? No se almacenan en la pila. En cambio, #definecrea fragmentos reutilizables de código fuente de una manera ligeramente más fácil de mantener que copiar y pegar. Los literales en el código fuente, como el compilador "foo"o los 42.87almacena, ya sea en línea como instrucciones especiales o en una sección de datos separada del binario resultante.

Sin embargo, los literales tienen tipos. Un literal de cadena es a char *. 42es un intpero también se puede usar para tipos más cortos (conversión de reducción). 42.8sería a double. Si tiene un literal y desea que tenga un tipo diferente (por ejemplo, para hacer 42.8un float, o 42un unsigned long int), puede usar sufijos, una letra después del literal que cambia la forma en que el compilador trata ese literal. En nuestro caso, podríamos decir 42.8fo 42ul.

Algunos lenguajes tienen escritura estática como en C, pero las anotaciones de tipo son opcionales. Ejemplos son ML, Haskell, Scala, C #, C ++ 11 y Go. ¿Cómo funciona? ¿Magia? No, esto se llama "inferencia de tipos". En C # y Go, el compilador mira el lado derecho de una tarea y deduce el tipo de eso. Esto es bastante sencillo si el lado derecho es un literal como 42ul. Entonces es obvio cuál debería ser el tipo de variable. Otros lenguajes también tienen algoritmos más complejos que tienen en cuenta cómo se usa una variable. Por ejemplo, si lo hace x/2, xno puede ser una cadena, pero debe tener algún tipo numérico.

amon
fuente
Gracias por explicarlo. Lo que entiendo es que cuando declaramos el tipo de una variable (local o global), en realidad le estamos diciendo al compilador, cuánto espacio debe reservar para esa variable en la pila. Por otro lado, #definetenemos una constante que se convierte directamente en código binario, por mucho tiempo que sea, y se almacena en la memoria tal como está.
user106313
2
@ user31782 - no del todo. Cuando declaras una variable, el tipo le dice al compilador qué propiedades tiene la variable. Una de esas propiedades es el tamaño; Otras propiedades incluyen cómo representa los valores y qué operaciones se pueden realizar en esos valores.
Pete Becker
@PeteBecker Entonces, ¿cómo conoce el compilador estas otras propiedades #define X 5.2?
user106313
1
Eso es porque al pasarle el tipo incorrecto printfinvocaste un comportamiento indefinido. En mi máquina, ese fragmento imprime un valor diferente cada vez, en Ideone se bloquea después de imprimir cero.
Matteo Italia
44
@ user31782 - "Parece que puedo realizar cualquier operación en cualquier tipo de datos" No. X*Yno es válido si Xy Yson punteros, pero está bien si son ints; *Xno es válido si Xes un int, pero está bien si es un puntero.
Pete Becker
4

X en el segundo ejemplo nunca es un flotador. Se llama macro, reemplaza el valor macro definido 'X' en la fuente con el valor. Un artículo legible sobre #define está aquí .

En el caso del código suministrado, antes de la compilación, el preprocesador cambia el código

Z=Y+X;

a

Z=Y+5.2;

y eso es lo que se compila.

Eso significa que también puede reemplazar esos 'valores' con código como

#define X sqrt(Y)

o incluso

#define X Y
James Snell
fuente
3
Simplemente se llama macro, no macro variable. Una macro variadic es una macro que toma un número variable de argumentos, por ejemplo #define FOO(...) { __VA_ARGS__ }.
hvd
2
Mi mal, lo arreglará :)
James Snell
1

La respuesta corta es que C necesita tipos debido a la historia / que representa el hardware.

Historia: C se desarrolló a principios de la década de 1970 y pretendía ser un lenguaje para la programación de sistemas. El código es idealmente rápido y hace el mejor uso de las capacidades del hardware.

Habría sido posible inferir tipos en el momento de la compilación, pero los tiempos de compilación ya lentos habrían aumentado (consulte la caricatura de 'compilación' de XKCD. Esto solía aplicarse al 'hola mundo' durante al menos 10 años después de la publicación de C ). Inferir tipos en tiempo de ejecución no habría encajado con los objetivos de la programación de sistemas. La inferencia de tiempo de ejecución requiere una biblioteca de tiempo de ejecución adicional. C llegó mucho antes de la primera PC. Que tenía 256 RAM. No Gigabytes o Megabytes sino Kilobytes.

En su ejemplo, si omite los tipos

   X=5.2;
   Y=5.1;

   Z=Y+X;

Entonces, el compilador pudo haber deducido felizmente que X e Y son flotantes e hizo Z igual. De hecho, un compilador moderno también resolvería que X e Y no son necesarios y solo establecería Z en 10.3.

Suponga que el cálculo está incrustado dentro de una función. El escritor de funciones podría querer usar su conocimiento del hardware o el problema que se está resolviendo.

¿Sería un doble más apropiado que un flotador? Toma más memoria y es más lento, pero la precisión del resultado sería mayor.

Quizás el valor de retorno de la función podría ser int (o largo) porque los decimales no eran importantes, aunque la conversión de float a int no está exenta de costo.

El valor de retorno también se puede hacer doble garantizando que float + float no se desborde.

Todas estas preguntas parecen inútiles para la gran mayoría del código escrito hoy, pero fueron vitales cuando se produjo C.

itj
fuente
1
esto no explica por qué por ejemplo, declaraciones de tipo no se hicieron opcional, permitiendo programador para elegir ya sea para declarar explícitamente o se basan en compilador inferir
mosquito
1
De hecho, no @gnat. He modificado el texto, pero no tenía sentido hacerlo en ese momento. El dominio C fue diseñado para realmente querer decidir almacenar 17 en 1 byte, o 2 bytes o 4 bytes o como una cadena o como 5 bits dentro de una palabra.
itj
0

C no tiene inferencia de tipos (así se llama cuando un compilador adivina el tipo de una variable para usted) porque es antigua. Fue desarrollado a principios de los años 70.

Muchos idiomas más nuevos tienen sistemas que le permiten usar variables sin especificar su tipo (ruby, javascript, python, etc.)

Tristan Burnside
fuente
12
Ninguno de los lenguajes que mencionó (Ruby, JS, Python) tiene inferencia de tipos como una característica del lenguaje, aunque las implementaciones pueden usarlo para aumentar la eficiencia. En cambio, usan la escritura dinámica donde los valores tienen tipos, pero las variables u otras expresiones no.
amon
2
JS no permitir que omite el tipo - es sólo no permite que declare que sea. Utiliza la tipificación dinámica, donde los valores tienen tipos (por ejemplo, truees boolean), no variables (por ejemplo, var xpueden contener valores de cualquier tipo). Además, la inferencia de tipos para casos tan simples como los cuestionados probablemente se haya conocido una década antes de que se lanzara C.
scriptin
2
Eso no hace que la declaración sea falsa (para forzar algo, también debe permitirlo). La inferencia de tipos existente no cambia el hecho de que el sistema de tipos de C es el resultado de su contexto histórico (en oposición a un razonamiento filosófico o una limitación técnica específicamente declarada)
Tristan Burnside
2
Teniendo en cuenta que ML, que es casi tan viejo como C, tiene inferencia de tipo, "es viejo" no es una buena explicación. El contexto en el que se usó y desarrolló C (máquinas pequeñas que exigían una huella muy pequeña para el compilador) parece más probable. No tengo idea de por qué mencionaría los lenguajes de escritura dinámicos en lugar de solo algunos ejemplos de lenguajes con inferencia de tipos: Haskell, ML, heck C # lo tiene, ya no es una característica oscura.
Voo
2
@BradS. Fortran no es un buen ejemplo porque la primera letra del nombre de la variable es una declaración de tipo, a menos que utilice, implicit noneen cuyo caso debe declarar un tipo.
dmckee