Por que el lenguaje de programación C todavía gobierna

Ninguna tecnología dura 50 años a menos que haga su trabajo mejor que cualquier otra cosa, especialmente una tecnología informática. El lenguaje de programación C ha estado vivo y coleando desde 1972, y todavía reina como uno de los bloques de construcción fundamentales de nuestro mundo definido por software.

Pero a veces una tecnología se queda porque la gente simplemente no ha podido reemplazarla. En las últimas décadas, han aparecido docenas de otros lenguajes, algunos diseñados explícitamente para desafiar el dominio de C, otros han desmenuzado a C desde un lado como un subproducto de su popularidad.

No es difícil argumentar que C necesita ser reemplazado. La investigación del lenguaje de programación y las prácticas de desarrollo de software apuntan a que hay formas mucho mejores de hacer las cosas que las de C. Pero C persiste de todos modos, con décadas de investigación y desarrollo detrás. Pocos otros lenguajes pueden superarlo en rendimiento, compatibilidad completa o ubicuidad. Aún así, vale la pena ver cómo C se compara con la competencia de idiomas de renombre en 2018.

C frente a C ++

Naturalmente, C se compara más comúnmente con C ++, el lenguaje que, como su propio nombre indica, se creó como una extensión de C. Las diferencias entre C ++ y C podrían caracterizarse como extensas o  excesivas , según a quién pregunte.

Aunque sigue siendo similar a C en su sintaxis y enfoque, C ++ proporciona muchas características realmente útiles que no están disponibles de forma nativa en C: espacios de nombres, plantillas, excepciones, administración automática de memoria, etc. Los proyectos que exigen un rendimiento de primer nivel (bases de datos, sistemas de aprendizaje automático) se escriben con frecuencia en C ++ utilizando esas funciones para sacar cada gota de rendimiento del sistema.

Además, C ++ continúa expandiéndose de manera mucho más agresiva que C. El próximo C ++ 20 trae aún más a la mesa, incluidos módulos, corrutinas, una biblioteca de sincronización y conceptos, que hacen que las plantillas sean más fáciles de usar. La última revisión del estándar C agrega poco y se enfoca en mantener la compatibilidad con versiones anteriores.

La cosa es que todas las ventajas de C ++ también pueden funcionar como desventajas. Grandes. Cuantas más funciones de C ++ utilice, más complejidad introducirá y más difícil será dominar los resultados. Los desarrolladores que se limitan a un subconjunto de C ++ pueden evitar muchos de sus peores escollos y excesos. Pero algunas tiendas quieren protegerse de la complejidad de C ++ por completo. Seguir con C obliga a los desarrolladores a limitarse a ese subconjunto. El equipo de desarrollo del kernel de Linux, por ejemplo, evita C ++.

Elegir C en lugar de C ++ es una forma para que usted, y cualquier desarrollador que mantenga el código después de usted, evite tener que enredarse con los excesos de C ++, adoptando un minimalismo forzado. Por supuesto, C ++ tiene un amplio conjunto de características de alto nivel por una buena razón. Pero si el minimalismo se adapta mejor a los proyectos actuales y futuros, y a los equipos de proyectos , entonces C tiene más sentido.

C contra Java

Después de décadas, Java sigue siendo un elemento básico del desarrollo de software empresarial y, en general, un elemento básico del desarrollo. Muchos de los proyectos de software empresarial más importantes se escribieron en Java, incluida la gran mayoría de los proyectos de Apache Software Foundation, y Java sigue siendo un lenguaje viable para desarrollar nuevos proyectos con requisitos de nivel empresarial.

La sintaxis de Java toma prestado mucho de C y C ++. Sin embargo, a diferencia de C, Java no se compila de forma predeterminada en código nativo. En su lugar, el entorno de ejecución de Java, la JVM, JIT (justo a tiempo) compila código Java para ejecutarse en el entorno de destino. En las circunstancias adecuadas, el código Java JITted puede acercarse o incluso superar el rendimiento de C.

La filosofía de "escribir una vez, ejecutar en cualquier lugar" detrás de Java también permite que los programas Java se ejecuten con relativamente pocos ajustes para una arquitectura de destino. Por el contrario, aunque C se ha adaptado a una gran cantidad de arquitecturas, es posible que cualquier programa C dado aún necesite personalización para ejecutarse correctamente en, digamos, Windows frente a Linux.

Esta combinación de portabilidad y rendimiento sólido, junto con un ecosistema masivo de bibliotecas de software y marcos, hacen de Java un lenguaje y un tiempo de ejecución de referencia para crear aplicaciones empresariales.

