C (lenguaje de programación)

ImprimirCitar

C (pronunciado como la letra c) es un lenguaje de programación de computadora de propósito general. Fue creado en la década de 1970 por Dennis Ritchie y sigue siendo muy utilizado e influyente. Por diseño, las funciones de C reflejan claramente las capacidades de las CPU de destino. Ha encontrado un uso duradero en sistemas operativos, controladores de dispositivos, pilas de protocolos, aunque cada vez menos para software de aplicación. C se usa comúnmente en arquitecturas informáticas que van desde las supercomputadoras más grandes hasta los microcontroladores y sistemas integrados más pequeños.

Un sucesor del lenguaje de programación B, C fue desarrollado originalmente en Bell Labs por Ritchie entre 1972 y 1973 para construir utilidades que se ejecutan en Unix. Se aplicó para reimplementar el núcleo del sistema operativo Unix. Durante la década de 1980, C ganó popularidad gradualmente. Se ha convertido en uno de los lenguajes de programación más utilizados, con compiladores de C disponibles para prácticamente todas las arquitecturas informáticas y sistemas operativos modernos. C ha sido estandarizado por ANSI desde 1989 (ANSI C) y por la Organización Internacional de Normalización (ISO).

C es un lenguaje de procedimiento imperativo que admite programación estructurada, recursividad y ámbito de variables léxicas, con un sistema de tipo estático. Fue diseñado para ser compilado para proporcionar acceso de bajo nivel a la memoria y construcciones de lenguaje que se asignan de manera eficiente a las instrucciones de la máquina, todo con un soporte de tiempo de ejecución mínimo. A pesar de sus capacidades de bajo nivel, el lenguaje fue diseñado para fomentar la programación multiplataforma. Un programa C que cumpla con los estándares escrito pensando en la portabilidad se puede compilar para una amplia variedad de plataformas informáticas y sistemas operativos con pocos cambios en su código fuente.

Desde el año 2000, C se ha clasificado constantemente entre los dos primeros idiomas en el índice TIOBE, una medida de la popularidad de los lenguajes de programación.

Resumen

Dennis Ritchie (derecha), el inventor del lenguaje de programación C, con Ken Thompson

C es un lenguaje procedimental imperativo en la tradición ALGOL. Tiene un sistema de tipo estático. En C, todo el código ejecutable está contenido en subrutinas (también llamadas "funciones", aunque no en el sentido de programación funcional). Los parámetros de función se pasan por valor, aunque las matrices se pasan como punteros, es decir, la dirección del primer elemento de la matriz. Pasar por referencia se simula en C al pasar explícitamente punteros a la cosa a la que se hace referencia.

El texto fuente del programa C es de formato libre, utiliza el punto y coma como separador de sentencias y llaves para agrupar bloques de sentencias.

El lenguaje C también exhibe las siguientes características:

  • El lenguaje tiene un pequeño número fijo de palabras clave, incluyendo un conjunto completo de primitivos flujo de control: if/else, for, do/while, while, y switch. Los nombres definidos por el usuario no se distinguen de las palabras clave por ningún tipo de sigil.
  • Tiene un gran número de operadores aritméticos, bitwise y lógicos: +,+=,++,&,||, etc.
  • Más de una asignación se puede realizar en una sola declaración.
  • Funciones:
    • Los valores de rendimiento de la función pueden ser ignorados, cuando no es necesario.
    • Las funciones y los indicadores de datos permiten ad hoc polimorfismo de tiempo de ejecución.
    • Las funciones no pueden definirse en el ámbito lexical de otras funciones.
    • Las variables pueden definirse dentro de una función, con alcance.
    • Una función puede llamarse a sí misma, por lo que se admite la recursión.
  • La clasificación de datos es estática, pero se aplica débilmente; todos los datos tienen un tipo, pero las conversiones implícitas son posibles.
  • Los tipos definidos por el usuario (tipodef) y compuestos son posibles.
    • Tipos de datos agregados heterogéneos (struct) permitir que los elementos de datos relacionados sean accedidos y asignados como unidad.
    • Unión es una estructura con miembros superpuestos; sólo el último miembro almacenado es válido.
    • La indización del rayo es una notación secundaria, definida en términos de aritmética puntero. A diferencia de las estructuras, los arrays no son objetos de primera clase: no pueden ser asignados o comparados usando operadores únicos incorporados. No hay "array" palabra clave en uso o definición; en cambio, los corchetes indican arrays sintacticamente, por ejemplo month[11].
    • Los tipos enumerados son posibles con el enum palabra clave. Son libremente interconvertibles con los enteros.
    • Las cuerdas no son un tipo de datos distinto, pero se implementan convencionalmente como arrays de caracteres nulos-terminados.
  • El acceso de bajo nivel a la memoria de la computadora es posible mediante la conversión de direcciones de la máquina a punteros.
  • Los procedimientos (subroutines not returning values) son un caso especial de función, con un tipo de retorno sin tipo void.
  • La memoria se puede asignar a un programa con llamadas a rutinas de biblioteca.
  • Un preprocesador realiza macrodefinición, inclusión de código fuente y compilación condicional.
  • Hay una forma básica de modularidad: los archivos se pueden compilar por separado y enlazar juntos, con control sobre qué funciones y objetos de datos son visibles a otros archivos a través de archivos estáticos y extern atributos.
  • Funciones complejas como I/O, manipulación de cadenas y funciones matemáticas se delegan consistentemente en rutinas de biblioteca.
  • El código generado después de la compilación tiene necesidades relativamente sencillas en la plataforma subyacente, lo que lo hace adecuado para crear sistemas operativos y para su uso en sistemas integrados.

Si bien C no incluye ciertas funciones que se encuentran en otros lenguajes (como la orientación a objetos y la recolección de basura), estas se pueden implementar o emular, a menudo mediante el uso de bibliotecas externas (por ejemplo, el sistema de objetos GLib o el recolector de basura Boehm).

Relaciones con otros idiomas

Muchos lenguajes posteriores han tomado prestado directa o indirectamente de C, incluidos C++, C#, C shell de Unix, D, Go, Java, JavaScript (incluidos los transpiladores), Julia, Limbo, LPC, Objective-C, Perl, PHP, Python, Ruby, Rust, Swift, Verilog y SystemVerilog (lenguajes de descripción de hardware). Estos lenguajes han extraído muchas de sus estructuras de control y otras características básicas de C. La mayoría de ellos (Python es una excepción dramática) también expresan una sintaxis muy similar a la de C, y tienden a combinar la expresión reconocible y la sintaxis de declaraciones de C con el tipo subyacente. sistemas, modelos de datos y semántica que pueden ser radicalmente diferentes.

Historia

Primeros desarrollos

Timeline of language development
Año C Estándar
1972 Nacimiento
1978 KRC C
1989/1990 ANSI C e ISO C
1999 C99
2011 C11
2018 C17
TBD C2x

El origen de C está estrechamente relacionado con el desarrollo del sistema operativo Unix, originalmente implementado en lenguaje ensamblador en un PDP-7 por Dennis Ritchie y Ken Thompson, incorporando varias ideas de colegas. Eventualmente, decidieron portar el sistema operativo a un PDP-11. La versión original PDP-11 de Unix también se desarrolló en lenguaje ensamblador.

B

