Buscar..


Introducción

Todos los comandos del preprocesador comienzan con el símbolo # hash (libra). La macro AC es solo un comando de preprocesador que se define mediante la directiva #define preprocesador. Durante la etapa de preprocesamiento, el preprocesador de C (una parte del compilador de C) simplemente sustituye el cuerpo de la macro donde aparece su nombre.

Observaciones

Cuando un compilador encuentra una macro en el código, realiza un reemplazo de cadena simple, no se realizan operaciones adicionales. Debido a esto, los cambios realizados por el preprocesador no respetan el alcance de los programas en C; por ejemplo, una definición de macro no se limita a estar dentro de un bloque, por lo que no se ve afectada por un '}' que termina una declaración de bloque.

El preprocesador, por diseño, no se ha completado, hay varios tipos de cálculo que no puede realizar solo el preprocesador.

Por lo general, los compiladores tienen un indicador de línea de comando (o configuración) que nos permite detener la compilación después de la fase de preprocesamiento e inspeccionar el resultado. En las plataformas POSIX esta bandera es -E . Por lo tanto, ejecutar gcc con este indicador imprime la fuente expandida a la salida estándar:

$ gcc -E cprog.c

A menudo, el preprocesador se implementa como un programa separado, que es invocado por el compilador, el nombre común para ese programa es cpp . Varios preprocesadores emiten información de apoyo, como información sobre números de línea, que se utiliza en las fases posteriores de la compilación para generar información de depuración. En el caso de que el preprocesador se base en gcc, la opción -P suprime dicha información.

$ cpp -P cprog.c

Inclusión condicional y modificación de la firma de la función condicional.