Donde Java no llega a C es un área en la que Java nunca tuvo la intención de competir: corriendo cerca del metal o trabajando directamente con hardware. El código C se compila en código máquina, que el proceso ejecuta directamente. Java se compila en código de bytes, que es un código intermedio que luego el intérprete de JVM convierte a código de máquina. Además, aunque la administración automática de memoria de Java es una bendición en la mayoría de las circunstancias, C es más adecuado para programas que deben hacer un uso óptimo de recursos de memoria limitados.

Dicho esto, hay algunas áreas en las que Java puede acercarse a C en términos de velocidad. El motor JIT de JVM optimiza las rutinas en tiempo de ejecución según el comportamiento del programa, lo que permite muchas clases de optimización que no son posibles con C compilada con anticipación. Y mientras que el tiempo de ejecución de Java automatiza la gestión de la memoria, algunas aplicaciones más nuevas solucionan eso. Por ejemplo, Apache Spark optimiza el procesamiento en memoria en parte mediante el uso de código de administración de memoria personalizado que elude la JVM.

C frente a C # y .Net

Casi dos décadas después de su introducción, C # y .Net Framework siguen siendo partes importantes del mundo del software empresarial. Se ha dicho que C # y .Net fueron la respuesta de Microsoft a Java, un sistema compilador de código administrado y un tiempo de ejecución universal, y muchas comparaciones entre C y Java también son válidas para C y C # /. Net.

Como Java (y hasta cierto punto Python), .Net ofrece portabilidad a través de una variedad de plataformas y un vasto ecosistema de software integrado. Estas no son pequeñas ventajas dado el gran desarrollo orientado a las empresas que tiene lugar en el mundo .Net. Cuando desarrolla un programa en C #, o cualquier otro lenguaje .Net, puede aprovechar un universo de herramientas y bibliotecas escritas para el tiempo de ejecución .Net. 

Otra ventaja de .NET similar a Java es la optimización JIT. Los programas C # y .Net se pueden compilar con anticipación según C, pero principalmente son compilados justo a tiempo por el tiempo de ejecución .Net y optimizados con información del tiempo de ejecución. La compilación JIT permite todo tipo de optimizaciones in situ para un programa .Net en ejecución que no se puede realizar en C.

Al igual que C, C # y .Net proporcionan varios mecanismos para acceder a la memoria directamente. El montón, la pila y la memoria del sistema no administrada son accesibles a través de objetos y API .Net. Y los desarrolladores pueden usar el unsafemodo en .Net para lograr un rendimiento aún mayor.

Sin embargo, nada de esto es gratis. Los objetos administrados y los unsafeobjetos no se pueden intercambiar arbitrariamente, y la ordenación entre ellos tiene un costo de rendimiento. Por lo tanto, maximizar el rendimiento de las aplicaciones .Net significa mantener al mínimo el movimiento entre objetos administrados y no administrados.

Cuando no puede pagar la penalización por la memoria administrada frente a la no administrada, o cuando el tiempo de ejecución .Net es una mala elección para el entorno de destino (por ejemplo, espacio del kernel) o puede no estar disponible en absoluto, entonces C es lo que necesita. necesitar. Y a diferencia de C # y .Net, C desbloquea el acceso directo a la memoria de forma predeterminada. 

C vs. Go

La sintaxis de Go debe mucho a C: llaves como delimitadores, sentencias terminadas con punto y coma, etc. Los desarrolladores que dominan C normalmente pueden pasar directamente a Go sin mucha dificultad, incluso teniendo en cuenta las nuevas funciones de Go, como los espacios de nombres y la gestión de paquetes.

El código legible fue uno de los objetivos de diseño rectores de Go: facilitar a los desarrolladores la puesta al día con cualquier proyecto de Go y dominar el código base en poco tiempo. Las bases de código C pueden ser difíciles de asimilar, ya que tienden a convertirse en un nido de macros y son #ifdefespecíficas tanto para un proyecto como para un equipo determinado. La sintaxis de Go y sus herramientas de gestión de proyectos y formato de código integradas están destinadas a mantener a raya ese tipo de problemas institucionales.

Go también presenta extras como goroutines y canales, herramientas a nivel de lenguaje para manejar la concurrencia y el paso de mensajes entre componentes. C requeriría que tales cosas se enrollen a mano o se suministren a través de una biblioteca externa, pero Go las proporciona de inmediato, lo que facilita la creación de software que las necesite.

Donde Go difiere más de C bajo el capó es en la administración de memoria. Los objetos Go se administran automáticamente y se recolectan basura de forma predeterminada. Para la mayoría de los trabajos de programación, esto es tremendamente conveniente. Pero también significa que cualquier programa que requiera un manejo determinista de la memoria será más difícil de escribir.

Go incluye el unsafepaquete para eludir algunas de las características de seguridad de manejo de tipos de Go, como leer y escribir memoria arbitraria con un Pointertipo. Pero unsafeviene con una advertencia de que los programas escritos con él "pueden no ser portátiles y no están protegidos por las pautas de compatibilidad de Go 1".