Thompson deseaba un lenguaje de programación para crear utilidades para la nueva plataforma. Al principio, trató de hacer un compilador de Fortran, pero pronto abandonó la idea. En cambio, creó una versión reducida del lenguaje de programación de sistemas BCPL desarrollado recientemente. La descripción oficial de BCPL no estaba disponible en ese momento, y Thompson modificó la sintaxis para que fuera menos prolija y similar a un ALGOL simplificado conocido como SMALGOL. El resultado fue lo que Thompson llamó B. Describió a B como "semántica BCPL con mucha sintaxis SMALGOL". Al igual que BCPL, B tenía un compilador de arranque para facilitar la migración a nuevas máquinas. Sin embargo, pocas utilidades finalmente se escribieron en B porque era demasiado lento y no podía aprovechar las funciones de PDP-11, como la direccionabilidad de bytes.

Nueva versión B y primera C

En 1971, Ritchie comenzó a mejorar B para utilizar las funciones del PDP-11, que es más potente. Una adición significativa fue un tipo de personaje. Llamó a esto Nueva B. Thompson comenzó a usar NB para escribir el kernel de Unix y sus requisitos dieron forma a la dirección del desarrollo del lenguaje. Hasta 1972, se agregaron tipos más ricos al lenguaje NB: NB tenía matrices de int y char. También se agregaron punteros, la capacidad de generar punteros a otros tipos, matrices de todos los tipos y tipos que se devolverán de las funciones. Las matrices dentro de las expresiones se convirtieron en punteros. Se escribió un nuevo compilador y el lenguaje pasó a llamarse C.

El compilador de C y algunas utilidades creadas con él se incluyeron en la versión 2 de Unix, que también se conoce como Research Unix.

Reescritura de estructuras y kernel de Unix

En la versión 4 de Unix, lanzada en noviembre de 1973, el kernel de Unix se volvió a implementar ampliamente en C. En ese momento, el lenguaje C había adquirido algunas características poderosas, como los tipos struct.

El preprocesador se introdujo alrededor de 1973 a instancias de Alan Snyder y también en reconocimiento de la utilidad de los mecanismos de inclusión de archivos disponibles en BCPL y PL/I. Su versión original solo proporcionaba archivos incluidos y reemplazos de cadenas simples: #include y #define de macros sin parámetros. Poco después, fue ampliado, principalmente por Mike Lesk y luego por John Reiser, para incorporar macros con argumentos y compilación condicional.

Unix fue uno de los primeros kernels de sistemas operativos implementados en un lenguaje diferente al ensamblador. Las instancias anteriores incluyen el sistema Multics (que se escribió en PL/I) y el Programa de control maestro (MCP) para Burroughs B5000 (que se escribió en ALGOL) en 1961. Alrededor de 1977, Ritchie y Stephen C. Johnson hicieron más cambios en el lenguaje para facilitar la portabilidad del sistema operativo Unix. El compilador portátil de C de Johnson sirvió como base para varias implementaciones de C en nuevas plataformas.

K&R C

La cubierta del libro El lenguaje de programación C, primera edición, de Brian Kernighan y Dennis Ritchie

En 1978, Brian Kernighan y Dennis Ritchie publicaron la primera edición de El lenguaje de programación C. Este libro, conocido por los programadores de C como K&R, sirvió durante muchos años como una especificación informal del lenguaje. La versión de C que describe se conoce comúnmente como "K&R C". Como se lanzó en 1978, también se conoce como C78. La segunda edición del libro cubre el estándar ANSI C posterior, que se describe a continuación.

K&R introdujo varias funciones de lenguaje:

  • Biblioteca I/O estándar
  • long int Tipo de datos
  • unsigned int Tipo de datos
  • Operadores de asignación compuestos del formulario =op (como =-) fueron cambiados a la forma op= (es decir, -=) para eliminar la ambigüedad semántica creada por construcciones tales como i=-10, que se había interpretado como i =- 10 (Destitución) i por 10) en lugar de la posible intención i = -10 (let i Ser −10).

Incluso después de la publicación de la norma ANSI de 1989, durante muchos años K&R C se siguió considerando el "mínimo común denominador" a los que los programadores de C se restringieron cuando se deseaba la máxima portabilidad, ya que muchos compiladores más antiguos todavía estaban en uso, y porque el código K&R C cuidadosamente escrito también puede ser el estándar C legal.

En las primeras versiones de C, solo las funciones que devuelven tipos distintos de int deben declararse si se usan antes de la definición de la función; Se suponía que las funciones utilizadas sin declaración previa devolvían el tipo int.

Por ejemplo:

largo algunos_función(); /* Esta es una declaración de función, por lo que el compilador puede conocer el nombre y el tipo de retorno de esta función. */* Int */ other_function(); /* Otra declaración de función. Hay un tipo implícito 'int' aquí ya que estamos hablando de la versión temprana de C. Se comenta aquí para mostrar dónde podría ir en variantes posteriores. */* Int */ función() /* esta es una definición de función, incluyendo el cuerpo del código siguiente en los { corchetes curly } el tipo de retorno es 'int', pero esto es implícito por lo que no hay necesidad de indicar 'int' al utilizar esta versión temprana de C */{} largo test1; registro * Int */ test2; /* de nuevo, note que el 'int' no es requerido aquí, y se muestra como */ /* un comentario sólo para ilustrar dónde sería necesario en variantes posteriores de C. */ /* La palabra clave 'register' indica al compilador que esta variable debe */ /* idealmente se almacena en un registro en lugar de dentro del marco de la pila. */ test1 = algunos_función(); si ()test1  1) test2 = 0; más test2 = other_function(); retorno test2;}

Los especificadores de tipo int que están comentados podrían omitirse en K&R C, pero se requieren en estándares posteriores.

Dado que las declaraciones de funciones de K&R no incluían ninguna información sobre los argumentos de las funciones, no se realizaron comprobaciones del tipo de parámetros de funciones, aunque algunos compiladores emitían un mensaje de advertencia si se llamaba a una función local con un número incorrecto de argumentos, o si varios las llamadas a una función externa usaban diferentes números o tipos de argumentos. Se desarrollaron herramientas independientes, como la utilidad lint de Unix, que (entre otras cosas) podían verificar la consistencia del uso de funciones en múltiples archivos de origen.