Para incluir condicionalmente un bloque de código, el preprocesador tiene varias directivas (por ejemplo #if , #ifdef , #else , #endif , etc).

/* Defines a conditional `printf` macro, which only prints if `DEBUG`
 * has been defined
 */
#ifdef DEBUG
#define DLOG(x) (printf(x))
#else
#define DLOG(x)
#endif

Se pueden usar operadores relacionales C normales para la condición #if

#if __STDC_VERSION__ >= 201112L
/* Do stuff for C11 or higher */
#elif __STDC_VERSION__ >= 199901L
/* Do stuff for C99 */
#else
/* Do stuff for pre C99 */
#endif

Las directivas #if comportan de manera similar a la instrucción C if , solo contendrá expresiones constantes integrales y no emitirá. Admite un operador unario adicional, defined( identifier ) , que devuelve 1 si el identificador está definido, y 0 contrario.

#if defined(DEBUG) && !defined(QUIET)
#define DLOG(x) (printf(x))
#else
#define DLOG(x)
#endif

Modificación de la firma de la función condicional

En la mayoría de los casos, se espera que la versión de lanzamiento de una aplicación tenga la menor sobrecarga posible. Sin embargo, durante la prueba de una compilación provisional, pueden ser útiles los registros adicionales y la información sobre los problemas encontrados.

Por ejemplo, supongamos que hay alguna función SHORT SerOpPluAllRead(PLUIF *pPif, USHORT usLockHnd) que al realizar una compilación de prueba se generará un registro sobre su uso. Sin embargo, esta función se usa en múltiples lugares y se desea que al generar el registro, parte de la información sea para saber de dónde se llama la función.

Entonces, al usar la compilación condicional, puede tener algo como lo siguiente en el archivo de inclusión que declara la función. Esto reemplaza la versión estándar de la función con una versión de depuración de la función. El preprocesador se usa para reemplazar las llamadas a la función SerOpPluAllRead() con llamadas a la función SerOpPluAllRead_Debug() con dos argumentos adicionales, el nombre del archivo y el número de línea donde se usa la función.

La compilación condicional se utiliza para elegir si se reemplaza o no la función estándar con una versión de depuración.

#if 0
// function declaration and prototype for our debug version of the function.
SHORT   SerOpPluAllRead_Debug(PLUIF *pPif, USHORT usLockHnd, char *aszFilePath, int nLineNo);

// macro definition to replace function call using old name with debug function with additional arguments.
#define SerOpPluAllRead(pPif,usLock) SerOpPluAllRead_Debug(pPif,usLock,__FILE__,__LINE__)
#else
// standard function declaration that is normally used with builds.
SHORT   SerOpPluAllRead(PLUIF *pPif, USHORT usLockHnd);
#endif

Esto le permite anular la versión estándar de la función SerOpPluAllRead() con una versión que proporcionará el nombre del archivo y el número de línea en el archivo donde se llama la función.

Hay una consideración importante: cualquier archivo que use esta función debe incluir el archivo de encabezado donde se utiliza este método para que el preprocesador modifique la función. De lo contrario verá un error de vinculador.

La definición de la función sería similar a la siguiente. Lo que hace esta fuente es solicitar que el preprocesador cambie el nombre de la función SerOpPluAllRead() para que sea SerOpPluAllRead_Debug() y para modificar la lista de argumentos para incluir dos argumentos adicionales, un puntero al nombre del archivo donde se llamó la función y el número de línea. en el archivo en el que se utiliza la función.

#if defined(SerOpPluAllRead)
// forward declare the replacement function which we will call once we create our log.
SHORT    SerOpPluAllRead_Special(PLUIF *pPif, USHORT usLockHnd);

SHORT    SerOpPluAllRead_Debug(PLUIF *pPif, USHORT usLockHnd, char *aszFilePath, int nLineNo)
{
    int iLen = 0;
    char  xBuffer[256];

    // only print the last 30 characters of the file name to shorten the logs.
    iLen = strlen (aszFilePath);
    if (iLen > 30) {
        iLen = iLen - 30;
    }
    else {
        iLen = 0;
    }

    sprintf (xBuffer, "SerOpPluAllRead_Debug(): husHandle = %d, File %s, lineno = %d", pPif->husHandle, aszFilePath + iLen, nLineNo);
    IssueDebugLog(xBuffer);

    // now that we have issued the log, continue with standard processing.
    return SerOpPluAllRead_Special(pPif, usLockHnd);
}

// our special replacement function name for when we are generating logs.
SHORT    SerOpPluAllRead_Special(PLUIF *pPif, USHORT usLockHnd)
#else
// standard, normal function name (signature) that is replaced with our debug version.
SHORT   SerOpPluAllRead(PLUIF *pPif, USHORT usLockHnd)
#endif
{
    if (STUB_SELF == SstReadAsMaster()) {
        return OpPluAllRead(pPif, usLockHnd);
    } 
    return OP_NOT_MASTER;
}

Inclusión de archivo fuente

Los usos más comunes de las directivas de preprocesamiento de #include son las siguientes:

#include <stdio.h>
#include "myheader.h"

#include reemplaza la declaración con el contenido del archivo al que se hace referencia. Los corchetes angulares (<>) hacen referencia a los archivos de cabecera instalados en el sistema, mientras que las comillas ("") corresponden a los archivos proporcionados por el usuario.

Las propias macros pueden expandir otras macros una vez, como lo ilustra este ejemplo:

#if VERSION == 1
    #define INCFILE  "vers1.h"
#elif VERSION == 2
    #define INCFILE  "vers2.h"
    /*  and so on */
#else
    #define INCFILE  "versN.h"
#endif
/* ... */
#include INCFILE

Reemplazo de macros

La forma más simple de reemplazo de macro es definir una manifest constant , como en

#define ARRSIZE 100
int array[ARRSIZE];

Esto define una macro similar a una función que multiplica una variable por 10 y almacena el nuevo valor:

#define TIMES10(A) ((A) *= 10)

double b = 34;
int c = 23;

TIMES10(b);   // good: ((b) *= 10);
TIMES10(c);   // good: ((c) *= 10);
TIMES10(5);   // bad:  ((5) *= 10);

El reemplazo se realiza antes de cualquier otra interpretación del texto del programa. En la primera llamada a TIMES10 el nombre A de la definición se reemplaza por b y el texto expandido se coloca en lugar de la llamada. Tenga en cuenta que esta definición de TIMES10 no es equivalente a

#define TIMES10(A) ((A) = (A) * 10)

porque esto podría evaluar el reemplazo de A , dos veces, lo que puede tener efectos secundarios no deseados.

Lo siguiente define una macro similar a una función cuyo valor es el máximo de sus argumentos. Tiene las ventajas de trabajar con cualquier tipo compatible de argumentos y de generar código en línea sin la sobrecarga de la función de llamada. Tiene las desventajas de evaluar uno u otro de sus argumentos por segunda vez (incluidos los efectos secundarios) y de generar más código que una función si se invoca varias veces.

#define max(a, b) ((a) > (b) ? (a) : (b))

int maxVal = max(11, 43);              /* 43 */
int maxValExpr = max(11 + 36, 51 - 7); /* 47 */

/* Should not be done, due to expression being evaluated twice */
int j = 0, i = 0;
int sideEffect = max(++i, ++j);       /* i == 4 */

Debido a esto, las macros que evalúan sus argumentos varias veces generalmente se evitan en el código de producción. Desde C11 existe la característica _Generic que permite evitar tales invocaciones múltiples.

Los abundantes paréntesis en las expansiones de macro (lado derecho de la definición) aseguran que los argumentos y la expresión resultante se unen correctamente y se ajusten bien al contexto en el que se llama la macro.

Directiva de error

Si el preprocesador encuentra una directiva #error , la compilación se detiene y se imprime el mensaje de diagnóstico incluido.

#define DEBUG

#ifdef DEBUG
#error "Debug Builds Not Supported"
#endif

int main(void) {
    return 0;
}

Salida posible:

$ gcc error.c
error.c: error: #error "Debug Builds Not Supported"

#if 0 para bloquear secciones de código

Si hay secciones de código que está considerando eliminar o desea desactivar temporalmente, puede comentarlo con un comentario de bloqueo.

/* Block comment around whole function to keep it from getting used.
 * What's even the purpose of this function?
int myUnusedFunction(void)
{
    int i = 5;
    return i;
}
*/

Sin embargo, si el código fuente que ha rodeado con un comentario de bloque tiene comentarios de estilo de bloque en la fuente, el final * / de los comentarios de bloque existentes puede hacer que su nuevo comentario de bloque no sea válido y causar problemas de compilación.

/* Block comment around whole function to keep it from getting used.
 * What's even the purpose of this function?
int myUnusedFunction(void)
{
    int i = 5;

    /* Return 5 */
    return i;
}
*/ 