Go es ideal para crear programas como utilidades de línea de comandos y servicios de red, porque rara vez necesitan manipulaciones tan precisas. Pero los controladores de dispositivos de bajo nivel, los componentes del sistema operativo del espacio del kernel y otras tareas que exigen un control exacto sobre el diseño y la administración de la memoria se crean mejor en C.

C contra óxido

De alguna manera, Rust es una respuesta a los enigmas de administración de memoria creados por C y C ++, y también a muchas otras deficiencias de estos lenguajes. Rust se compila en código de máquina nativo, por lo que se considera a la par con C en cuanto a rendimiento. La seguridad de la memoria por defecto, sin embargo, es el principal atractivo de Rust.

La sintaxis y las reglas de compilación de Rust ayudan a los desarrolladores a evitar errores comunes en la administración de memoria. Si un programa tiene un problema de administración de memoria que cruza la sintaxis de Rust, simplemente no se compilará. Los recién llegados al lenguaje, especialmente de un lenguaje como C que proporciona mucho espacio para tales errores, pasan la primera fase de su educación en Rust aprendiendo cómo apaciguar al compilador. Pero los defensores de Rust argumentan que este dolor a corto plazo tiene una recompensa a largo plazo: un código más seguro que no sacrifica la velocidad.

El óxido también mejora en C con sus herramientas. La gestión de proyectos y componentes forman parte de la cadena de herramientas que se proporciona con Rust de forma predeterminada, al igual que con Go. Existe una forma predeterminada y recomendada de administrar paquetes, organizar carpetas de proyectos y manejar muchas otras cosas que en C son ad-hoc en el mejor de los casos, con cada proyecto y equipo manejándolos de manera diferente.

Aún así, lo que se promociona como una ventaja en Rust puede no parecerlo para un desarrollador de C. Las funciones de seguridad en tiempo de compilación de Rust no se pueden desactivar, por lo que incluso el programa más trivial de Rust debe cumplir con las normas de seguridad de la memoria de Rust. C puede ser menos seguro de forma predeterminada, pero es mucho más flexible y tolerante cuando es necesario.

Otro posible inconveniente es el tamaño del lenguaje Rust. C tiene relativamente pocas funciones, incluso cuando se tiene en cuenta la biblioteca estándar. El conjunto de características de Rust se está expandiendo y sigue creciendo. Al igual que con C ++, el conjunto de características de Rust más grande significa más potencia, pero también más complejidad. C es un lenguaje más pequeño, pero mucho más fácil de modelar mentalmente, por lo que quizás se adapte mejor a proyectos en los que Rust sería excesivo.

C contra Python

En estos días, cuando se habla de desarrollo de software, Python siempre parece entrar en la conversación. Después de todo, Python es "el segundo mejor lenguaje para todo" y, sin duda, uno de los más versátiles, con miles de bibliotecas de terceros disponibles.

Lo que Python enfatiza, y donde más se diferencia de C, es favorecer la velocidad de desarrollo sobre la velocidad de ejecución. Un programa que puede tardar una hora en ensamblarse en otro lenguaje, como C, puede ensamblarse en Python en minutos. Por otro lado, ese programa puede tardar unos segundos en ejecutarse en C, pero un minuto en ejecutarse en Python. (Una buena regla general: los programas de Python generalmente se ejecutan en un orden de magnitud más lento que sus contrapartes de C). Pero para muchos trabajos en hardware moderno, Python es lo suficientemente rápido y eso ha sido clave para su aceptación.

Otra gran diferencia es la gestión de la memoria. Los programas de Python están completamente administrados por el tiempo de ejecución de Python, por lo que los desarrolladores no tienen que preocuparse por el meollo de la asignación y liberación de memoria. Pero, de nuevo, la facilidad para los desarrolladores tiene como coste el rendimiento en tiempo de ejecución. Escribir programas en C requiere una atención escrupulosa a la gestión de la memoria, pero los programas resultantes suelen ser el estándar de oro para la velocidad de la máquina pura.

Sin embargo, bajo la piel, Python y C comparten una conexión profunda: el tiempo de ejecución de Python de referencia está escrito en C. Esto permite que los programas de Python envuelvan bibliotecas escritas en C y C ++. Partes importantes del ecosistema Python de bibliotecas de terceros, como el aprendizaje automático, tienen código C en su núcleo.

Si la velocidad de desarrollo es más importante que la velocidad de ejecución, y si la mayoría de las partes de rendimiento del programa se pueden aislar en componentes independientes (en lugar de distribuirse por todo el código), Python puro o una mezcla de bibliotecas de Python y C hacen una mejor opción que C sola. De lo contrario, C todavía gobierna.