En los años posteriores a la publicación de K&R C, se agregaron varias funciones al lenguaje, respaldadas por compiladores de AT&T (en particular, PCC) y algunos otros proveedores. Estos incluyeron:

  • void funciones (es decir, funciones sin valor de retorno)
  • funciones de repatriación struct o union tipos (antes sólo se puede devolver un puntero, entero o flotador)
  • asignación struct Tipos de datos
  • Se utilizaron tipos enumerados (anteriormente, definiciones de preprocesador para valores fijos enteros, por ejemplo. #define GREEN 3)

La gran cantidad de extensiones y la falta de acuerdo sobre una biblioteca estándar, junto con la popularidad del lenguaje y el hecho de que ni siquiera los compiladores de Unix implementaron con precisión la especificación K&R, llevaron a la necesidad de la estandarización.

ANSI C e ISO C

Durante los últimos años de la década de 1970 y 1980, se implementaron versiones de C para una amplia variedad de computadoras centrales, minicomputadoras y microcomputadoras, incluida la PC de IBM, a medida que su popularidad comenzó a aumentar significativamente.

En 1983, el Instituto Nacional Estadounidense de Estándares (ANSI) formó un comité, X3J11, para establecer una especificación estándar de C. X3J11 basó el estándar C en la implementación de Unix; sin embargo, la parte no portátil de la biblioteca Unix C se transfirió al grupo de trabajo IEEE 1003 para convertirse en la base del estándar POSIX de 1988. En 1989, el estándar C fue ratificado como ANSI X3.159-1989 "Lenguaje de programación C". Esta versión del lenguaje a menudo se denomina ANSI C, Standard C o, a veces, C89.

En 1990, la Organización Internacional de Normalización (ISO) adoptó el estándar ANSI C (con cambios de formato) como ISO/IEC 9899:1990, que a veces se denomina C90. Por lo tanto, los términos "C89" y "C90" se refieren al mismo lenguaje de programación.

ANSI, al igual que otros organismos nacionales de normalización, ya no desarrolla la norma C de forma independiente, sino que se remite a la norma C internacional, mantenida por el grupo de trabajo ISO/IEC JTC1/SC22/WG14. La adopción nacional de una actualización de la norma internacional generalmente ocurre dentro de un año de la publicación de ISO.

Uno de los objetivos del proceso de estandarización de C era producir un superconjunto de K&R C, incorporando muchas de las características no oficiales introducidas posteriormente. El comité de estándares también incluyó varias funciones adicionales, como prototipos de funciones (tomados de C++), punteros void, soporte para juegos de caracteres y locales internacionales y mejoras en el preprocesador. Aunque la sintaxis de las declaraciones de parámetros se aumentó para incluir el estilo utilizado en C++, la interfaz K&R siguió estando permitida, por compatibilidad con el código fuente existente.

C89 es compatible con los compiladores de C actuales y la mayoría del código C moderno se basa en él. Cualquier programa escrito solo en C estándar y sin suposiciones dependientes del hardware se ejecutará correctamente en cualquier plataforma con una implementación C conforme, dentro de sus límites de recursos. Sin tales precauciones, los programas pueden compilarse solo en una determinada plataforma o con un compilador en particular, debido, por ejemplo, al uso de bibliotecas no estándar, como bibliotecas GUI, o a la dependencia de atributos específicos del compilador o de la plataforma, como como el tamaño exacto de los tipos de datos y el byte endian.

En los casos en que el código deba ser compilable por compiladores basados en C de K&R o que cumplan con los estándares, se puede usar la macro __STDC__ para dividir el código en secciones estándar y K&R para evitar el uso en un compilador basado en C de K&R de funciones disponibles solo en C estándar.

Después del proceso de estandarización ANSI/ISO, la especificación del lenguaje C se mantuvo relativamente estática durante varios años. En 1995, se publicó la Enmienda Normativa 1 al estándar C de 1990 (ISO/IEC 9899/AMD1:1995, conocido informalmente como C95), para corregir algunos detalles y agregar un soporte más extenso para juegos de caracteres internacionales.

C99

El estándar C se revisó nuevamente a fines de la década de 1990, lo que condujo a la publicación de ISO/IEC 9899:1999 en 1999, que comúnmente se conoce como "C99". Desde entonces ha sido enmendado tres veces por Corrección Técnica.

C99 introdujo varias características nuevas, incluidas funciones en línea, varios tipos de datos nuevos (incluidos long long int y un tipo complex para representar números complejos), matrices de longitud variable y miembros de matriz flexibles, soporte mejorado para punto flotante IEEE 754, soporte para macros variádicas (macros de aridad variable) y soporte para comentarios de una línea que comienzan con //, como en BCPL o C++. Muchos de estos ya se habían implementado como extensiones en varios compiladores de C.

C99 es en su mayor parte retrocompatible con C90, pero es más estricto en algunos aspectos; en particular, una declaración que carece de un especificador de tipo ya no asume implícitamente int. Se define una macro estándar __STDC_VERSION__ con el valor 199901L para indicar que la compatibilidad con C99 está disponible. GCC, Solaris Studio y otros compiladores de C ahora admiten muchas o todas las funciones nuevas de C99. Sin embargo, el compilador de C en Microsoft Visual C++ implementa el estándar C89 y aquellas partes de C99 que son necesarias para la compatibilidad con C++11.

Además, el estándar requiere compatibilidad con identificadores Unicode (nombres de variables/funciones) en forma de caracteres de escape (por ejemplo, U0001f431) y sugiere soporte para nombres Unicode sin formato.

C11

En 2007, se comenzó a trabajar en otra revisión del estándar C, denominada informalmente "C1X" hasta su publicación oficial de ISO/IEC 9899:2011 el 2011-12-08. El comité de estándares de C adoptó pautas para limitar la adopción de nuevas características que no habían sido probadas por las implementaciones existentes.

El estándar C11 agrega numerosas funciones nuevas a C y la biblioteca, incluidas macros genéricas de tipo, estructuras anónimas, compatibilidad mejorada con Unicode, operaciones atómicas, subprocesos múltiples y funciones de verificación de límites. También hace que algunas partes de la biblioteca C99 existente sean opcionales y mejora la compatibilidad con C++. La macro estándar __STDC_VERSION__ se define como 201112L para indicar que la compatibilidad con C11 está disponible.

C17

Publicado en junio de 2018 como ISO/IEC 9899:2018, C17 es el estándar actual para el lenguaje de programación C. No introduce nuevas funciones de lenguaje, solo correcciones técnicas y aclaraciones a defectos en C11. La macro estándar __STDC_VERSION__ se define como 201710L.

C2x

C2x es un nombre informal para la próxima (después de C17) revisión principal del estándar del lenguaje C. Se espera que se vote en 2023 y, por lo tanto, se llamaría C23.

C incrustada

(feminine)

Históricamente, la programación C integrada requiere extensiones no estándar del lenguaje C para admitir características exóticas como la aritmética de punto fijo, varios bancos de memoria distintos y operaciones básicas de E/S.

En 2008, el Comité de estándares de C publicó un informe técnico que amplía el lenguaje C para abordar estos problemas al proporcionar un estándar común al que deben adherirse todas las implementaciones. Incluye una serie de funciones que no están disponibles en C normal, como aritmética de punto fijo, espacios de direcciones con nombre y direccionamiento básico de hardware de E/S.

Sintaxis

C tiene una gramática formal especificada por el estándar C. Los finales de línea generalmente no son significativos en C; sin embargo, los límites de línea tienen importancia durante la fase de preprocesamiento. Los comentarios pueden aparecer entre los delimitadores /* y */, o (desde C99) después de // hasta el final de la línea. Los comentarios delimitados por /* y */ no se anidan, y estas secuencias de caracteres no se interpretan como delimitadores de comentarios si aparecen dentro de cadenas o caracteres literales.

Los archivos fuente C contienen declaraciones y definiciones de funciones. Las definiciones de funciones, a su vez, contienen declaraciones y sentencias. Las declaraciones definen nuevos tipos usando palabras clave como struct, union y enum, o asignan tipos y quizás reservan almacenamiento para nuevas variables, generalmente escribiendo el tipo seguido del nombre de la variable. Palabras clave como char y int especifican tipos integrados. Las secciones de código están encerradas entre llaves ({ y }, a veces llamados "corchetes") para limitar el alcance de las declaraciones y actuar como un solo declaración para las estructuras de control.

Como lenguaje imperativo, C usa declaraciones para especificar acciones. La instrucción más común es una instrucción de expresión, que consta de una expresión que se va a evaluar, seguida de un punto y coma; como efecto secundario de la evaluación, se pueden llamar funciones y se pueden asignar nuevos valores a las variables. Para modificar la ejecución secuencial normal de sentencias, C proporciona varias sentencias de flujo de control identificadas por palabras clave reservadas. La programación estructurada es compatible con la ejecución condicional if... [else] y con do... while, while, y for ejecución iterativa (bucle). La declaración for tiene expresiones separadas de inicialización, prueba y reinicialización, cualquiera de las cuales o todas pueden omitirse. break y continue se pueden usar para dejar la declaración de bucle envolvente más interna o saltar a su reinicialización. También hay una instrucción goto no estructurada que se bifurca directamente a la etiqueta designada dentro de la función. switch selecciona un case para ser ejecutado en base al valor de una expresión entera.

Las expresiones pueden usar una variedad de operadores integrados y pueden contener llamadas a funciones. No se especifica el orden en que se evalúan los argumentos de las funciones y los operandos de la mayoría de los operadores. Las evaluaciones pueden incluso estar intercaladas. Sin embargo, todos los efectos secundarios (incluido el almacenamiento en variables) ocurrirán antes del siguiente "punto de secuencia"; los puntos de secuencia incluyen el final de cada instrucción de expresión y la entrada y el retorno de cada llamada de función. Los puntos de secuencia también ocurren durante la evaluación de expresiones que contienen ciertos operadores (&&, ||, ?: y el operador coma). Esto permite un alto grado de optimización del código objeto por parte del compilador, pero requiere que los programadores de C tengan más cuidado para obtener resultados confiables que los necesarios para otros lenguajes de programación.

Kernighan y Ritchie dicen en la Introducción de El lenguaje de programación C: "C, como cualquier otro lenguaje, tiene sus defectos. Algunos de los operadores tienen la precedencia incorrecta; algunas partes de la sintaxis podrían ser mejores." El estándar C no intentó corregir muchas de estas imperfecciones, debido al impacto de dichos cambios en el software ya existente.

Conjunto de personajes

El conjunto básico de caracteres fuente de C incluye los siguientes caracteres:

  • Cartas minúsculas y minúsculas del alfabeto latino básico ISO: az AZ
  • dígitos decimales: 09
  • Personajes gráficos: ! " # % & ' () * + -. /:; < = > ? [ ] ^ _ { | } ~
  • Personajes del espacio blanco: espacio, pestaña horizontal, pestaña vertical, alimento, newline

Nueva línea indica el final de una línea de texto; no es necesario que corresponda a un solo carácter real, aunque por conveniencia C lo trata como tal.

Se pueden usar caracteres codificados de varios bytes adicionales en cadenas literales, pero no son totalmente portátiles. El estándar C más reciente (C11) permite que los caracteres Unicode multinacionales se incrusten de manera portátil dentro del texto fuente C mediante el uso de la codificación uXXXX o UXXXXXXXXX (donde el X denota un carácter hexadecimal), aunque esta característica aún no está ampliamente implementada.

El juego de caracteres de ejecución básico de C contiene los mismos caracteres, junto con representaciones de alerta, retroceso y retorno de carro. El soporte en tiempo de ejecución para juegos de caracteres extendidos ha aumentado con cada revisión del estándar C.

Palabras reservadas

C89 tiene 32 palabras reservadas, también conocidas como palabras clave, que son las palabras que no se pueden usar para otros fines que no sean aquellos para los que están predefinidas:

  • auto
  • break
  • case
  • char
  • const
  • continue
  • default
  • do
  • double
  • else
  • enum
  • extern
  • float
  • for
  • goto
  • if
  • int
  • long
  • register
  • return
  • short
  • signed
  • sizeof
  • static
  • struct
  • switch
  • typedef
  • union
  • unsigned
  • void
  • volatile
  • while

C99 reservó cinco palabras más:

  • _Bool
  • _Complex
  • _Imaginary
  • inline
  • restrict

C11 reservó siete palabras más:

  • _Alignas
  • _Alignof
  • _Atomic
  • _Generic
  • _Noreturn
  • _Static_assert
  • _Thread_local

La mayoría de las palabras reservadas recientemente comienzan con un guión bajo seguido de una letra mayúscula, porque el estándar C reservaba anteriormente identificadores de esa forma para que los usaran solo las implementaciones. Dado que el código fuente del programa existente no debería haber estado usando estos identificadores, no se vería afectado cuando las implementaciones de C comenzaran a admitir estas extensiones del lenguaje de programación. Algunos encabezados estándar definen sinónimos más convenientes para los identificadores subrayados. Anteriormente, el lenguaje incluía una palabra reservada llamada entry, pero rara vez se implementaba y ahora se eliminó como palabra reservada.

Operadores

C admite un amplio conjunto de operadores, que son símbolos que se usan dentro de una expresión para especificar las manipulaciones que se realizarán al evaluar esa expresión. C tiene operadores para:

  • aritmética: +, -, *, /, %
  • asignación: =
  • asignación aumentada: +=,-=,*=,/=,%=,&=,|=,^=,<<=,>>=
  • lógica bitwise: ~, &, |, ^
  • turnos de bitwise: <<, >>
  • lógica booleana: !, &&, ||
  • evaluación condicional: ?:
  • prueba de igualdad: ==, !=
  • funciones de llamada: ()
  • aumento y decremento: ++, --
  • selección de miembros: ., ->
  • tamaño del objeto: sizeof
  • relaciones de orden: <, <=, >, >=
  • referencia y referencia: &, *, [ ]
  • secuenciación:
  • subexpresión agrupación: ()
  • conversión de tipo: (typename)

C usa el operador = (usado en matemáticas para expresar igualdad) para indicar asignación, siguiendo el precedente de Fortran y PL/I, pero a diferencia de ALGOL y sus derivados. C usa el operador == para probar la igualdad. La similitud entre estos dos operadores (asignación e igualdad) puede resultar en el uso accidental de uno en lugar del otro y, en muchos casos, el error no produce un mensaje de error (aunque algunos compiladores generan advertencias). Por ejemplo, la expresión condicional if (a == b + 1) podría escribirse erróneamente como if (a = b + 1), que se evaluará como verdadera si a no es cero después de la asignación.

La precedencia del operador C no siempre es intuitiva. Por ejemplo, el operador == enlaza más estrechamente que (se ejecuta antes) que los operadores & (Y bit a bit) y | (bit a bit OR) en expresiones como x & 1 == 0, que debe escribirse como (x & 1) == 0 si esa es la intención del codificador.

"Hola mundo" ejemplo

"¡Hola, Mundo!" programa de Brian Kernighan (1978)

El mensaje "hola, mundo" ejemplo, que apareció en la primera edición de K&R, se ha convertido en el modelo de un programa introductorio en la mayoría de los libros de texto de programación. El programa imprime "hola, mundo" a la salida estándar, que suele ser un terminal o una pantalla.

La versión original era:

principal(){} printf()"Hola, mundon");}

Un "hola mundo" conforme a los estándares programa es:

#include Identificado.hint principal()vacío){} printf()"Hola, mundon");}

La primera línea del programa contiene una directiva de preprocesamiento, indicada por #include. Esto hace que el compilador reemplace esa línea con el texto completo del encabezado estándar stdio.h, que contiene declaraciones para funciones estándar de entrada y salida como printf y escanear. Los corchetes angulares que rodean a stdio.h indican que stdio.h se puede ubicar usando una estrategia de búsqueda que prefiere los encabezados proporcionados con el compilador a otros encabezados que tienen el mismo nombre, en lugar de a comillas dobles que normalmente incluyen archivos de encabezado locales o específicos del proyecto.

La siguiente línea indica que se está definiendo una función llamada main. La función main tiene un propósito especial en los programas C; el entorno de tiempo de ejecución llama a la función main para comenzar la ejecución del programa. El especificador de tipo int indica que el valor que se devuelve al invocador (en este caso, el entorno de tiempo de ejecución) como resultado de evaluar la función main, es un número entero. La palabra clave void como lista de parámetros indica que esta función no acepta argumentos.

La llave de apertura indica el comienzo de la definición de la función main.

La siguiente línea llama (desvía la ejecución a) una función llamada printf, que en este caso se proporciona desde una biblioteca del sistema. En esta llamada, la función printf se pasa (se proporciona con) un solo argumento, la dirección del primer carácter en el literal de cadena "hola, mundon". El literal de cadena es una matriz sin nombre con elementos de tipo char, configurada automáticamente por el compilador con un carácter final con valor 0 para marcar el final de la matriz (printf necesita saber esto). El n es una secuencia de escape que C traduce a un carácter de nueva línea, que en la salida significa el final de la línea actual. El valor de retorno de la función printf es de tipo int, pero se descarta silenciosamente ya que no se usa. (Un programa más cuidadoso podría probar el valor devuelto para determinar si la función printf tuvo éxito o no.) El punto y coma ; termina la instrucción.

La llave de cierre indica el final del código para la función main. Según la especificación C99 y posteriores, la función main, a diferencia de cualquier otra función, devolverá implícitamente un valor de 0 al llegar al } que termina la función. (Anteriormente, se requería una declaración return 0; explícita.) El sistema de tiempo de ejecución interpreta esto como un código de salida que indica una ejecución exitosa.

Tipos de datos

1999 ISO C Concepts.png

El sistema de tipos en C es estático y débilmente tipificado, lo que lo hace similar al sistema de tipos de los descendientes de ALGOL como Pascal. Hay tipos incorporados para enteros de varios tamaños, tanto con signo como sin signo, números de coma flotante y tipos enumerados (enum). El tipo entero char se usa a menudo para caracteres de un solo byte. C99 agregó un tipo de datos booleano. También hay tipos derivados que incluyen matrices, punteros, registros (struct) y uniones (union).

C se usa a menudo en la programación de sistemas de bajo nivel donde pueden ser necesarios escapes del sistema de tipos. El compilador intenta garantizar la corrección de tipos de la mayoría de las expresiones, pero el programador puede anular las comprobaciones de varias formas, ya sea utilizando una conversión de tipos para convertir explícitamente un valor de un tipo a otro, o utilizando punteros. o uniones para reinterpretar los bits subyacentes de un objeto de datos de alguna otra manera.

Algunos encuentran que la sintaxis de la declaración de C es poco intuitiva, particularmente para los punteros de función. (La idea de Ritchie era declarar los identificadores en contextos similares a su uso: 'la declaración refleja el uso').

Las conversiones aritméticas habituales de C's permiten generar un código eficiente, pero a veces pueden producir resultados inesperados. Por ejemplo, una comparación de enteros con y sin signo de igual ancho requiere una conversión del valor con signo a sin signo. Esto puede generar resultados inesperados si el valor firmado es negativo.

Puntero

C admite el uso de punteros, un tipo de referencia que registra la dirección o ubicación de un objeto o función en la memoria. Los punteros se pueden desreferenciar para acceder a los datos almacenados en la dirección a la que se apunta, o para invocar una función a la que se apunta. Los punteros se pueden manipular mediante asignación o aritmética de punteros. La representación en tiempo de ejecución de un valor de puntero suele ser una dirección de memoria sin procesar (quizás aumentada por un campo de desplazamiento dentro de la palabra), pero dado que el tipo de un puntero incluye el tipo de cosa a la que apunta, las expresiones que incluyen punteros pueden ser verificado en tiempo de compilación. La aritmética de punteros se escala automáticamente según el tamaño del tipo de datos apuntado.

Los punteros se usan para muchos propósitos en C. Las cadenas de texto se manipulan comúnmente usando punteros en matrices de caracteres. La asignación de memoria dinámica se realiza mediante punteros; el resultado de un malloc generalmente se convierte en el tipo de datos de los datos que se almacenarán. Muchos tipos de datos, como los árboles, se implementan comúnmente como objetos struct asignados dinámicamente vinculados entre sí mediante punteros. Los punteros a otros punteros se utilizan a menudo en matrices multidimensionales y matrices de objetos struct. Los punteros a funciones (punteros de función) son útiles para pasar funciones como argumentos a funciones de orden superior (como qsort o bsearch), en tablas de despacho o como devoluciones de llamadas a controladores de eventos.

Un valor de puntero nulo apunta explícitamente a una ubicación no válida. La desreferenciación de un valor de puntero nulo no está definida, lo que a menudo da como resultado un error de segmentación. Los valores de puntero nulo son útiles para indicar casos especiales como no "siguiente" puntero en el nodo final de una lista enlazada, o como una indicación de error de las funciones que devuelven punteros. En contextos apropiados en el código fuente, como para asignar a una variable de puntero, una constante de puntero nulo se puede escribir como 0, con o sin conversión explícita a un tipo de puntero, o como la macro NULL definida por varios encabezados estándar. En contextos condicionales, los valores de puntero nulo se evalúan como falsos, mientras que todos los demás valores de puntero se evalúan como verdaderos.

Los punteros vacíos (void *) apuntan a objetos de tipo no especificado y, por lo tanto, se pueden usar como "genéricos" punteros de datos Dado que no se conoce el tamaño y el tipo del objeto apuntado, los punteros vacíos no se pueden desreferenciar, ni se permite la aritmética de punteros en ellos, aunque se pueden convertir fácilmente (y en muchos contextos implícitamente) se convierten a y desde cualquier otro puntero de objeto. escribe.

El uso descuidado de punteros es potencialmente peligroso. Debido a que normalmente no están marcadas, se puede hacer que una variable de puntero apunte a cualquier ubicación arbitraria, lo que puede causar efectos no deseados. Aunque los punteros utilizados correctamente apuntan a lugares seguros, se pueden hacer que apunten a lugares inseguros mediante el uso de aritmética de puntero no válida; los objetos a los que apuntan pueden seguir usándose después de la desasignación (punteros colgantes); pueden usarse sin haber sido inicializados (wild pointers); o se les puede asignar directamente un valor inseguro mediante una conversión, unión o mediante otro puntero corrupto. En general, C es permisivo al permitir la manipulación y la conversión entre tipos de punteros, aunque los compiladores suelen proporcionar opciones para varios niveles de verificación. Algunos otros lenguajes de programación abordan estos problemas mediante el uso de tipos de referencia más restrictivos.

Arreglos

Los tipos de matriz en C son tradicionalmente de un tamaño fijo y estático especificado en el momento de la compilación. El estándar C99 más reciente también permite una forma de matrices de longitud variable. Sin embargo, también es posible asignar un bloque de memoria (de tamaño arbitrario) en tiempo de ejecución, utilizando la función malloc de la biblioteca estándar, y tratarlo como una matriz.

Dado que siempre se accede a los arreglos (en efecto) a través de punteros, los accesos a los arreglos generalmente no se comparan con el tamaño del arreglo subyacente, aunque algunos compiladores pueden proporcionar verificación de límites como una opción. Por lo tanto, las violaciones de los límites de la matriz son posibles y pueden tener varias repercusiones, incluidos accesos ilegales a la memoria, corrupción de datos, desbordamientos del búfer y excepciones en tiempo de ejecución.