En el ejemplo anterior, el compilador ve las últimas dos líneas de la función y el último '* /', por lo que se compilaría con errores. Un método más seguro es usar una directiva #if 0 alrededor del código que desea bloquear.

#if 0
/* #if 0 evaluates to false, so everything between here and the #endif are
 * removed by the preprocessor. */
int myUnusedFunction(void)
{
    int i = 5;
    return i;
}
#endif

Un beneficio de esto es que cuando quiere regresar y encontrar el código, es mucho más fácil hacer una búsqueda de "#if 0" que buscar todos sus comentarios.

Otro beneficio muy importante es que puede anidar el código de comentario con #if 0 . Esto no se puede hacer con comentarios.

Una alternativa al uso de #if 0 es usar un nombre que no estará #defined pero es más descriptivo de por qué se está bloqueando el código. Por ejemplo, si hay una función que parece ser un código muerto inútil, podría usar #if defined(POSSIBLE_DEAD_CODE) o #if defined(FUTURE_CODE_REL_020201) para el código necesario una vez que haya otra funcionalidad o algo similar. Luego, al volver para eliminar o habilitar esa fuente, esas secciones de la fuente son fáciles de encontrar.

Pegado de ficha

El pegado de token permite pegar dos argumentos de macro. Por ejemplo, front##back produce frontback . Un ejemplo famoso es el encabezado <TCHAR.H> Win32. En el estándar C, se puede escribir L"string" para declarar una cadena de caracteres amplia. Sin embargo, la API de Windows le permite a uno convertir entre cadenas de caracteres amplias y cadenas de caracteres estrechas simplemente con #define ing UNICODE . Para implementar los literales de cadena, TCHAR.H usa esto

#ifdef UNICODE
#define TEXT(x) L##x
#endif

Cada vez que un usuario escribe TEXT("hello, world") y UNICODE se define, el preprocesador de C concatena L y el argumento de la macro. L concatenado con "hello, world" da L"hello, world" .

Macros predefinidas

Una macro predefinida es una macro que el preprocesador C ya entiende sin necesidad de que el programa la defina. Ejemplos incluyen

Macros obligatorias predefinidas

  • __FILE__ , que proporciona el nombre del archivo fuente actual (un literal de cadena),
  • __LINE__ para el número de línea actual (una constante entera),
  • __DATE__ para la fecha de compilación (un literal de cadena),
  • __TIME__ para el tiempo de compilación (un literal de cadena).

También hay un identificador predefinido relacionado, __func__ (ISO / IEC 9899: 2011 §6.4.2.2), que no es una macro:

El identificador __func__ será declarado implícitamente por el traductor como si, inmediatamente después de la llave de apertura de cada definición de función, la declaración:

 static const char __func__[] = "function-name";

apareció, donde nombre-función es el nombre de la función que encierra léxicamente.

__FILE__ , __LINE__ y __func__ son especialmente útiles para propósitos de depuración. Por ejemplo:

fprintf(stderr, "%s: %s: %d: Denominator is 0", __FILE__, __func__, __LINE__);

Los compiladores anteriores a C99, pueden o no ser compatibles con __func__ o pueden tener una macro que actúa de la misma manera que se nombra de manera diferente. Por ejemplo, gcc usó __FUNCTION__ en modo C89.

Las siguientes macros permiten solicitar detalles sobre la implementación:

  • __STDC_VERSION__ La versión del estándar C implementada. Este es un entero constante que usa el formato yyyymmL (el valor 201112L para C11, el valor 199901L para C99; no se definió para C89 / C90)
  • __STDC_HOSTED__ 1 si es una implementación hospedada, de lo contrario 0 .
  • __STDC__ Si es 1 , la implementación cumple con el estándar C.

Otras macros predefinidas (no obligatorio)

ISO / IEC 9899: 2011 §6.10.9.2 Macros de entorno:

  • __STDC_ISO_10646__ Una constante entera de la forma yyyymmL (por ejemplo, 199712L). Si se define este símbolo, entonces cada carácter en el conjunto requerido de Unicode, cuando se almacena en un objeto de tipo wchar_t , tiene el mismo valor que el identificador corto de ese carácter. El conjunto requerido de Unicode consta de todos los caracteres que se definen en ISO / IEC 10646, junto con todas las enmiendas y correcciones técnicas, a partir del año y mes especificados. Si se utiliza alguna otra codificación, la macro no se definirá y la codificación real utilizada se definirá por la implementación.

  • __STDC_MB_MIGHT_NEQ_WC__ La constante entera 1, destinada a indicar que, en la codificación de wchar_t , un miembro del conjunto de caracteres básico no necesita tener un valor de código igual a su valor cuando se usa como carácter único en una constante de carácter entero.

  • __STDC_UTF_16__ La constante entera 1, que pretende indicar que los valores de tipo char16_t están codificados en UTF-16. Si se utiliza alguna otra codificación, la macro no se definirá y la codificación real utilizada se definirá por la implementación.

  • __STDC_UTF_32__ La constante entera 1, que pretende indicar que los valores de tipo char32_t están codificados en UTF − 32. Si se utiliza alguna otra codificación, la macro no se definirá y la codificación real utilizada se definirá por la implementación.

ISO / IEC 9899: 2011 §6.10.8.3 Macros de funciones condicionales

  • __STDC_ANALYZABLE__ La constante entera 1, con la intención de indicar conformidad con las especificaciones en el anexo L (Análisis).
  • __STDC_IEC_559__ La constante entera 1, que pretende indicar la conformidad con las especificaciones en el anexo F (IEC 60559 aritmética de punto flotante).
  • __STDC_IEC_559_COMPLEX__ La constante entera 1, destinada a indicar el cumplimiento de las especificaciones en el anexo G (aritmética compleja compatible con IEC 60559).
  • __STDC_LIB_EXT1__ La constante entera 201112L , diseñada para indicar el soporte para las extensiones definidas en el anexo K (Interfaces de verificación de límites).
  • __STDC_NO_ATOMICS__ La constante entera 1, que pretende indicar que la implementación no admite tipos atómicos (incluido el _Atomic tipo _Atomic ) y el encabezado <stdatomic.h> .
  • __STDC_NO_COMPLEX__ La constante entera 1, que pretende indicar que la implementación no admite tipos complejos o el encabezado <complex.h> .
  • __STDC_NO_THREADS__ La constante entera 1, que pretende indicar que la implementación no admite el encabezado <threads.h> .
  • __STDC_NO_VLA__ La constante entera 1, que pretende indicar que la implementación no admite matrices de longitud variable o tipos modificados de forma variable.

El encabezado incluye guardias

Casi todos los archivos de encabezado deben seguir el lenguaje incluido en la guarda :

mi-encabezado-archivo.h

#ifndef MY_HEADER_FILE_H
#define MY_HEADER_FILE_H

// Code body for header file

#endif

Esto garantiza que cuando #include "my-header-file.h" en varios lugares, no obtenga declaraciones duplicadas de funciones, variables, etc. Imagine la siguiente jerarquía de archivos:

encabezado-1.h

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

encabezado-2.h

#include "header-1.h"

int myFunction2(MyStruct *value);

C Principal

#include "header-1.h"
#include "header-2.h"

int main() {
    // do something
}

Este código tiene un problema grave: el contenido detallado de MyStruct se define dos veces, lo que no está permitido. Esto podría resultar en un error de compilación que puede ser difícil de rastrear, ya que un archivo de encabezado incluye otro. Si en cambio lo hiciste con guardias de cabecera:

encabezado-1.h

#ifndef HEADER_1_H
#define HEADER_1_H

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

#endif

encabezado-2.h

#ifndef HEADER_2_H
#define HEADER_2_H

#include "header-1.h"

int myFunction2(MyStruct *value);

#endif

C Principal

#include "header-1.h"
#include "header-2.h"

int main() {
    // do something
}

Esto luego se expandiría a:

#ifndef HEADER_1_H
#define HEADER_1_H

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

#endif

#ifndef HEADER_2_H
#define HEADER_2_H

#ifndef HEADER_1_H // Safe, since HEADER_1_H was #define'd before.
#define HEADER_1_H

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

#endif

int myFunction2(MyStruct *value);

#endif

int main() {
    // do something
}

Cuando el compilador alcanza la segunda inclusión de header-1.h , HEADER_1_H ya estaba definido por la inclusión anterior. Ergo, se reduce a lo siguiente:

#define HEADER_1_H

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

#define HEADER_2_H

int myFunction2(MyStruct *value);

int main() {
    // do something
}

Y así no hay error de compilación.

Nota: Existen múltiples convenciones diferentes para nombrar los protectores de encabezado. A algunas personas les gusta HEADER_2_H_ , algunas incluyen el nombre del proyecto como MY_PROJECT_HEADER_2_H . Lo importante es asegurarse de que la convención que sigue lo haga para que cada archivo en su proyecto tenga un protector de encabezado único.


Si los detalles de la estructura no se incluyeran en el encabezado, el tipo declarado sería incompleto o un tipo opaco . Tales tipos pueden ser útiles, ocultando los detalles de implementación de los usuarios de las funciones. Para muchos propósitos, el tipo de FILE en la biblioteca C estándar puede considerarse como un tipo opaco (aunque generalmente no es opaco, por lo que las implementaciones de macro de las funciones de E / S estándar pueden hacer uso de los elementos internos de la estructura). En ese caso, el header-1.h podría contener:

#ifndef HEADER_1_H
#define HEADER_1_H

typedef struct MyStruct MyStruct;

int myFunction(MyStruct *value);

#endif

Tenga en cuenta que la estructura debe tener un nombre de etiqueta (aquí MyStruct , que está en el espacio de nombres de las etiquetas, separado del espacio de nombres de los identificadores ordinarios del nombre typedef MyStruct ), y que se omite { … } . Esto dice "hay un tipo de estructura struct MyStruct y hay un alias para MyStruct ".

En el archivo de implementación, los detalles de la estructura se pueden definir para completar el tipo:

struct MyStruct {
    …
};

Si está utilizando C11, puede repetir la typedef struct MyStruct MyStruct; Declaración sin causar un error de compilación, pero las versiones anteriores de C se quejarían. Por consiguiente, aún es mejor utilizar el lenguaje de inclusión de protección, aunque en este ejemplo, sería opcional si el código solo se compilara con compiladores que admitieran C11.


Muchos compiladores admiten la directiva #pragma once , que tiene los mismos resultados:

mi-encabezado-archivo.h

#pragma once

// Code for header file

Sin embargo, #pragma once no es parte del estándar C, por lo que el código es menos portátil si lo usas.


Unos pocos encabezados no usan el modismo de inclusión. Un ejemplo específico es el encabezado estándar <assert.h> . Puede incluirse varias veces en una sola unidad de traducción, y el efecto de hacerlo depende de si la macro NDEBUG se define cada vez que se incluye el encabezado. Ocasionalmente puede tener un requisito análogo; Tales casos serán pocos y distantes entre sí. Por lo general, sus encabezados deben estar protegidos por el lenguaje de inclusión de la guardia.

Implementación FOREACH

También podemos usar macros para hacer que el código sea más fácil de leer y escribir. Por ejemplo, podemos implementar macros para implementar el constructo foreach en C para algunas estructuras de datos como listas, colas, etc. vinculadas de manera simple y doble.

Aquí hay un pequeño ejemplo.

#include <stdio.h>
#include <stdlib.h>

struct LinkedListNode
{
    int data;
    struct LinkedListNode *next;
};

#define FOREACH_LIST(node, list) \
     for (node=list; node; node=node->next)

/* Usage */
int main(void)
{
    struct LinkedListNode *list, **plist = &list, *node;
    int i;

    for (i=0; i<10; i++)
    {
         *plist = malloc(sizeof(struct LinkedListNode));
         (*plist)->data = i;
         (*plist)->next = NULL;
         plist          = &(*plist)->next;
    }

    /* printing the elements here */
    FOREACH_LIST(node, list)
    {
        printf("%d\n", node->data);
    }
}

Puede crear una interfaz estándar para dichas estructuras de datos y escribir una implementación genérica de FOREACH como:

#include <stdio.h>
#include <stdlib.h>

typedef struct CollectionItem_
{
    int data;
    struct CollectionItem_ *next;
} CollectionItem;

typedef struct Collection_
{
    /* interface functions */
    void* (*first)(void *coll);
    void* (*last) (void *coll);
    void* (*next) (void *coll, CollectionItem *currItem);

    CollectionItem *collectionHead;
    /* Other fields */
} Collection;

/* must implement */
void *first(void *coll)
{
    return ((Collection*)coll)->collectionHead;
}

/* must implement */
void *last(void *coll)
{
    return NULL;
}

/* must implement */
void *next(void *coll, CollectionItem *curr)
{
    return curr->next;
}

CollectionItem *new_CollectionItem(int data)
{
    CollectionItem *item = malloc(sizeof(CollectionItem));
    item->data = data;
    item->next = NULL;
    return item;
}

void Add_Collection(Collection *coll, int data)
{
    CollectionItem **item = &coll->collectionHead;
    while(*item)
        item = &(*item)->next;
    (*item) = new_CollectionItem(data);
}

Collection *new_Collection()
{
    Collection *nc = malloc(sizeof(Collection));
    nc->first = first;
    nc->last  = last;
    nc->next  = next;
    return nc;
}

/* generic implementation */
#define FOREACH(node, collection)                      \
    for (node  = (collection)->first(collection);      \
         node != (collection)->last(collection);       \
         node  = (collection)->next(collection, node))

int main(void)
{
    Collection *coll = new_Collection();
    CollectionItem *node;
    int i;

    for(i=0; i<10; i++)
    {
         Add_Collection(coll, i);
    }

    /* printing the elements here */
    FOREACH(node, coll)
    {
        printf("%d\n", node->data);
    }
}

Para usar esta implementación genérica, simplemente implemente estas funciones para su estructura de datos.

1.  void* (*first)(void *coll);
2.  void* (*last) (void *coll);
3.  void* (*next) (void *coll, CollectionItem *currItem);

__cplusplus para usar C externos en el código C ++ compilado con C ++ - denominación de nombres

Hay ocasiones en que un archivo de inclusión tiene que generar una salida diferente del preprocesador dependiendo de si el compilador es un compilador de C o un compilador de C ++ debido a las diferencias de idioma.

Por ejemplo, una función u otra externa se define en un archivo fuente C, pero se usa en un archivo fuente C ++. Como C ++ utiliza la denominación de nombres (o la decoración de nombres) para generar nombres de función únicos basados ​​en los tipos de argumento de función, una declaración de función C utilizada en un archivo fuente de C ++ causará errores de enlace. El compilador de C ++ modificará el nombre externo especificado para la salida del compilador usando las reglas de manipulación de nombres para C ++. El resultado son errores de enlace debido a elementos externos que no se encuentran cuando la salida del compilador de C ++ está vinculada con la salida del compilador de C.

Dado que los compiladores de C no modifican los nombres, los compiladores de C ++ lo hacen para todas las etiquetas externas (nombres de funciones o nombres de variables) generadas por el compilador de C ++, se introdujo una macro preprocesadora predefinida, __cplusplus , para permitir la detección del compilador.

Para evitar este problema de salida de compilador incompatible para nombres externos entre C y C ++, la macro __cplusplus se define en el preprocesador de C ++ y no se define en el preprocesador de C ++. Este nombre de macro se puede usar con la directiva #ifdef preprocesador condicional o #if con el operador defined() para indicar si un código fuente o un archivo de inclusión se está compilando como C ++ o C.

#ifdef __cplusplus
printf("C++\n");
#else
printf("C\n");
#endif

O podrías usar

#if defined(__cplusplus)
printf("C++\n");
#else
printf("C\n");
#endif

Para especificar el nombre correcto de la función de una función de un archivo fuente C compilado con el compilador C que se está utilizando en un archivo fuente C ++, puede verificar la constante definida __cplusplus para provocar la extern "C" { /* ... */ }; para ser usado para declarar externos de C cuando el archivo de encabezado se incluye en un archivo fuente de C ++. Sin embargo, cuando se compila con un compilador de C, el extern "C" { */ ... */ }; no se utiliza Esta compilación condicional es necesaria porque extern "C" { /* ... */ }; es válido en C ++ pero no en C.

#ifdef __cplusplus
// if we are being compiled with a C++ compiler then declare the
// following functions as C functions to prevent name mangling.
extern "C" {
#endif

// exported C function list.
int foo (void);

#ifdef __cplusplus
// if this is a C++ compiler, we need to close off the extern declaration.
};
#endif

Macros similares a funciones

Las macros similares a funciones son similares a las funciones en inline , son útiles en algunos casos, como el registro de depuración temporal:

#ifdef DEBUG
# define LOGFILENAME "/tmp/logfile.log"

# define LOG(str) do {                            \
  FILE *fp = fopen(LOGFILENAME, "a");            \
  if (fp) {                                       \
    fprintf(fp, "%s:%d %s\n", __FILE__, __LINE__, \
                 /* don't print null pointer */   \
                 str ?str :"<null>");             \
    fclose(fp);                                   \
  }                                               \
  else {                                          \
    perror("Opening '" LOGFILENAME "' failed");   \
  }                                               \
} while (0)
#else
  /* Make it a NOOP if DEBUG is not defined. */
# define LOG(LINE) (void)0
#endif


#include <stdio.h>

int main(int argc, char* argv[])
{
    if (argc > 1)
        LOG("There are command line arguments");
    else
        LOG("No command line arguments");
    return 0;
}

Aquí, en ambos casos (con DEBUG o no), la llamada se comporta de la misma manera que una función con tipo de retorno void . Esto asegura que los condicionales if/else se interpreten como se espera.

En el caso de DEBUG , esto se implementa mediante una construcción do { ... } while(0) . En el otro caso, (void)0 es una declaración sin efecto secundario que simplemente se ignora.

Una alternativa para este último sería

#define LOG(LINE) do { /* empty */ } while (0)

de modo que sea en todos los casos sintácticamente equivalente al primero.

Si usa GCC, también puede implementar una macro similar a una función que devuelva resultados utilizando una extensión GNU no estándar: expresiones de declaración . Por ejemplo:

#include <stdio.h>

#define POW(X, Y) \
({ \
        int i, r = 1; \
        for (i = 0; i < Y; ++i) \
                r *= X; \
        r; \ // returned value is result of last operation
})

int main(void)
{
        int result;

        result = POW(2, 3); 
        printf("Result: %d\n", result);
}

Argumentos variables macro

C99

Macros con argumentos variadicos:

Supongamos que desea crear una macro de impresión para depurar su código, tomemos esta macro como ejemplo:

#define debug_print(msg) printf("%s:%d %s", __FILE__, __LINE__, msg)

Algunos ejemplos de uso:

La función somefunc() devuelve -1 si falla y 0 si es somefunc() , y se llama desde muchos lugares diferentes dentro del código:

int retVal = somefunc();

if(retVal == -1)
{
    debug_printf("somefunc() has failed");
}

/* some other code */

 retVal = somefunc();

if(retVal == -1)
{
    debug_printf("somefunc() has failed");
}

¿Qué sucede si cambia la implementación de somefunc() y ahora devuelve valores diferentes que coinciden con diferentes tipos de error posibles? Aún desea utilizar la macro de depuración e imprimir el valor de error.

debug_printf(retVal);      /* this would obviously fail */
debug_printf("%d",retVal); /* this would also fail */

Para resolver este problema se introdujo la macro __VA_ARGS__ . Esta macro permite múltiples parámetros de X-macro:

Ejemplo:

 #define debug_print(msg, ...) printf(msg, __VA_ARGS__) \
                               printf("\nError occurred in file:line (%s:%d)\n", __FILE__, __LINE)

Uso:

int retVal = somefunc();

debug_print("retVal of somefunc() is-> %d", retVal);

Esta macro le permite pasar varios parámetros e imprimirlos, pero ahora le prohíbe enviar parámetros.

debug_print("Hey");

Esto provocaría un error de sintaxis ya que la macro espera al menos un argumento más y el preprocesador no ignorará la falta de coma en la macro debug_print() . También debug_print("Hey",); generaría un error de sintaxis ya que no puede mantener vacío el argumento pasado a la macro.

Para resolver esto, se introdujo la macro ##__VA_ARGS__ , esta macro indica que si no existen argumentos variables, el preprocesador elimina la coma del código.

Ejemplo:

 #define debug_print(msg, ...) printf(msg, ##__VA_ARGS__) \
                               printf("\nError occured in file:line (%s:%d)\n", __FILE__, __LINE)

Uso:

 debug_print("Ret val of somefunc()?");
 debug_print("%d",somefunc());


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