Buscar..


Introducción

El lenguaje C es tradicionalmente un lenguaje compilado (en lugar de interpretado). El Estándar C define las fases de traducción , y el producto de su aplicación es una imagen del programa (o un programa compilado). En , las fases se enumeran en §5.1.1.2.

Observaciones

Extensión de nombre de archivo Descripción
.c Archivo fuente. Por lo general contiene definiciones y código.
.h Archivo de cabecera. Suele contener declaraciones.
.o Archivo de objeto Código compilado en lenguaje máquina.
.obj Extensión alternativa para archivos de objetos.
.a Archivo de la biblioteca. Paquete de archivos de objeto.
.dll Biblioteca de enlace dinámico en Windows.
.so Objeto compartido (biblioteca) en muchos sistemas similares a Unix.
.dylib Dynamic-Link Library en OSX (variante Unix).
.exe , .com Archivo ejecutable de Windows. Formado por la vinculación de archivos de objetos y archivos de la biblioteca. En sistemas similares a Unix, no hay una extensión de nombre de archivo especial para el archivo ejecutable.
POSIX c99 banderas del compilador Descripción
-o filename Nombre del archivo de salida, por ejemplo. ( bin/program.exe , program )
-I directory búsqueda de encabezados en direrctory .
-D name definir name macro
-L directory búsqueda de bibliotecas en el directory .
-l name biblioteca de enlaces libname .

Los compiladores en plataformas POSIX (Linux, mainframes, Mac) generalmente aceptan estas opciones, incluso si no se llaman c99 .

Banderas GCC (Colección de compiladores GNU) Descripción
-Wall Permite que todos los mensajes de advertencia que se aceptan comúnmente sean útiles.
-Wextra Habilita más mensajes de advertencia, puede ser demasiado ruidoso.
-pedantic Forzar advertencias donde el código viole el estándar elegido.
-Wconversion Habilite las advertencias sobre la conversión implícita, use con cuidado.
-c Compila archivos fuente sin vincularlos.
-v Imprime la información de la compilación.
  • gcc acepta los indicadores POSIX y muchos otros.
  • Muchos otros compiladores en plataformas POSIX ( clang , compiladores específicos del proveedor) también usan los indicadores que se enumeran anteriormente.
  • Vea también Invocar GCC para muchas más opciones.
Banderas TCC (Compilador C) Descripción
-Wimplicit-function-declaration Avisar sobre la declaración de función implícita.
-Wunsupported Avisar sobre las funciones de GCC no compatibles que TCC ignora.
-Wwrite-strings Haga que las constantes de cadena sean de tipo const char * en lugar de char *.
-Werror Abortar la compilación si se emiten advertencias.
-Wall Active todas las advertencias, excepto las -Werror , -Wunusupported y -Wwrite strings .

El enlazador

El trabajo del enlazador es vincular un grupo de archivos de objetos (archivos .o ) en un ejecutable binario. El proceso de vinculación implica principalmente resolver direcciones simbólicas a direcciones numéricas . El resultado del proceso de enlace es normalmente un programa ejecutable.

Durante el proceso de enlace, el vinculador recogerá todos los módulos de objeto especificados en la línea de comandos, agregará un código de inicio específico del sistema al frente e intentará resolver todas las referencias externas en el módulo de objeto con definiciones externas en otros archivos de objeto (archivos de objeto se puede especificar directamente en la línea de comandos o se puede agregar implícitamente a través de bibliotecas). A continuación, asignará direcciones de carga para los archivos de objeto, es decir, especifica dónde terminarán el código y los datos en el espacio de direcciones del programa terminado. Una vez que tiene las direcciones de carga, puede reemplazar todas las direcciones simbólicas en el código del objeto con direcciones numéricas "reales" en el espacio de direcciones del objetivo. El programa está listo para ser ejecutado ahora.

Esto incluye tanto los archivos de objetos que el compilador creó a partir de los archivos de código fuente como los archivos de objetos que se han compilado previamente y que se han recopilado en archivos de biblioteca. Estos archivos tienen nombres que terminan en .a o .so , y normalmente no necesita conocerlos, ya que el enlazador sabe dónde se encuentran la mayoría de ellos y los vinculará automáticamente según sea necesario.

Invocación implícita del enlazador

Al igual que el preprocesador, el enlazador es un programa separado, a menudo llamado ld (pero Linux usa collect2 , por ejemplo). Al igual que el preprocesador, el vinculador se invoca automáticamente cuando utiliza el compilador. Por lo tanto, la forma normal de utilizar el enlazador es la siguiente:

% gcc foo.o bar.o baz.o -o myprog

Esta línea le dice al compilador que vincule tres archivos de objetos ( foo.o , bar.o y baz.o ) en un archivo ejecutable binario llamado myprog . Ahora tiene un archivo llamado myprog que puede ejecutar y que, con suerte, hará algo interesante y / o útil.