C no tiene una disposición especial para declarar matrices multidimensionales, sino que se basa en la recursividad dentro del sistema de tipos para declarar matrices de matrices, lo que efectivamente logra lo mismo. Los valores de índice de la "matriz multidimensional" se puede considerar como creciente en orden de fila principal. Las matrices multidimensionales se usan comúnmente en algoritmos numéricos (principalmente de álgebra lineal aplicada) para almacenar matrices. La estructura de la matriz C se adapta bien a esta tarea en particular. Sin embargo, en las primeras versiones de C, los límites de la matriz deben ser valores fijos conocidos o, de lo contrario, pasarse explícitamente a cualquier subrutina que los requiera, y no se puede acceder a las matrices de matrices de tamaño dinámico mediante la doble indexación. (Una solución para esto fue asignar la matriz con un "vector de fila" adicional de punteros a las columnas). C99 introdujo "matrices de longitud variable" que abordan este tema.

El siguiente ejemplo que usa C moderno (C99 o posterior) muestra la asignación de una matriz bidimensional en el montón y el uso de la indexación de matriz multidimensional para los accesos (que pueden usar la verificación de límites en muchos compiladores de C):

int func()int N, int M){} flotador ()*p[N[ ]M] = malloc()tamaño *p); si ()!p) retorno -1; para ()int i = 0; i . N; i++) para ()int j = 0; j . M; j++) ()*p[i[ ]j] = i + j; print_array()N, M, p); gratis()p); retorno 1;}

Y aquí hay una implementación similar usando la función Auto VLA de C99:

int func()int N, int M){} // Precaución: se deben realizar comprobaciones para garantizar que N*M*sizeof(float) no exceda las limitaciones para los VLAs automáticos y está dentro del tamaño disponible de la pila. flotador p[N[ ]M]; // auto VLA se mantiene en la pila, y tamaño cuando se invoca la función para ()int i = 0; i . N; i++) para ()int j = 0; j . M; j++) p[i[ ]j] = i + j; // no es necesario free(p) ya que desaparecerá cuando la función salga, junto con el resto del marco de la pila retorno 1;}

Intercambiabilidad matriz-puntero

La notación de subíndice x[i] (donde x designa un puntero) es azúcar sintáctica para *(x+i). Aprovechando el conocimiento del compilador del tipo de puntero, la dirección a la que apunta x + i no es la dirección base (señalada por x) incrementada por i bytes, sino que se define como la dirección base incrementada por i multiplicada por el tamaño de un elemento al que apunta x. Así, x[i] designa el i+1ésimo elemento del arreglo.

Además, en la mayoría de los contextos de expresión (una excepción notable es como operando de sizeof), una expresión de tipo matriz se convierte automáticamente en un puntero al primer elemento de la matriz. Esto implica que una matriz nunca se copia como un todo cuando se nombra como argumento de una función, sino que solo se pasa la dirección de su primer elemento. Por lo tanto, aunque las llamadas a funciones en C usan semántica de paso por valor, las matrices se pasan en efecto por referencia.

El tamaño total de una matriz x se puede determinar aplicando sizeof a una expresión de tipo matriz. El tamaño de un elemento se puede determinar aplicando el operador sizeof a cualquier elemento sin referencia de una matriz A, como en n = sizeof A[0]. Por lo tanto, el número de elementos en una matriz declarada A se puede determinar como sizeof A / sizeof A[0]. Tenga en cuenta que si solo está disponible un puntero al primer elemento, como suele ser el caso en el código C debido a la conversión automática descrita anteriormente, se pierde la información sobre el tipo completo de la matriz y su longitud.

Gestión de memoria

Una de las funciones más importantes de un lenguaje de programación es proporcionar facilidades para administrar la memoria y los objetos que se almacenan en la memoria. C proporciona tres formas principales de asignar memoria para objetos:

  • Asignación de memoria estatica: espacio para el objeto se proporciona en el binario a tiempo de compilación; estos objetos tienen una extensión (o vida) mientras el binario que contiene se carga en la memoria.
  • Asignación automática de la memoria: los objetos temporales pueden almacenarse en la pila, y este espacio se libera automáticamente y se reutiliza después de que el bloque en el que se declara se salga.
  • Asignación dinámica de memoria: bloques de memoria de tamaño arbitrario se pueden solicitar en tiempo de ejecución utilizando funciones de biblioteca como malloc de una región de memoria llamada el montón; estos bloques persisten hasta posteriormente liberados para reutilizar llamando a la función bibliotecaria realloc o free

Estos tres enfoques son apropiados en diferentes situaciones y tienen varias ventajas y desventajas. Por ejemplo, la asignación de memoria estática tiene poca sobrecarga de asignación, la asignación automática puede implicar un poco más de sobrecarga y la asignación de memoria dinámica puede potencialmente tener una gran sobrecarga tanto para la asignación como para la desasignación. La naturaleza persistente de los objetos estáticos es útil para mantener la información de estado en las llamadas a funciones, la asignación automática es fácil de usar, pero el espacio de pila suele ser mucho más limitado y transitorio que la memoria estática o el espacio de almacenamiento dinámico, y la asignación de memoria dinámica permite la asignación conveniente de objetos cuyo el tamaño solo se conoce en tiempo de ejecución. La mayoría de los programas en C hacen un uso extensivo de los tres.

Cuando es posible, la asignación automática o estática suele ser más sencilla porque el compilador administra el almacenamiento, lo que libera al programador de la tarea potencialmente propensa a errores de asignar y liberar almacenamiento manualmente. Sin embargo, muchas estructuras de datos pueden cambiar de tamaño en tiempo de ejecución, y dado que las asignaciones estáticas (y las asignaciones automáticas antes de C99) deben tener un tamaño fijo en tiempo de compilación, hay muchas situaciones en las que es necesaria la asignación dinámica. Antes del estándar C99, los arreglos de tamaño variable eran un ejemplo común de esto. (Consulte el artículo sobre malloc para ver un ejemplo de arreglos asignados dinámicamente). A diferencia de la asignación automática, que puede fallar en tiempo de ejecución con consecuencias incontroladas, las funciones de asignación dinámica devuelven una indicación (en forma de un valor nulo valor del puntero) cuando no se puede asignar el almacenamiento necesario. (La asignación estática que es demasiado grande generalmente es detectada por el enlazador o el cargador, incluso antes de que el programa pueda comenzar a ejecutarse).

A menos que se especifique lo contrario, los objetos estáticos contienen valores de puntero cero o nulos al iniciar el programa. Los objetos asignados automática y dinámicamente se inicializan solo si se especifica explícitamente un valor inicial; de lo contrario, inicialmente tienen valores indeterminados (por lo general, cualquier patrón de bits que esté presente en el almacenamiento, que podría no representar un valor válido para ese tipo). Si el programa intenta acceder a un valor no inicializado, los resultados no están definidos. Muchos compiladores modernos intentan detectar y advertir sobre este problema, pero pueden ocurrir tanto falsos positivos como falsos negativos.

La asignación de memoria del montón debe sincronizarse con su uso real en cualquier programa para que se reutilice tanto como sea posible. Por ejemplo, si el único puntero a una asignación de memoria de pila queda fuera del alcance o se sobrescribe su valor antes de que se desasigne explícitamente, entonces esa memoria no se puede recuperar para su reutilización posterior y esencialmente se pierde para el programa, un fenómeno conocido como < i>pérdida de memoria. Por el contrario, es posible que se libere memoria, pero se hace referencia a ella posteriormente, lo que genera resultados impredecibles. Por lo general, los síntomas de falla aparecen en una parte del programa que no está relacionada con el código que causa el error, lo que dificulta el diagnóstico de la falla. Tales problemas se mejoran en idiomas con recolección automática de basura.

Bibliotecas

El lenguaje de programación C usa bibliotecas como su principal método de extensión. En C, una biblioteca es un conjunto de funciones contenidas dentro de un solo "archivo" expediente. Cada biblioteca normalmente tiene un archivo de encabezado, que contiene los prototipos de las funciones contenidas dentro de la biblioteca que puede usar un programa, y declaraciones de tipos de datos especiales y símbolos de macro usados con estas funciones. Para que un programa use una biblioteca, debe incluir el archivo de encabezado de la biblioteca y la biblioteca debe estar vinculada con el programa, lo que en muchos casos requiere indicadores del compilador (por ejemplo, -lm, abreviatura de "vincular la biblioteca de matemáticas").

La biblioteca C más común es la biblioteca estándar C, que está especificada por los estándares ISO y ANSI C y viene con cada implementación de C (las implementaciones que tienen como objetivo entornos limitados, como los sistemas integrados, pueden proporcionar solo un subconjunto de la biblioteca estándar). Esta biblioteca admite entrada y salida de secuencias, asignación de memoria, matemáticas, cadenas de caracteres y valores de tiempo. Varios encabezados estándar independientes (por ejemplo, stdio.h) especifican las interfaces para estas y otras funciones de biblioteca estándar.

Otro conjunto común de funciones de la biblioteca C son las que utilizan las aplicaciones diseñadas específicamente para sistemas Unix y similares a Unix, especialmente las funciones que proporcionan una interfaz para el kernel. Estas funciones se detallan en varios estándares, como POSIX y Single UNIX Specification.

Dado que muchos programas se han escrito en C, existe una amplia variedad de otras bibliotecas disponibles. Las bibliotecas a menudo se escriben en C porque los compiladores de C generan código objeto eficiente; Luego, los programadores crean interfaces para la biblioteca para que las rutinas se puedan usar desde lenguajes de nivel superior como Java, Perl y Python.

Manejo de archivos y flujos

La entrada y salida de archivos (E/S) no es parte del lenguaje C en sí, sino que es manejada por bibliotecas (como la biblioteca estándar de C) y sus archivos de encabezado asociados (p. ej., stdio.h). El manejo de archivos generalmente se implementa a través de E/S de alto nivel que funciona a través de flujos. Un flujo es, desde esta perspectiva, un flujo de datos que es independiente de los dispositivos, mientras que un archivo es un dispositivo concreto. La E/S de alto nivel se realiza mediante la asociación de un flujo a un archivo. En la biblioteca estándar de C, un búfer (un área de memoria o cola) se usa temporalmente para almacenar datos antes de enviarlos al destino final. Esto reduce el tiempo de espera de dispositivos más lentos, por ejemplo, un disco duro o una unidad de estado sólido. Las funciones de E/S de bajo nivel no forman parte de la biblioteca C estándar, pero generalmente forman parte de "bare metal" programación (programación que es independiente de cualquier sistema operativo, como la mayoría de la programación integrada). Con pocas excepciones, las implementaciones incluyen E/S de bajo nivel.

Herramientas de idioma

Se han desarrollado una serie de herramientas para ayudar a los programadores de C a encontrar y corregir sentencias con un comportamiento indefinido o posiblemente con expresiones erróneas, con mayor rigor que el proporcionado por el compilador. La pelusa de la herramienta fue la primera, y dio lugar a muchas otras.

La verificación y auditoría automáticas del código fuente son beneficiosas en cualquier idioma, y para C existen muchas herramientas de este tipo, como Lint. Una práctica común es usar Lint para detectar código cuestionable cuando se escribe un programa por primera vez. Una vez que un programa pasa Lint, luego se compila usando el compilador C. Además, muchos compiladores pueden advertir opcionalmente sobre construcciones sintácticamente válidas que probablemente sean errores. MISRA C es un conjunto patentado de pautas para evitar dicho código cuestionable, desarrollado para sistemas integrados.

También hay compiladores, bibliotecas y mecanismos a nivel del sistema operativo para realizar acciones que no son una parte estándar de C, como la comprobación de límites para matrices, la detección de desbordamiento de búfer, la serialización, el seguimiento dinámico de la memoria y la recolección automática de elementos no utilizados.

Las herramientas como Purify o Valgrind y la vinculación con bibliotecas que contienen versiones especiales de las funciones de asignación de memoria pueden ayudar a descubrir errores de tiempo de ejecución en el uso de la memoria.

Usos

Razones para su uso en la programación de sistemas

El lenguaje de programación C

C se usa ampliamente para la programación de sistemas en la implementación de sistemas operativos y aplicaciones de sistemas integrados. Esto es por varias razones:

  • El código generado después de la compilación no exige muchas características del sistema, y puede ser invocado de algún código de arranque de manera sencilla – es simple de ejecutar.
  • Las declaraciones y expresiones del lenguaje C suelen mapear bien en secuencias de instrucciones para el procesador de destino, y por lo tanto hay una baja demanda de tiempo de ejecución en los recursos del sistema – es rápido de ejecutar.
  • Con su rico conjunto de operadores, el lenguaje C puede utilizar muchas de las características de las CPU de destino. Cuando una CPU en particular tiene instrucciones más esotéricas, una variante de lenguaje se puede construir con funciones quizás intrínsecas para explotar esas instrucciones – puede utilizar prácticamente todas las características de la CPU objetivo.
  • El lenguaje hace que sea fácil superponer las estructuras en bloques de datos binarios, permitiendo que los datos sean comprimidos, navegados y modificados – puede escribir estructuras de datos, incluso sistemas de archivos.
  • El lenguaje soporta un rico conjunto de operadores, incluyendo manipulación de bits, para aritmética y lógica entero, y quizás diferentes tamaños de números de puntos flotantes – puede procesar datos adecuadamente estructurados de manera efectiva.
  • C es un lenguaje bastante pequeño, con sólo un puñado de declaraciones, y sin demasiadas características que generan amplio código de destino – es comprensible.
  • C tiene control directo sobre la asignación de memoria y la distribución de datos, lo que proporciona una eficiencia razonable y un calendario predecible para las operaciones de gestión de memoria, sin ninguna preocupación por el esporádico Para el mundo eventos de recolección de basura – tiene un rendimiento predecible.
  • El hardware de plataforma se puede acceder con punteros y tipo punning, por lo que las características específicas del sistema (por ejemplo, Registros de Control/Status, registros I/O) se pueden configurar y utilizar con código escrito en C – interactúa bien con la plataforma que se está ejecutando.
  • Dependiendo del linker y el entorno, el código C también puede llamar a las bibliotecas escritas en lenguaje de ensamblaje, y puede llamarse de lenguaje de ensamblaje – interopera bien con otro código de nivel inferior.
  • C y sus convenciones llamativas y estructuras de enlace se utilizan comúnmente en combinación con otros idiomas de alto nivel, con llamadas tanto a C como desde C compatibles – interopera bien con otros códigos de alto nivel.
  • C tiene un ecosistema muy maduro y amplio, incluyendo bibliotecas, marcos, compiladores de código abierto, depuradores y utilidades, y es el estándar de facto. Es probable que los controladores ya existen en C, o que hay una arquitectura CPU similar como un back-end de un compilador C, por lo que hay un incentivo reducido para elegir otro idioma.

Una vez utilizado para el desarrollo web

Históricamente, C se usaba a veces para el desarrollo web utilizando Common Gateway Interface (CGI) como "puerta de enlace" para obtener información entre la aplicación web, el servidor y el navegador. Es posible que se haya elegido C en lugar de los lenguajes interpretados debido a su velocidad, estabilidad y disponibilidad casi universal. Ya no es una práctica común que el desarrollo web se realice en C, y existen muchas otras herramientas de desarrollo web.

Algunos otros idiomas están escritos en C

Una consecuencia de la amplia disponibilidad y eficiencia de C es que los compiladores, bibliotecas e intérpretes de otros lenguajes de programación a menudo se implementan en C. Por ejemplo, las implementaciones de referencia de Python, Perl, Ruby y PHP están escritas en C.

Utilizado para bibliotecas computacionalmente intensivas

C permite a los programadores crear implementaciones eficientes de algoritmos y estructuras de datos, porque la capa de abstracción del hardware es delgada y su sobrecarga es baja, un criterio importante para los programas de computación intensiva. Por ejemplo, la biblioteca aritmética de precisión múltiple de GNU, la biblioteca científica de GNU, Mathematica y MATLAB están total o parcialmente escritos en C. Muchos lenguajes admiten llamadas a funciones de biblioteca en C, por ejemplo, el marco basado en Python NumPy usa C para la alta -aspectos de rendimiento e interacción con el hardware.

C como lenguaje intermedio

C a veces se usa como lenguaje intermedio en implementaciones de otros lenguajes. Este enfoque se puede utilizar por motivos de portabilidad o conveniencia; al usar C como lenguaje intermedio, no son necesarios generadores de código adicionales específicos de la máquina. C tiene algunas características, como directivas de preprocesador de número de línea y comas superfluas opcionales al final de las listas de inicializadores, que admiten la compilación del código generado. Sin embargo, algunas de las deficiencias de C han impulsado el desarrollo de otros lenguajes basados en C diseñados específicamente para su uso como lenguajes intermedios, como C--. Además, los principales compiladores contemporáneos, GCC y LLVM, cuentan con una representación intermedia que no es C, y esos compiladores admiten front-end para muchos lenguajes, incluido C.

Aplicaciones de usuario final

C también se ha utilizado ampliamente para implementar aplicaciones de usuario final. Sin embargo, dichas aplicaciones también se pueden escribir en lenguajes más nuevos y de mayor nivel.

Limitaciones

el poder del lenguaje de montaje y la comodidad de...

Dennis Ritchie,

Si bien C ha sido popular, influyente y enormemente exitoso, tiene inconvenientes, que incluyen:

  • El manejo de memoria dinámica estándar con malloc y free es un error prono. Los errores incluyen: La memoria se filtra cuando la memoria se asigna pero no se libera; y el acceso a la memoria previamente liberada.
  • El uso de punteros y la manipulación directa de la memoria significa que la corrupción de la memoria es posible, tal vez debido al error del programador, o la comprobación insuficiente de datos malos.
  • Hay algún tipo de comprobación, pero no se aplica a áreas como funciones variadas, y el tipo de comprobación puede ser trivial o inadvertidamente eludido.
  • Dado que el código generado por el compilador contiene pocos cheques en sí mismo, hay una carga para el programador para considerar todos los resultados posibles, para proteger contra los sobrecostos de amortiguación, verificación de límites de matriz, flujos de pila, agotamiento de memoria, y considerar las condiciones de raza, aislamiento de hilo, etc.
  • El uso de punteros y la manipulación de estos medios puede haber dos maneras de acceder a los mismos datos (aliasing), que no es determinante en el tiempo de compilación. Esto significa que algunas optimizaciones que pueden estar disponibles para otros idiomas no son posibles en C. FORTRAN se considera más rápido.
  • Algunas de las funciones de biblioteca estándar, por ejemplo. scanf o strncat, puede conducir a sobrecostos de amortiguación.
  • Hay una estandarización limitada en apoyo de variantes de bajo nivel en código generado, por ejemplo: diferentes convenciones de funciones y ABI; diferentes convenios de embalaje de estructuras; orden de byte diferente dentro de enteros más grandes (incluyendo endianness). En muchas implementaciones lingüísticas, algunas de estas opciones se pueden manejar con la directiva del preprocesador #pragma, y algunos con palabras clave adicionales, por ejemplo, uso __cdecl llamando convención. Pero la directiva y las opciones no son consistentemente compatibles.
  • El manejo de cuerdas usando la biblioteca estándar es intensivo en código, con la gestión explícita de memoria necesaria.
  • El lenguaje no apoya directamente la orientación del objeto, la introspección, la evaluación de la expresión de tiempo de ejecución, genéricos, etc.
  • Hay pocos guardias contra el uso inapropiado de características de lenguaje, lo que puede llevar a código inmantenible. Esta instalación de código complicado se ha celebrado con competiciones como la Concurso Internacional de Código C Obfuscado y el Concurso C subjetivo.
  • C carece de soporte estándar para el manejo de excepciones y sólo ofrece códigos de devolución para la comprobación de errores. Las funciones de biblioteca estándar setjmp y longjmp se han utilizado para implementar un mecanismo de búsqueda a través de macros.

Para algunos propósitos, se han adoptado estilos restringidos de C, p. MISRA C o CERT C, en un intento de reducir la oportunidad de errores. Las bases de datos como CWE intentan contar las formas en que C, etc. tiene vulnerabilidades, junto con recomendaciones para la mitigación.

Existen herramientas que pueden mitigar algunos de los inconvenientes. Los compiladores de C contemporáneos incluyen controles que pueden generar advertencias para ayudar a identificar muchos errores potenciales.

Algunos de estos inconvenientes han impulsado la construcción de otros lenguajes.

Idiomas relacionados

El gráfico índice TIOBE, mostrando una comparación de la popularidad de varios lenguajes de programación

C ha influido tanto directa como indirectamente en muchos lenguajes posteriores, como C++, C#, D, Go, Java, JavaScript, Perl, PHP, Rust y el shell C de Unix. La influencia más penetrante ha sido sintáctica; todos los lenguajes mencionados combinan la declaración y (más o menos reconocible) la sintaxis de expresión de C con sistemas de tipos, modelos de datos y/o estructuras de programas a gran escala que difieren de las de C, a veces radicalmente.

Existen varios intérpretes de C o casi C, incluidos Ch y CINT, que también se pueden usar para secuencias de comandos.

Cuando los lenguajes de programación orientados a objetos se hicieron populares, C++ y Objective-C eran dos extensiones diferentes de C que proporcionaban capacidades orientadas a objetos. Ambos lenguajes se implementaron originalmente como compiladores fuente a fuente; El código fuente se tradujo a C y luego se compiló con un compilador de C.

El lenguaje de programación C++ (originalmente llamado "C con clases") fue ideado por Bjarne Stroustrup como un enfoque para proporcionar funcionalidad orientada a objetos con una sintaxis similar a la de C. C ++ agrega una mayor fuerza de escritura, alcance y otras herramientas útiles en la programación orientada a objetos, y permite la programación genérica a través de plantillas. Casi un superconjunto de C, C++ ahora admite la mayor parte de C, con algunas excepciones.

Objetivo-C era originalmente un muy "delgado" capa encima de C, y sigue siendo un superconjunto estricto de C que permite la programación orientada a objetos utilizando un paradigma híbrido de escritura dinámica/estática. Objective-C deriva su sintaxis tanto de C como de Smalltalk: la sintaxis que implica preprocesamiento, expresiones, declaraciones de funciones y llamadas a funciones se hereda de C, mientras que la sintaxis para funciones orientadas a objetos se tomó originalmente de Smalltalk.

Además de C++ y Objective-C, Ch, Cilk y Unified Parallel C son casi superconjuntos de C.

Contenido relacionado

La ley de Linus

JUnit

Doctora V64

Más resultados...
Tamaño del texto: