C ( pronunciado / ˈs iː / – como la letra c ) [6] es un lenguaje de programación 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 características de C reflejan claramente las capacidades de las CPU de destino. Ha encontrado un uso duradero en el código de sistemas operativos (especialmente en kernels [7] ), controladores de dispositivos y pilas de protocolos , pero su uso en software de aplicación ha ido disminuyendo. [8] C se usa comúnmente en arquitecturas de computadoras 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 ejecutaban en Unix . Se aplicó para reimplementar el núcleo del sistema operativo Unix. [9] Durante la década de 1980, C ganó popularidad gradualmente. Se ha convertido en uno de los lenguajes de programación más utilizados, [10] [11] con compiladores de C disponibles para prácticamente todas las arquitecturas informáticas y sistemas operativos modernos. El libro The C Programming Language , coescrito por el diseñador original del lenguaje, sirvió durante muchos años como el estándar de facto para el lenguaje. [12] [1] C ha sido estandarizado desde 1989 por el American National Standards Institute (ANSI) y, posteriormente, conjuntamente por la Organización Internacional de Normalización (ISO) y la Comisión Electrotécnica Internacional (IEC).
C es un lenguaje procedimental imperativo , compatible con programación estructurada , alcance de variable léxica y recursión , con un sistema de tipos 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 instrucciones de máquina , todo con un soporte mínimo en tiempo de ejecución . A pesar de sus capacidades de bajo nivel, el lenguaje fue diseñado para fomentar la programación multiplataforma. Un programa C compatible con estándares escrito con la portabilidad en mente 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 situado constantemente entre los tres primeros lenguajes en el índice TIOBE , una medida de la popularidad de los lenguajes de programación. [13]
C es un lenguaje imperativo y procedimental en la tradición ALGOL . Tiene un sistema de tipos estático . En C, todo el código ejecutable está contenido dentro de 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. El paso por referencia se simula en C al pasar explícitamente punteros al elemento al que se hace referencia.
El texto fuente del programa C es un código de formato libre . Los puntos y comas terminan las sentencias , mientras que las llaves se utilizan para agrupar sentencias en bloques .
El lenguaje C también exhibe las siguientes características:
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 sigilo .+
, +=
, ++
, &
, ||
, etc.struct
) permiten acceder a elementos de datos relacionados y asignarlos como una unidad. El contenido de estructuras completas no se puede comparar utilizando un único operador integrado (los elementos se deben comparar individualmente).month[11]
.enum
palabra clave. Se pueden convertir libremente con números enteros.void
.static
y .extern
Si bien C no incluye ciertas características 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 ).
Muchos lenguajes posteriores han tomado prestado directa o indirectamente de C, incluyendo C++ , C# , C shell de Unix , D , Go , Java , JavaScript (incluyendo transpiladores ), Julia , Limbo , LPC , Objective-C , Perl , PHP , Python , Ruby , Rust , Swift , Verilog y SystemVerilog (lenguajes de descripción de hardware). [5] Estos lenguajes han tomado muchas de sus estructuras de control y otras características básicas de C. La mayoría de ellos también expresan una sintaxis muy similar a C, y tienden a combinar la sintaxis reconocible de expresión y declaración de C con sistemas de tipos subyacentes, modelos de datos y semántica que pueden ser radicalmente diferentes.
El origen de C está estrechamente ligado al desarrollo del sistema operativo Unix , implementado originalmente en lenguaje ensamblador en un PDP-7 por Dennis Ritchie y Ken Thompson , incorporando varias ideas de colegas. Finalmente, decidieron portar el sistema operativo a un PDP-11 . La versión original PDP-11 de Unix también se desarrolló en lenguaje ensamblador. [9]
Thompson quería un lenguaje de programación para desarrollar utilidades para la nueva plataforma. Al principio intentó escribir un compilador Fortran , pero pronto abandonó la idea. En su lugar, creó una versión reducida del lenguaje de programación de sistemas recientemente desarrollado llamado BCPL . La descripción oficial de BCPL no estaba disponible en ese momento, [14] y Thompson modificó la sintaxis para que fuera menos prolija y similar a un ALGOL simplificado conocido como SMALGOL. [ 15] Thompson llamó al resultado B. [9] Describió B como "semántica BCPL con mucha sintaxis SMALGOL". [15] Al igual que BCPL, B tenía un compilador de arranque para facilitar la portabilidad a nuevas máquinas. [15] Sin embargo, pocas utilidades se escribieron finalmente en B porque era demasiado lento y no podía aprovechar las características de PDP-11 como la direccionabilidad de bytes .
En 1971, Ritchie comenzó a mejorar B para utilizar las características del más potente PDP-11. Una adición significativa fue un tipo de datos de carácter, al que llamó New B (NB). [15] Thompson comenzó a utilizar NB para escribir el núcleo de Unix , y sus requisitos dieron forma a la dirección del desarrollo del lenguaje. [15] [16] Hasta 1972, se añadieron tipos más ricos al lenguaje NB: NB tenía matrices de int
y char
. También se añadieron punteros, la capacidad de generar punteros a otros tipos, matrices de todos los tipos y tipos que se devolverían desde funciones. Las matrices dentro de expresiones se convirtieron en punteros. Se escribió un nuevo compilador y el lenguaje pasó a llamarse C. [9]
El compilador de C y algunas utilidades creadas con él se incluyeron en la versión 2 de Unix , también conocida como Research Unix . [17]
En la versión 4 de Unix , lanzada en noviembre de 1973, el núcleo de Unix fue ampliamente reimplementado en C. [9] 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 . [9]
Unix fue uno de los primeros núcleos de sistemas operativos implementados en un lenguaje distinto del ensamblador . Ejemplos anteriores incluyen el sistema Multics (que fue escrito en PL/I ) y el Programa de Control Maestro (MCP) para el Burroughs B5000 (que fue escrito en ALGOL ) en 1961. Alrededor de 1977, Ritchie y Stephen C. Johnson realizaron más cambios al lenguaje para facilitar la portabilidad del sistema operativo Unix. El compilador C portátil de Johnson sirvió como base para varias implementaciones de C en nuevas plataformas. [16]
En 1978 Brian Kernighan y Dennis Ritchie publicaron la primera edición de The C Programming Language . [18] Conocido como K&R por las iniciales de sus autores, el libro 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 publicó en 1978, ahora también se conoce como C78 . [19] La segunda edición del libro [20] cubre el estándar ANSI C posterior , que se describe a continuación.
K&R introdujo varias características del lenguaje:
long int
tipo de datosunsigned int
tipo de datos=op
=-
op=
-=
i=-10
i =- 10
i
i = -10
i
Incluso después de la publicación del estándar ANSI de 1989, durante muchos años K&R C todavía se consideró el " mínimo común denominador " al que se restringieron los programadores de C cuando se deseaba la máxima portabilidad, ya que muchos compiladores antiguos todavía estaban en uso y porque el código K&R C cuidadosamente escrito también puede ser estándar C legal.
En las primeras versiones de C, solo las funciones que devuelven tipos distintos de int
debían declararse si se utilizaban 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:
long some_function (); /* 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. Debido a que esta es una versión temprana de C, hay un tipo 'int' implícito aquí. Un comentario muestra dónde se requeriría el especificador de tipo 'int' explícito en versiones posteriores. */ /* int */ calling_function () /* Esta es una definición de función, que incluye el cuerpo del código que sigue entre las { llaves }. Debido a que no se especifica ningún tipo de retorno, la función retorna implícitamente un 'int' en esta versión temprana de C. */ { long test1 ; register /* int */ test2 ; /* Nuevamente, note que 'int' no es requerido aquí. El especificador de tipo 'int' */ /* en el comentario sería requerido en versiones posteriores de C. */ /* La palabra clave 'register' indica al compilador que esta variable debería */ /* almacenarse idealmente en un registro en lugar de dentro del marco de la pila. */ test1 = some_function (); if ( test1 > 1 ) test2 = 0 ; else test2 = other_function (); return test2 ; }
Los int
especificadores de tipo que están comentados podrían omitirse en K&R C, pero son necesarios en estándares posteriores.
Dado que las declaraciones de funciones de K&R no incluían ninguna información sobre los argumentos de la función, no se realizaban comprobaciones de tipo de los parámetros de la función, aunque algunos compiladores emitían un mensaje de advertencia si se llamaba a una función local con una cantidad incorrecta de argumentos o si diferentes llamadas a una función externa utilizaban diferentes cantidades o tipos de argumentos. Se desarrollaron herramientas independientes, como la utilidad lint de Unix , que (entre otras cosas) podían comprobar la coherencia del uso de funciones en varios archivos fuente.
En los años posteriores a la publicación de K&R C, se añadieron varias características al lenguaje, respaldadas por compiladores de AT&T (en particular PCC [21] ) y otros proveedores. Entre ellas se incluyen:
void
funciones (es decir, funciones sin valor de retorno)struct
o union
tipos (anteriormente solo se podía devolver un único puntero, entero o flotante)struct
tipos de datos#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 estandarización. [22]
A finales de la década de 1970 y durante la de 1980, se implementaron versiones de C para una amplia variedad de computadoras mainframe , minicomputadoras y microcomputadoras , incluida la IBM PC , 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 C de Unix se entregó 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 conoce como ANSI C , Estándar C o, a veces, C89.
En 1990, la Organización Internacional de Normalización (ISO) adoptó la norma 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 de normalización nacionales, 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 suele producirse en el plazo de un año a partir de la publicación de la ISO.
Uno de los objetivos del proceso de estandarización de C era producir un superconjunto de K&R C, que incorporara muchas de las características no oficiales introducidas posteriormente. El comité de estándares también incluyó varias características adicionales, como prototipos de funciones (tomados de C++), punteros, compatibilidad con conjuntos de caracteres y configuraciones regionalesvoid
internacionales y mejoras del preprocesador. Aunque se amplió la sintaxis para las declaraciones de parámetros para incluir el estilo utilizado en C++, se siguió permitiendo la interfaz K&R, por compatibilidad con el código fuente existente.
Los compiladores de C actuales admiten C89 y la mayor parte del código C moderno se basa en él. Cualquier programa escrito únicamente en C estándar y sin suposiciones dependientes del hardware se ejecutará correctamente en cualquier plataforma con una implementación de 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 particular, debido, por ejemplo, al uso de bibliotecas no estándar, como bibliotecas GUI , o a una dependencia de atributos específicos del compilador o de la plataforma, como el tamaño exacto de los tipos de datos y el orden de bytes .
En los casos en que el código debe ser compilable por compiladores que cumplen con el estándar o basados en C K&R, la __STDC__
macro se puede utilizar para dividir el código en secciones estándar y K&R para evitar el uso en un compilador basado en C K&R de características disponibles solo en C estándar.
Después del proceso de estandarización ANSI/ISO, la especificación del lenguaje C permaneció relativamente estática durante varios años. En 1995, se publicó la Enmienda Normativa 1 a la norma C de 1990 (ISO/IEC 9899/AMD1:1995, conocida informalmente como C95), para corregir algunos detalles y agregar un soporte más amplio para conjuntos de caracteres internacionales. [23]
La norma C fue revisada nuevamente a fines de la década de 1990, lo que llevó a la publicación de la norma ISO/IEC 9899:1999 en 1999, conocida comúnmente como " C99 ". Desde entonces ha sido modificada tres veces mediante Corrigenda Técnicas. [24]
La C99 introdujo varias características nuevas, incluidas funciones en línea , varios tipos de datos nuevos (incluidos long long int
y un complex
tipo para representar números complejos ), matrices de longitud variable y miembros de matriz flexibles , compatibilidad mejorada con el punto flotante IEEE 754 , compatibilidad con macros variádicas (macros de aridad variable ) y compatibilidad con comentarios de una línea que comienzan con //
, como en BCPL o C++. Muchas de estas ya se habían implementado como extensiones en varios compiladores de C.
C99 es en su mayor parte compatible con versiones anteriores de C90, pero es más estricto en algunos aspectos; en particular, una declaración que carece de un especificador de tipo ya no tiene int
implícitamente asumido. Una macro estándar __STDC_VERSION__
se define con valor 199901L
para indicar que está disponible el soporte de C99. GCC , Solaris Studio y otros compiladores de C ahora [ ¿cuándo? ] admiten muchas o todas las nuevas características 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 . [25] [ necesita actualización ]
Además, el estándar C99 requiere soporte para identificadores que utilicen Unicode en forma de caracteres de escape (por ejemplo, \u0040
o \U0001f431
) y sugiere soporte para nombres Unicode sin formato.
En 2007 se comenzó a trabajar en otra revisión del estándar C, denominada informalmente "C1X" hasta su publicación oficial, ISO/IEC 9899:2011, el 8 de diciembre de 2011. El comité de estándares C adoptó directrices para limitar la adopción de nuevas características que no habían sido probadas por implementaciones existentes.
El estándar C11 añade numerosas características nuevas a C y a la biblioteca, entre las que se incluyen macros genéricas de tipos, estructuras anónimas, compatibilidad mejorada con Unicode, operaciones atómicas, subprocesamiento múltiple y funciones con comprobació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 para 201112L
indicar que se encuentra disponible la compatibilidad con C11.
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 características del lenguaje, solo correcciones técnicas y aclaraciones sobre defectos en C11. La macro estándar __STDC_VERSION__
se define para 201710L
indicar que se encuentra disponible la compatibilidad con C17.
C23 es el nombre informal de la siguiente revisión importante del estándar del lenguaje C (después de C17). Se lo conocía informalmente como "C2X" durante la mayor parte de su desarrollo. Se espera que C23 se publique a principios de 2024 como ISO/IEC 9899:2024. [26] La macro estándar __STDC_VERSION__
se define para 202311L
indicar que el soporte de C23 está disponible.
C2Y es un nombre informal temporal para la próxima revisión importante del estándar del lenguaje C, después de C23 (C2X), que se espera que se publique más adelante en la década de 2020, de ahí el '2' en "C2Y". Un borrador preliminar de trabajo de C2Y fue publicado en febrero de 2024 como N3220 por el grupo de trabajo ISO/IEC JTC1/SC22 /WG14. [27]
Históricamente, la programación C integrada requiere extensiones no estándar del lenguaje C para soportar características exóticas como aritmética de punto fijo , múltiples bancos de memoria distintos y operaciones básicas de E/S.
En 2008, el Comité de Estándares C publicó un informe técnico que ampliaba el lenguaje C [28] para abordar estos problemas al proporcionar un estándar común al que debían adherirse todas las implementaciones. Incluye una serie de características que no están disponibles en el lenguaje C normal, como la aritmética de punto fijo , los espacios de direcciones con nombre y el direccionamiento básico de hardware de E/S.
C tiene una gramática formal especificada por el estándar C. [29] Los finales de línea generalmente no son significativos en C; sin embargo, los límites de línea sí tienen importancia durante la fase de preprocesamiento. Los comentarios pueden aparecer entre los delimitadores /*
y */
, o (desde C99) a continuación //
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 literales de cadena o de caracteres. [30]
Los archivos fuente de C contienen declaraciones y definiciones de funciones. Las definiciones de funciones, a su vez, contienen declaraciones y sentencias . Las declaraciones definen nuevos tipos utilizando palabras clave como struct
, union
, y enum
, o bien asignan tipos a y quizás reservan almacenamiento para nuevas variables, normalmente escribiendo el tipo seguido del nombre de la variable. Las palabras clave como char
y int
especifican tipos integrados. Las secciones de código se encierran entre llaves ( {
y }
, a veces llamadas "llaves") para limitar el alcance de las declaraciones y para actuar como una única sentencia para las estructuras de control.
Como lenguaje imperativo, C utiliza sentencias para especificar acciones. La sentencia más común es una sentencia de expresión , que consiste en una expresión 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 está respaldada por if
... [ else
] ejecución condicional y por do
... while
, while
, y for
ejecución iterativa (bucle). La for
sentencia tiene expresiones de inicialización, prueba y reinicialización independientes, cualquiera o todas las cuales se pueden omitir. break
y continue
se puede utilizar dentro del bucle. Break se utiliza para salir de la sentencia de bucle que encierra más internamente y continue se utiliza para saltar a su reinicialización. También hay una goto
sentencia no estructurada que se ramifica directamente a la etiqueta designada dentro de la función. switch
selecciona una case
para ser ejecutada en función del valor de una expresión entera. A diferencia de muchos otros lenguajes, el flujo de control pasará al siguiente case
a menos que finalice con un break
.
Las expresiones pueden utilizar una variedad de operadores integrados y pueden contener llamadas a funciones. El orden en el que se evalúan los argumentos de las funciones y los operandos de la mayoría de los operadores no está especificado. Las evaluaciones pueden incluso estar intercaladas. Sin embargo, todos los efectos secundarios (incluido el almacenamiento en variables) se producirán antes del siguiente " punto de secuencia "; los puntos de secuencia incluyen el final de cada declaración de expresión y la entrada y el retorno de cada llamada a función. Los puntos de secuencia también se producen durante la evaluación de expresiones que contienen ciertos operadores ( &&
, ||
, ?:
y el operador de 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 el necesario para otros lenguajes de programación.
Kernighan y Ritchie dicen en la Introducción del lenguaje de programación C : "C, como cualquier otro lenguaje, tiene sus defectos. Algunos de los operadores tienen una precedencia incorrecta; algunas partes de la sintaxis podrían ser mejores". [31] El estándar C no intentó corregir muchos de estos defectos, debido al impacto de dichos cambios en el software ya existente.
El conjunto de caracteres fuente básico de C incluye los siguientes caracteres:
a
– z
A
–Z
0
–9
! " # % & ' ( ) * + , - . / : ; < = > ? [ \ ] ^ _ { | } ~
La nueva línea indica el final de una línea de texto; no necesita corresponder a un único carácter real, aunque por conveniencia C lo trata como tal.
Se pueden utilizar caracteres codificados multibyte adicionales en literales de cadena, pero no son completamente portables . El último estándar de C ( C11 ) permite que caracteres Unicode multinacionales se incorporen de manera portable dentro del texto fuente de C mediante el uso de \uXXXX
o \UXXXXXXXX
codificación (donde X
denota un carácter hexadecimal), aunque esta característica aún no está ampliamente implementada.
El conjunto de caracteres de ejecución básico de C contiene los mismos caracteres, junto con representaciones de alerta , retroceso y retorno de carro . La compatibilidad en tiempo de ejecución con conjuntos de caracteres extendidos ha aumentado con cada revisión del estándar C.
Las siguientes palabras reservadas distinguen entre mayúsculas y minúsculas .
C89 tiene 32 palabras reservadas, también conocidas como palabras clave, que son las palabras que no se pueden utilizar para ningún otro propósito que no sea aquel para el que están predefinidas:
C99 reservó cinco palabras más: (‡ es un alias ortográfico alternativo para una palabra clave C23)
C11 reservó siete palabras más: [32] (‡ es un alias ortográfico alternativo para una palabra clave C23)
_Alignas
‡_Alignof
‡_Atomic
_Generic
_Noreturn
_Static_assert
‡_Thread_local
‡C23 reservó quince palabras más:
alignas
alignof
bool
constexpr
false
nullptr
static_assert
thread_local
true
typeof
typeof_unqual
_BitInt
_Decimal32
_Decimal64
_Decimal128
La mayoría de las palabras recientemente reservadas comienzan con un guión bajo seguido de una letra mayúscula, porque los identificadores de esa forma estaban previamente reservados por el estándar C para su uso exclusivo en implementaciones. Dado que el código fuente de los programas existentes no debería haber estado utilizando 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 con guión bajo. Algunas de esas palabras se agregaron como palabras clave con su ortografía convencional en C23 y se eliminaron las macros correspondientes.
Antes de C89, entry
estaba reservada como palabra clave. En la segunda edición de su libro The C Programming Language (El lenguaje de programación C ), que describe lo que se conocería como C89, Kernighan y Ritchie escribieron: "La ... [palabra clave] entry
, anteriormente reservada pero nunca utilizada, ya no está reservada" y "La entry
palabra clave que nació muerta se retira". [33]
C admite un amplio conjunto de operadores , que son símbolos que se utilizan dentro de una expresión para especificar las manipulaciones que se deben realizar al evaluar esa expresión. C tiene operadores para:
=
+=
, , , , , , , , , -=
*=
/=
%=
&=
|=
^=
<<=
>>=
~
, &
, |
,^
<<
,>>
!
, &&
,||
( )
++
,--
.
,->
sizeof
typeof
, typeof_unqual
desde C23<
, <=
, >
,>=
&
, *
,[ ]
( )
(typename)
C utiliza 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 utiliza 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 producen advertencias). Por ejemplo, la expresión condicional if (a == b + 1)
podría escribirse por error como if (a = b + 1)
, que se evaluará como true
a menos que el valor de a
sea 0
posterior a la asignación. [34]
La precedencia de los operadores en C no siempre es intuitiva. Por ejemplo, el operador ==
se vincula de forma más estricta que (se ejecuta antes que) los operadores &
(AND bit a bit) y |
(OR bit a bit) en expresiones como x & 1 == 0
, que deben escribirse como (x and 1) == 0
si esa fuera la intención del codificador. [35]
El ejemplo " hola, mundo ", que apareció en la primera edición de K&R , se ha convertido en el modelo para un programa introductorio en la mayoría de los libros de texto de programación. El programa imprime "hola, mundo" en la salida estándar , que suele ser una terminal o una pantalla.
La versión original fue: [36]
main () { printf ( "hola, mundo \n " ); }
Un programa "hola, mundo" que cumple con el estándar es: [a]
# incluir <stdio.h> int main ( void ) { printf ( "hola, mundo \n " ); }
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 stdio.h
encabezado estándar, que contiene declaraciones para funciones de entrada y salida estándar como printf
y scanf
. Los corchetes angulares que rodean stdio.h
indican que stdio.h
se puede ubicar utilizando una estrategia de búsqueda que prefiere los encabezados proporcionados con el compilador a otros encabezados que tienen el mismo nombre, en lugar de las comillas dobles que generalmente incluyen archivos de encabezado locales o específicos del proyecto.
La siguiente línea indica que main
se está definiendo una función denominada. La main
función cumple una función especial en los programas C; el entorno de ejecución llama a la main
función 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 ejecución) como resultado de la evaluación de la main
función es un entero. La palabra clave void
como lista de parámetros indica que esta función no acepta argumentos. [b]
La llave de apertura indica el comienzo de la definición de la main
función.
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, se pasaprintf
(se proporciona) a la función un único argumento, la dirección del primer carácter en el literal de cadena . El literal de cadena es una matriz sin nombre con elementos de tipo , configurado automáticamente por el compilador con un carácter NULL final (valor ASCII 0) para marcar el final de la matriz (para saber la longitud de la cadena). El carácter NULL también se puede escribir como una secuencia de escape , escrita como . El 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 es de tipo , pero se descarta silenciosamente ya que no se usa. (Un programa más cuidadoso podría probar el valor de retorno para determinar si la función tuvo éxito o no). El punto y coma termina la declaración. "hello, world\n"
char
printf
\0
\n
printf
int
printf
;
La llave de cierre indica el final del código de la main
función. Según la especificación C99 y posteriores, la main
función, a diferencia de cualquier otra función, devolverá implícitamente un valor de 0
al alcanzar el }
que finaliza la función. (Anteriormente return 0;
se requería una declaración explícita). Esto es interpretado por el sistema de tiempo de ejecución como un código de salida que indica una ejecución exitosa. [37]
El sistema de tipos en C es estático y débilmente tipado , lo que lo hace similar al sistema de tipos de los descendientes de ALGOL como Pascal . [38] Hay tipos integrados para números enteros de varios tamaños, tanto con signo como sin signo, números de punto 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 utiliza a menudo en la programación de sistemas de bajo nivel en la que 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 maneras, ya sea mediante una conversión de tipos para convertir explícitamente un valor de un tipo a otro, o mediante el uso de punteros o uniones para reinterpretar los bits subyacentes de un objeto de datos de alguna otra manera.
Algunos consideran que la sintaxis de declaración de C no es intuitiva, en particular para los punteros de función . (La idea de Ritchie era declarar identificadores en contextos que se asemejaran a su uso: " la declaración refleja el uso ".) [39]
Las conversiones aritméticas habituales de C permiten generar código eficiente, pero a veces pueden producir resultados inesperados. Por ejemplo, una comparación de números 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 con signo es negativo.
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 apuntan o para invocar una función a la que apuntan. 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 es normalmente una dirección de memoria sin formato (quizás aumentada por un campo de desplazamiento dentro de la palabra), pero dado que el tipo de un puntero incluye el tipo de la cosa a la que apuntan, las expresiones que incluyen punteros se pueden comprobar en tiempo de compilación. La aritmética de punteros se escala automáticamente según el tamaño del tipo de datos al que apuntan.
Los punteros se utilizan para muchos propósitos en C. Las cadenas de texto se manipulan comúnmente utilizando punteros en matrices de caracteres. La asignación de memoria dinámica se realiza utilizando punteros; el resultado de a malloc
generalmente se convierte al tipo de datos de los datos que se almacenarán. Muchos tipos de datos, como árboles , se implementan comúnmente como struct
objetos asignados dinámicamente vinculados entre sí mediante punteros. Los punteros a otros punteros se utilizan a menudo en matrices multidimensionales y matrices de struct
objetos. 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 . [37]
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 la ausencia de un puntero "próximo" en el nodo final de una lista enlazada , o como una indicación de error de 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, como la NULL
macro definida por varios encabezados estándar o, desde C23, con la constante nullptr
. En contextos condicionales, los valores de puntero nulo evalúan como false
, mientras que todos los demás valores de puntero evalúan como true
.
Los punteros void ( void *
) apuntan a objetos de un tipo no especificado y, por lo tanto, pueden utilizarse como punteros de datos "genéricos". Dado que no se conoce el tamaño ni el tipo del objeto al que apuntan, no se puede desreferenciar a los punteros void ni se permite la aritmética de punteros sobre ellos, aunque se pueden convertir fácilmente (y en muchos contextos se convierten de manera implícita) a y desde cualquier otro tipo de puntero de objeto. [37]
El uso descuidado de punteros es potencialmente peligroso. Debido a que normalmente no se controlan, una variable de puntero puede apuntar a cualquier ubicación arbitraria, lo que puede causar efectos no deseados. Aunque los punteros utilizados correctamente apuntan a lugares seguros, se puede hacer que apunten a lugares inseguros mediante el uso de aritmética de punteros no válida ; los objetos a los que apuntan pueden seguir utilizándose después de la desasignación ( punteros colgantes ); pueden usarse sin haber sido inicializados ( punteros salvajes ); 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 en lo que respecta a permitir la manipulación y conversión entre tipos de puntero, aunque los compiladores suelen proporcionar opciones para varios niveles de comprobación. Algunos otros lenguajes de programación abordan estos problemas mediante el uso de tipos de referencia más restrictivos .
Los tipos de matriz en C son tradicionalmente de un tamaño fijo y estático que se especifica en tiempo de compilación. El estándar C99 más reciente también permite una forma de matriz 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 malloc
función de la biblioteca estándar, y tratarlo como una matriz.
Dado que siempre se accede a las matrices (en efecto) a través de punteros, los accesos a las matrices normalmente no se verifican con el tamaño de la matriz subyacente, aunque algunos compiladores pueden proporcionar la verificación de límites como una opción. [40] [41] Por lo tanto, las violaciones de los límites de la matriz son posibles y pueden llevar a varias repercusiones, incluidos accesos ilegales a la memoria, corrupción de datos, desbordamientos de 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 recursión dentro del sistema de tipos para declarar matrices de matrices, lo que efectivamente logra lo mismo. Los valores de índice de la "matriz multidimensional" resultante se pueden considerar como crecientes en orden de fila principal . Las matrices multidimensionales se utilizan comúnmente en algoritmos numéricos (principalmente de álgebra lineal aplicada ) para almacenar matrices. La estructura de la matriz de C es muy adecuada para 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 matrices de matrices de tamaño dinámico mediante indexación doble. (Una solución alternativa para esto fue asignar la matriz con un "vector de fila" adicional de punteros a las columnas). C99 introdujo "matrices de longitud variable" que solucionan este problema.
El siguiente ejemplo que utiliza C moderno (C99 o posterior) muestra la asignación de una matriz bidimensional en el montón y el uso de indexación de matriz multidimensional para accesos (que pueden usar verificación de límites en muchos compiladores de C):
int func ( int N , int M ) { float ( * p )[ N ] [ M ] = malloc ( sizeof * p ); if ( p == 0 ) return -1 ; for ( int i = 0 ; i < N ; i ++ ) for ( int j = 0 ; j < M ; j ++ ) ( * p )[ i ] [ j ] = i + j ; print_array ( N , M , p ); free ( p ); return 1 ; }
Y aquí hay una implementación similar que utiliza la función Auto VLA de C99 : [c]
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 VLA automáticos y esté dentro del tamaño disponible de la pila. float p [ N ] [ M ]; // el VLA automático se mantiene en la pila y se dimensiona cuando se invoca la función for ( int i = 0 ; i < N ; i ++ ) for ( int j = 0 ; j < M ; j ++ ) p [ i ] [ j ] = i + j ; print_array ( N , M , p ); // no es necesario free(p) ya que desaparecerá cuando la función salga, junto con el resto del marco de la pila return 1 ; }
La notación de subíndice x[i]
(donde x
designa un puntero) es una sintaxis simplificada para *(x+i)
. [42] Aprovechando el conocimiento del compilador sobre el tipo de puntero, la dirección a la que x + i
apunta no es la dirección base (a la que apunta x
) incrementada en i
bytes, sino que se define como la dirección base incrementada por i
multiplicada por el tamaño de un elemento al que x
apunta. Por lo tanto, x[i]
designa el i+1
elemento n de la matriz.
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 la 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 realidad 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 desreferenciado de una matriz A
, como en n = sizeof A[0]
. Por lo tanto, la cantidad 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.
Una de las funciones más importantes de un lenguaje de programación es proporcionar facilidades para gestionar la memoria y los objetos que se almacenan en ella. C ofrece tres formas principales de asignar memoria a los objetos: [37]
malloc
desde una región de memoria llamada montón ; estos bloques persisten hasta que se liberan posteriormente para su reutilización llamando a la función de biblioteca realloc
ofree
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 tener potencialmente una gran cantidad de 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 entre las llamadas de función, la asignación automática es fácil de usar pero el espacio de pila es típicamente mucho más limitado y transitorio que la memoria estática o el espacio de montón, y la asignación de memoria dinámica permite la asignación conveniente de objetos cuyo tamaño se conoce solo en tiempo de ejecución. La mayoría de los programas C hacen un uso extensivo de los tres.
Siempre que sea posible, la asignación automática o estática suele ser la más sencilla, ya que el compilador gestiona el almacenamiento, lo que libera al programador de la tarea, potencialmente propensa a errores, de asignar y liberar el 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 anteriores a 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. [37] Antes del estándar C99, las matrices de tamaño variable eran un ejemplo común de esto. (Consulte el artículo sobre malloc
para ver un ejemplo de matrices asignadas 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 de puntero nulo) cuando no se puede asignar el almacenamiento requerido. (La asignación estática que es demasiado grande suele ser detectada por el enlazador o el cargador , antes de que el programa pueda siquiera comenzar la ejecución).
A menos que se especifique lo contrario, los objetos estáticos contienen valores de puntero nulos o cero al iniciar el programa. Los objetos asignados de forma automática y dinámica se inicializan solo si se especifica explícitamente un valor inicial; de lo contrario, inicialmente tienen valores indeterminados (normalmente, cualquier patrón de bits que esté presente en el almacenamiento , que podría no representar ni siquiera 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 producirse tanto falsos positivos como falsos negativos .
La asignación de memoria del montón debe estar sincronizada con su uso real en cualquier programa para poder reutilizarla tanto como sea posible. Por ejemplo, si el único puntero a una asignación de memoria del montón queda fuera de alcance o se sobrescribe su valor antes de que se desasigne explícitamente, entonces esa memoria no se puede recuperar para su posterior reutilización y esencialmente se pierde para el programa, un fenómeno conocido como pérdida de memoria . Por el contrario, es posible que se libere memoria, pero que se haga referencia a ella posteriormente, lo que lleva a resultados impredecibles. Normalmente, los síntomas de error aparecen en una parte del programa no relacionada con el código que causa el error, lo que dificulta el diagnóstico del error. Estos problemas se mejoran en lenguajes con recolección automática de basura .
El lenguaje de programación C utiliza bibliotecas como su principal método de extensión. En C, una biblioteca es un conjunto de funciones contenidas dentro de un único archivo "archivado". Cada biblioteca normalmente tiene un archivo de encabezado , que contiene los prototipos de las funciones contenidas en la biblioteca que pueden ser utilizadas por un programa, y las declaraciones de tipos de datos especiales y símbolos de macro utilizados con estas funciones. Para que un programa utilice 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 matemática"). [37]
La biblioteca C más común es la biblioteca estándar C , que está especificada por los estándares C ISO y ANSI y viene con cada implementación C (las implementaciones que apuntan a entornos limitados, como sistemas integrados, pueden proporcionar solo un subconjunto de la biblioteca estándar). Esta biblioteca admite entrada y salida de flujo, asignación de memoria, matemáticas, cadenas de caracteres y valores de tiempo. Varios encabezados estándar separados (por ejemplo, stdio.h
) especifican las interfaces para estas y otras funciones de la biblioteca estándar.
Otro conjunto común de funciones de la biblioteca C son las que utilizan las aplicaciones específicamente diseñadas para sistemas Unix y similares , especialmente las funciones que proporcionan una interfaz al núcleo . Estas funciones se detallan en varios estándares como POSIX y la Especificación Única de UNIX .
Dado que muchos programas se han escrito en C, existe una amplia variedad de otras bibliotecas disponibles. Las bibliotecas suelen escribirse en C porque los compiladores de C generan código objeto eficiente ; luego, los programadores crean interfaces con la biblioteca para que las rutinas se puedan usar desde lenguajes de nivel superior como Java , Perl y Python . [37]
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 (por ejemplo, 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 a través de la asociación de un flujo a un archivo. En la biblioteca estándar de C, se utiliza temporalmente un búfer (un área de memoria o cola) para almacenar datos antes de que se envíen 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 son parte de la biblioteca estándar de C [ aclaración necesaria ] pero generalmente son parte de la programación "bare metal" (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.
Se han desarrollado varias herramientas para ayudar a los programadores de C a encontrar y corregir sentencias con un comportamiento indefinido o expresiones posiblemente erróneas, con un rigor mayor que el proporcionado por el compilador. La herramienta lint fue la primera de ellas, y dio origen a muchas otras.
La verificación y auditoría automáticas del código fuente son beneficiosas en cualquier lenguaje, y para C existen muchas herramientas de este tipo, como Lint . Una práctica común es utilizar Lint para detectar código cuestionable cuando se escribe un programa por primera vez. Una vez que un programa pasa Lint, se compila utilizando el compilador de C. Además, muchos compiladores pueden advertir opcionalmente sobre construcciones sintácticamente válidas que es probable que en realidad sean errores. MISRA C es un conjunto propietario de pautas para evitar ese código cuestionable, desarrollado para sistemas integrados. [43]
También hay compiladores, bibliotecas y mecanismos a nivel de 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 memoria y la recolección automática de basura .
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. [44] [45]
C se utiliza ampliamente para la programación de sistemas en la implementación de sistemas operativos y aplicaciones de sistemas integrados . [46] Esto se debe a varias razones:
malloc
; free
un mecanismo más sofisticado con arenas ; o una versión para un núcleo de sistema operativo que puede adaptarse a DMA , usarse dentro de controladores de interrupciones o integrarse con el sistema de memoria virtual .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 programas de gran intensidad computacional. Por ejemplo, la Biblioteca de aritmética de precisión múltiple de GNU , la Biblioteca científica de GNU , Mathematica y MATLAB están escritas total o parcialmente en C. Muchos lenguajes admiten la llamada de funciones de biblioteca en C; por ejemplo, el marco de trabajo NumPy basado en Python utiliza C para los aspectos de alto rendimiento e interacción con hardware.
En ocasiones, C se utiliza como lenguaje intermedio en las implementaciones de otros lenguajes. Este enfoque se puede utilizar por razones de portabilidad o conveniencia; al utilizar C como lenguaje intermedio, no son necesarios generadores de código específicos de la máquina adicionales. 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 interfaces para muchos lenguajes, incluido 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. [47] Por ejemplo, las implementaciones de referencia de Python , [48] Perl , [49] Ruby , [50] y PHP [51] están escritas en C.
Históricamente, C se ha utilizado a veces para el desarrollo web utilizando la Interfaz de Puerta de Enlace Común (CGI) como una "puerta de enlace" para la información entre la aplicación web, el servidor y el navegador. [52] C puede haber sido elegido en lugar de los lenguajes interpretados debido a su velocidad, estabilidad y disponibilidad casi universal. [53] Ya no es una práctica común que el desarrollo web se realice en C, [54] y existen muchas otras herramientas de desarrollo web .
Los dos servidores web más populares , Apache HTTP Server y Nginx , están escritos en C. Estos servidores web interactúan con el sistema operativo, escuchan en los puertos TCP las solicitudes HTTP y luego ofrecen contenido web estático o provocan la ejecución de otros lenguajes que manejan el "renderizado" de contenido, como PHP , que está escrito principalmente en C. El enfoque cercano al metal de C permite la construcción de estos sistemas de software de alto rendimiento.
C también se ha utilizado ampliamente para implementar aplicaciones para el usuario final . [55] Sin embargo, dichas aplicaciones también pueden escribirse en lenguajes más nuevos y de nivel superior.
El poder del lenguaje ensamblador y la conveniencia del lenguaje ensamblador
—Dennis Ritchie [56]
Si bien el lenguaje C ha sido popular, influyente y enormemente exitoso, tiene desventajas, entre ellas:
malloc
y free
es propenso a errores. Entre los errores se incluyen: pérdidas de memoria cuando se asigna memoria pero no se libera y acceso a memoria previamente liberada.scanf
o strncat
, pueden provocar desbordamientos de búfer .#pragma
, [57] [58] y algunas con palabras clave adicionales, por ejemplo use __cdecl
calling Convention. Pero la directiva y las opciones no son compatibles de manera consistente. [59]setjmp
ylongjmp
se han utilizado [61] para implementar un mecanismo try-catch mediante macros.Para algunos propósitos, se han adoptado estilos restringidos de C, por ejemplo, MISRA C o CERT C , en un intento de reducir la posibilidad de errores. Las bases de datos como CWE intentan contar las formas en que C, etc., tiene vulnerabilidades, junto con recomendaciones para mitigarlas.
Existen herramientas que pueden mitigar algunos de los inconvenientes. Los compiladores de C actuales incluyen comprobaciones que pueden generar advertencias para ayudar a identificar muchos errores potenciales.
C ha influido tanto directa como indirectamente en muchos lenguajes posteriores como C++ y Java . [63] La influencia más generalizada ha sido sintáctica; todos los lenguajes mencionados combinan la sintaxis de declaración y expresión (más o menos reconocible) de C con sistemas de tipos, modelos de datos o estructuras de programas a gran escala que difieren de los de C, a veces radicalmente.
Existen varios intérpretes de C o similares, incluidos Ch y CINT , que también se pueden utilizar para crear scripts.
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 de código fuente a código fuente ; el código fuente se traducía a C y luego se compilaba con un compilador de C. [64]
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 C. [65] C++ agrega mayor fuerza de tipado, 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 [ ¿cuándo? ] admite la mayor parte de C, con unas pocas excepciones .
Objective-C era originalmente una capa muy "delgada" sobre C, y sigue siendo un superconjunto estricto de C que permite la programación orientada a objetos utilizando un paradigma híbrido de tipado dinámico/estático. 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 las características 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.
main
función tiene dos argumentos, int argc
y char *argv[]
, respectivamente, que se pueden usar para manejar argumentos de línea de comandos . El estándar ISO C (sección 5.1.2.2.1) requiere main
que se admitan ambas formas de , lo que es un tratamiento especial que no se brinda a ninguna otra función.print_array
(no se muestra) difiere ligeramente, [ ¿por qué? ] también.Década de 1980: se presenta por primera vez Verilog; Verilog se inspira en el lenguaje de programación C