Invocación explícita del enlazador

Es posible invocar el enlazador directamente, pero esto rara vez es recomendable, y suele ser muy específico de la plataforma. Es decir, las opciones que funcionan en Linux no necesariamente funcionarán en Solaris, AIX, macOS, Windows y de manera similar para cualquier otra plataforma. Si trabaja con GCC, puede usar gcc -v para ver qué se ejecuta en su nombre.

Opciones para el enlazador.

El enlazador también toma algunos argumentos para modificar su comportamiento. El siguiente comando le diría a gcc que vincule foo.o y bar.o , pero que también incluya la biblioteca ncurses .

% gcc foo.o bar.o -o foo -lncurses

Esto es en realidad (más o menos) equivalente a

% gcc foo.o bar.o /usr/lib/libncurses.so -o foo

(aunque libncurses.so podría ser libncurses.a , que es solo un archivo creado con ar ). Tenga en cuenta que debe enumerar las bibliotecas (ya sea por nombre de ruta o mediante las opciones -lname ) después de los archivos de objeto. Con las bibliotecas estáticas, el orden en que se especifican importa; A menudo, con bibliotecas compartidas, el orden no importa.

Tenga en cuenta que en muchos sistemas, si está utilizando funciones matemáticas (de <math.h> ), debe especificar -lm para cargar la biblioteca de matemáticas, pero Mac OS X y macOS Sierra no lo requieren. Hay otras bibliotecas que son bibliotecas separadas en Linux y otros sistemas Unix, pero no en macOS - POSIX threads, y POSIX en tiempo real, y las bibliotecas de red son ejemplos. En consecuencia, el proceso de vinculación varía entre plataformas.

Otras opciones de compilación

Esto es todo lo que necesita saber para comenzar a compilar sus propios programas en C. En general, también recomendamos que use la opción de línea de comando -Wall :

% gcc -Wall -c foo.cc

La opción -Wall hace que el compilador le advierta sobre construcciones de código legales pero dudosas, y lo ayudará a detectar muchos errores muy pronto.

Si desea que el compilador le envíe más advertencias (incluidas las variables declaradas pero no utilizadas, olvidándose de devolver un valor, etc.), puede usar este conjunto de opciones, ya que -Wall , a pesar del nombre, no gira todas las posibles advertencias sobre:

% gcc -Wall -Wextra -Wfloat-equal -Wundef -Wcast-align -Wwrite-strings -Wlogical-op \
>     -Wmissing-declarations -Wredundant-decls -Wshadow …

Tenga en cuenta que clang tiene una opción: -Weverything que realmente -Weverything todas las advertencias en clang .

Tipos de archivo

La compilación de programas en C requiere que trabajes con cinco tipos de archivos:

  1. Archivos de origen : estos archivos contienen definiciones de funciones y tienen nombres que terminan en .c por convención. Nota: .cc y .cpp son archivos C ++; no archivos C.
    por ejemplo, foo.c

  2. Archivos de encabezado : estos archivos contienen prototipos de funciones y varias declaraciones del preprocesador (ver más abajo). Se utilizan para permitir que los archivos de código fuente accedan a funciones definidas externamente. Los archivos de encabezado terminan en .h por convención.
    por ejemplo, foo.h

  3. Archivos de objeto : estos archivos se producen como la salida del compilador. Consisten en definiciones de funciones en forma binaria, pero no son ejecutables por sí mismas. Los archivos de objetos terminan en .o por convención, aunque en algunos sistemas operativos (por ejemplo, Windows, MS-DOS), a menudo terminan en .obj .
    por ejemplo, foo.o foo.obj

  4. Ejecutables binarios : estos se producen como la salida de un programa llamado "vinculador". El vinculador vincula una serie de archivos de objeto para producir un archivo binario que se puede ejecutar directamente. Los ejecutables binarios no tienen un sufijo especial en los sistemas operativos Unix, aunque generalmente terminan en .exe en Windows.
    por ejemplo, foo foo.exe

  5. Bibliotecas : una biblioteca es un binario compilado, pero no es en sí un ejecutable (es decir, no hay una función main() en una biblioteca). Una biblioteca contiene funciones que pueden ser utilizadas por más de un programa. Una biblioteca debe enviarse con archivos de encabezado que contengan prototipos para todas las funciones de la biblioteca; estos archivos de encabezado deben ser referenciados (por ejemplo, #include <library.h> ) en cualquier archivo fuente que use la biblioteca. El enlazador entonces debe ser referido a la biblioteca para que el programa pueda compilarse exitosamente. Hay dos tipos de bibliotecas: estáticas y dinámicas.

    • Biblioteca estática : una biblioteca estática (archivos .a para sistemas POSIX y archivos .lib para Windows, que no debe confundirse con los archivos de biblioteca de importación DLL , que también utilizan la extensión .lib ) está incorporada estáticamente en el programa. Las bibliotecas estáticas tienen la ventaja de que el programa sabe exactamente qué versión de una biblioteca se utiliza. Por otro lado, los tamaños de los ejecutables son más grandes, ya que se incluyen todas las funciones de biblioteca utilizadas.
      por ejemplo, libfoo.a foo.lib
    • Biblioteca dinámica : una biblioteca dinámica (archivos .so para la mayoría de los sistemas POSIX, .dylib para OSX y archivos .dll para Windows) está vinculada dinámicamente en tiempo de ejecución por el programa. En ocasiones, también se hace referencia a estas bibliotecas como bibliotecas compartidas, ya que muchos programas pueden compartir una imagen de biblioteca. Las bibliotecas dinámicas tienen la ventaja de ocupar menos espacio en disco si más de una aplicación está utilizando la biblioteca. Además, permiten actualizaciones de la biblioteca (corrección de errores) sin tener que reconstruir archivos ejecutables.
      por ejemplo, foo.so foo.dylib foo.dll

El preprocesador

Antes de que el compilador de C comience a compilar un archivo de código fuente, el archivo se procesa en una fase de preprocesamiento. Esta fase se puede realizar mediante un programa separado o se puede integrar completamente en un ejecutable. En cualquier caso, el compilador lo invoca automáticamente antes de que comience la compilación propiamente dicha. La fase de preprocesamiento convierte su código fuente en otro código fuente o unidad de traducción mediante la aplicación de reemplazos textuales. Puede considerarlo como un código fuente "modificado" o "expandido". Esa fuente expandida puede existir como un archivo real en el sistema de archivos, o solo puede almacenarse en la memoria por un corto tiempo antes de seguir procesándose.

Los comandos del preprocesador comienzan con el signo de número ("#"). Hay varios comandos de preprocesador; Dos de los más importantes son:

  1. Define :

    #define se usa principalmente para definir constantes. Por ejemplo,

    #define BIGNUM 1000000
    int a = BIGNUM; 
    

    se convierte en

    int a = 1000000;
    

    #define se usa de esta manera para evitar tener que escribir explícitamente algún valor constante en muchos lugares diferentes en un archivo de código fuente. Esto es importante en caso de que necesite cambiar el valor constante más adelante; es mucho menos propenso a errores cambiarlo una vez, en #define , que tener que cambiarlo en varios lugares dispersos por todo el código.

    Como #define solo hace una búsqueda avanzada y reemplaza, también puede declarar macros. Por ejemplo:

    #define ISTRUE(stm) do{stm = stm ? 1 : 0;}while(0)
    // in the function:
    a = x;
    ISTRUE(a);
    

    se convierte en:

    // in the function:
    a = x;
    do {
        a = a ? 1 : 0;
    } while(0);
    

    En la primera aproximación, este efecto es aproximadamente el mismo que con las funciones en línea, pero el preprocesador no proporciona la comprobación de tipos para las macros #define . Se sabe que esto es propenso a errores y su uso requiere una gran precaución.

    También tenga en cuenta que el preprocesador también reemplazará los comentarios con un espacio en blanco como se explica a continuación.

  2. Incluye :

    #include se usa para acceder a definiciones de funciones definidas fuera de un archivo de código fuente. Por ejemplo:

     #include <stdio.h> 
    

    hace que el preprocesador pegue el contenido de <stdio.h> en el archivo de código fuente en la ubicación de la instrucción #include antes de que se compile. #include casi siempre se usa para incluir archivos de encabezado, que son archivos que contienen principalmente declaraciones de funciones y #define sentencias. En este caso, utilizamos #include para poder utilizar funciones como printf y scanf , cuyas declaraciones se encuentran en el archivo stdio.h . Los compiladores de C no le permiten usar una función a menos que se haya declarado o definido previamente en ese archivo; Por lo tanto, las declaraciones #include son la forma de reutilizar el código escrito previamente en sus programas C.

  3. Operaciones lógicas :

    #if defined A || defined B
    variable = another_variable + 1;
    #else
    variable = another_variable * 2;
    #endif
    

    se cambiará a:

    variable = another_variable + 1;
    

    si A o B se definieron en algún lugar del proyecto antes. Si este no es el caso, por supuesto, el preprocesador hará esto:

    variable = another_variable * 2;
    

    Esto se usa a menudo para el código, que se ejecuta en diferentes sistemas o compila en diferentes compiladores. Ya que hay definiciones globales, que son específicas del compilador / sistema, puede probarlas y dejar que el compilador solo use el código que compilará con seguridad.

  4. Comentarios

    El preprocesador reemplaza todos los comentarios en el archivo fuente por espacios individuales. Los comentarios se indican con // hasta el final de la línea, o una combinación de apertura /* y cierre */ comentario entre paréntesis.

El compilador

Después de que el preprocesador C haya incluido todos los archivos de encabezado y expandido todas las macros, el compilador puede compilar el programa. Lo hace convirtiendo el código fuente de C en un archivo de código objeto, que es un archivo que termina en .o que contiene la versión binaria del código fuente. Sin embargo, el código objeto no es ejecutable directamente. Para hacer un ejecutable, también debe agregar código para todas las funciones de biblioteca que fueron #include d en el archivo (esto no es lo mismo que incluir las declaraciones, que es lo que hace #include ). Este es el trabajo del enlazador .

En general, la secuencia exacta de cómo invocar un compilador de C depende mucho del sistema que esté utilizando. Aquí estamos usando el compilador GCC, aunque se debe tener en cuenta que existen muchos más compiladores:

% gcc -Wall -c foo.c

% es el símbolo del sistema operativo. Esto le indica al compilador que ejecute el preprocesador en el archivo foo.c y luego lo compile en el archivo de código de objeto foo.o La opción -c significa compilar el archivo de código fuente en un archivo objeto pero no invocar el enlazador. Esta opción -c está disponible en sistemas POSIX, como Linux o macOS; Otros sistemas pueden usar una sintaxis diferente.

Si su programa completo está en un archivo de código fuente, puede hacer esto:

% gcc -Wall foo.c -o foo

Esto le dice al compilador que ejecute el preprocesador en foo.c , lo compila y luego lo vincula para crear un ejecutable llamado foo . La opción -o indica que la siguiente palabra en la línea es el nombre del archivo ejecutable binario (programa). Si no especifica la -o , (si solo escribe gcc foo.c ), el ejecutable se llamará a.out por razones históricas.

En general, el compilador toma cuatro pasos al convertir un archivo .c en un ejecutable:

  1. preprocesamiento : expande textualmente las directivas #include y #define macros en su archivo .c
  2. compilación : convierte el programa en ensamblaje (puede detener el compilador en este paso agregando la opción -S )
  3. ensamblaje - convierte el ensamblaje en código de máquina
  4. vinculación : vincula el código objeto a bibliotecas externas para crear un ejecutable

Tenga en cuenta también que el nombre del compilador que estamos utilizando es GCC, que significa "compilador GNU C" y "colección de compiladores GNU", según el contexto. Existen otros compiladores de C. Para sistemas operativos similares a Unix, muchos de ellos tienen el nombre cc , para "compilador C", que a menudo es un enlace simbólico a algún otro compilador. En sistemas Linux, cc es a menudo un alias para GCC. En macOS o OS-X, apunta a clang.

Los estándares POSIX actualmente exigen a c99 como el nombre de un compilador de C: es compatible con el estándar C99 de forma predeterminada. Las versiones anteriores de POSIX exigían c89 como compilador. POSIX también exige que este compilador entienda las opciones -c y -o que usamos anteriormente.


Nota: La opción -Wall presente en ambos ejemplos de gcc le dice al compilador que imprima advertencias sobre construcciones cuestionables, lo cual es muy recomendable. También es una buena idea agregar otras opciones de advertencia , por ejemplo, -Wextra .

Las fases de traducción

A partir de la norma C 2011, enumerada en §5.1.1.2 Fases de traducción , la traducción del código fuente a la imagen del programa (p. Ej., El archivo ejecutable) se indica en 8 pasos ordenados.

  1. La entrada del archivo de origen se asigna al conjunto de caracteres de origen (si es necesario). Trigraphs se reemplazan en este paso.
  2. Las líneas de continuación (líneas que terminan con \ ) se empalman con la siguiente línea.
  3. El código fuente se analiza en espacios en blanco y tokens de preprocesamiento.
  4. Se aplica el preprocesador, que ejecuta directivas, expande macros y aplica pragmas. Cada archivo fuente obtenido por #include pasa por las fases de traducción 1 a 4 (recursivamente si es necesario). Todas las directivas relacionadas con el preprocesador se eliminan.
  5. Los valores del conjunto de caracteres de origen en las constantes de caracteres y los literales de cadena se asignan al conjunto de caracteres de ejecución.
  6. Los literales de cuerdas adyacentes entre sí están concatenados.
  7. El código fuente se analiza en tokens, que comprenden la unidad de traducción.
  8. Las referencias externas se resuelven y se forma la imagen del programa.

Una implementación de un compilador de C puede combinar varios pasos juntos, pero la imagen resultante todavía debe comportarse como si los pasos anteriores hubieran ocurrido por separado en el orden indicado anteriormente.



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow