¿Cuál es tu truco de programación C favorito? [cerrado]

134

Por ejemplo, recientemente me encontré con esto en el kernel de Linux:

/ * Forzar un error de compilación si la condición es verdadera * /
#define BUILD_BUG_ON (condición) ((nulo) sizeof (char [1 - 2 * !! (condición)]))

Entonces, en su código, si tiene alguna estructura que debe ser, digamos un múltiplo de 8 bytes de tamaño, tal vez debido a algunas restricciones de hardware, puede hacer:

BUILD_BUG_ON ((sizeof (struct mystruct)% 8)! = 0);

y no se compilará a menos que el tamaño de struct mystruct sea un múltiplo de 8, y si es un múltiplo de 8, no se generará ningún código de tiempo de ejecución.

Otro truco que conozco es del libro "Graphics Gems", que permite que un solo archivo de encabezado declare e inicialice variables en un módulo mientras que en otros módulos que usan ese módulo, simplemente los declara como externos.

#ifdef DEFINE_MYHEADER_GLOBALS
#define GLOBAL
#definir INIT (x, y) (x) = (y)
#más
#definir GLOBAL externo
#definir INIT (x, y)
#terminara si

GLOBAL int INIT (x, 0);
GLOBAL int somefunc (int a, int b);

Con eso, el código que define xy somefunc hace:

#define DEFINE_MYHEADER_GLOBALS
#include "the_above_header_file.h"

mientras que el código que simplemente usa xy somefunc () hace:

#include "the_above_header_file.h"

Por lo tanto, obtiene un archivo de encabezado que declara ambas instancias de prototipos globales y de función donde se necesitan, y las correspondientes declaraciones externas.

Entonces, ¿cuáles son tus trucos de programación C favoritos en ese sentido?

smcameron
fuente
9
Esto parece más trucos de preprocesador C.
jmucchiello 01 de
Sobre BUILD_BUG_ONmacro, ¿qué tiene de malo usar #errordentro y #if?
Ricardo

Respuestas:

80

C99 ofrece algunas cosas realmente geniales usando matrices anónimas:

Eliminar variables sin sentido

{
    int yes=1;
    setsockopt(yourSocket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
}

se convierte

setsockopt(yourSocket, SOL_SOCKET, SO_REUSEADDR, (int[]){1}, sizeof(int));

Pasar una cantidad variable de argumentos

void func(type* values) {
    while(*values) {
        x = *values++;
        /* do whatever with x */
    }
}

func((type[]){val1,val2,val3,val4,0});

Listas enlazadas estáticas

int main() {
    struct llist { int a; struct llist* next;};
    #define cons(x,y) (struct llist[]){{x,y}}
    struct llist *list=cons(1, cons(2, cons(3, cons(4, NULL))));
    struct llist *p = list;
    while(p != 0) {
        printf("%d\n", p->a);
        p = p->next;
    }
}

Estoy seguro de que hay muchas otras técnicas geniales que no he pensado.

Evan Teran
fuente
2
Creo que su primer ejemplo también se puede escribir como &(int){1}, si desea dejar un poco más claro cuál es su intención aquí.
Lily Ballard
67

Mientras leía el código fuente de Quake 2 se me ocurrió algo como esto:

double normals[][] = {
  #include "normals.txt"
};

(más o menos, no tengo el código a mano para verificarlo ahora).

Desde entonces, un nuevo mundo de uso creativo del preprocesador se abrió ante mis ojos. Ya no incluyo solo encabezados, sino fragmentos enteros de código de vez en cuando (mejora mucho la reutilización) :-p

Gracias John Carmack! xD

fortran
fuente
13
No se puede decir carmack en un hilo de optimización sin mencionar el rápido sqrt inverso que estaba en la fuente del terremoto. en.wikipedia.org/wiki/Fast_inverse_square_root
pg1989
¿De dónde sacó 0x5f3759df en primer lugar?
RSH1
2
@RoryHarvey: Por lo que pude encontrar al buscarlo, parece que fue puramente empírico. Algunos estudios (no recuerdo dónde los vi) demostraron que era casi óptimo, pero no completamente óptimo. Del mismo modo, parece que para 64 bits se descubrió el valor, en lugar de la computación.
Matthieu M.
50

Me gusta usar = {0};para inicializar estructuras sin necesidad de llamar a memset.

struct something X = {0};

Esto inicializará todos los miembros de la estructura (o matriz) a cero (pero no los bytes de relleno; use memset si necesita ponerlos a cero también).

Pero debe tener en cuenta que hay algunos problemas con esto para estructuras grandes y dinámicamente asignadas .

John Carter
fuente
No es necesario para las variables globales, por cierto.
extraño
55
No es necesario para variables estáticas . Las variables globales pueden ponerse a cero, pero no es un requisito.
Jamie el
44
A veces extiendo esto a: const struct something zero_something = { 0 };y luego puedo restablecer una variable sobre la marcha struct something X = zero_something;o a medio camino a través de una rutina, puedo usar 'X = zero_something;'. La única objeción posible es que implica leer datos de alguna parte; en estos días, un 'memset ()' podría ser más rápido, pero me gusta la claridad de la asignación, y también es posible usar valores distintos de cero en el inicializador (y memset () seguido de ajustes para miembros individuales puede ser más lento que una simple copia).
Jonathan Leffler el
45

¡Si hablamos de trucos en C, mi favorito tiene que ser el Dispositivo de Duff para desenrollar el bucle! Solo estoy esperando la oportunidad adecuada para que lo use en realidad con ira ...

Jackson
fuente
44
Lo he usado una vez para producir un aumento de rendimiento medible, pero en estos días no es útil en una gran cantidad de hardware. Siempre perfil!
Dan Olson
66
Sí, el tipo de personas que no entienden el contexto en el que se creó el dispositivo de Duff: la "legibilidad del código" es inútil si el código no es lo suficientemente rápido como para funcionar. Probablemente ninguna de las personas que lo rechazaron haya tenido que codificar en tiempo real.
Rob K
1
+1, realmente he necesitado usar el dispositivo de Duff varias veces. La primera vez fue un ciclo que básicamente solo copió cosas e hizo una pequeña transformación en el camino. Fue mucho, mucho más rápido que una simple memcpy () en esa arquitectura.
Makis
3
La ira será de sus colegas y sucesores que deben mantener su código después de usted.
Jonathan Leffler el
1
Como dije, todavía estoy esperando la oportunidad correcta, pero nadie me ha molestado lo suficiente todavía. He estado escribiendo C durante unos 25 años, creo que me encontré por primera vez con el dispositivo de Duff a principios de los 90 y aún no he tenido que usarlo. Como otros han comentado, este tipo de truco es cada vez menos útil ahora que los compiladores mejoran en este tipo de optimización.
Jackson
42

utilizando __FILE__y __LINE__para depurar

#define WHERE fprintf(stderr,"[LOG]%s:%d\n",__FILE__,__LINE__);
Pierre
fuente
66
En algunos compiladores también obtienes FUNCIÓN .
JBRWilkinson el
11
__FUNCTION__es solo un alias para __func__, y __func__está en c99. Bastante práctico. __PRETTY_FUNCTION__en C (GCC) es solo otro alias para __func__, pero en C ++ obtendrá la firma de la función completa.
sklnd
ARCHIVO muestra la ruta completa del nombre de archivo, así que uso basename ( ARCHIVO )
Jeegar Patel
31

En C99

typedef struct{
    int value;
    int otherValue;
} s;

s test = {.value = 15, .otherValue = 16};

/* or */
int a[100] = {1,2,[50]=3,4,5,[23]=6,7};
Jasper Bekkers
fuente
28

Una vez que un compañero mío y yo redefinimos volver a encontrar un error de corrupción de pila complicado.

Algo como:

#define return DoSomeStackCheckStuff, return
Andrew Barrett
fuente
44
¡Esperemos que haya sido # definido en el cuerpo de la función y # no definido al final!
extraño
No me gusta mucho eso: lo primero que me viene a la mente es que DoSomeStackCheckStuff arruina la memoria debido a algún error y quien está leyendo el código no es consciente de la redefinición del retorno y se pregunta qué está pasando / hell /.
gilligan
8
@strager Pero eso lo haría básicamente inútil. El punto es agregar un poco de seguimiento a cada llamada a la función. De lo contrario, simplemente agregaría una llamada DoSomeStackCheckStuffa las funciones que desea rastrear.
Clueless
1
@gilligan No creo que este sea el tipo de cosas que dejas habilitado todo el tiempo; parece bastante útil para el trabajo de depuración de una sola vez.
sunetos
¿eso realmente funciona? :) Hubiera escrito #define return if((DoSomeStackCheckStuff) && 0) ; else return... ¡tan loco como supongo!
Paolo Bonzini
22

Me gusta el "truco de estructura" por tener un objeto de tamaño dinámico. Este sitio también lo explica bastante bien (aunque se refieren a la versión C99 donde puede escribir "str []" como el último miembro de una estructura). podrías hacer un "objeto" de cadena como este:

struct X {
    int len;
    char str[1];
};

int n = strlen("hello world");
struct X *string = malloc(sizeof(struct X) + n);
strcpy(string->str, "hello world");
string->len = n;

aquí, hemos asignado una estructura de tipo X en el montón que es del tamaño de un int (para len), más la longitud de "hello world", más 1 (desde str 1 está incluido en el tamaño de (X).

Generalmente es útil cuando desea tener un "encabezado" justo antes de algunos datos de longitud variable en el mismo bloque.

Evan Teran
fuente
Personalmente, me resulta más fácil simplemente usar malloc () y realloc () y usar strlen () cada vez que necesito encontrar la longitud, pero si necesita un programa que nunca sepa la longitud de la cadena y probablemente necesite encontrarla veces, este es probablemente el mejor camino.
Chris Lutz
44
"... la versión C99 donde puedes escribir" str [] "" He visto matrices de tamaño cero en ese contexto, como str [0]; bastante a menudo. Creo que es C99. Sin embargo, sé que los compiladores más antiguos se quejan de las matrices de tamaño cero.
smcameron 01 de
3
También me gusta este, sin embargo, debes usar algo como malloc (offsetof (X, str) + numbytes) de lo contrario las cosas saldrán mal debido a problemas de relleno y alineación. Por ejemplo, sizeof (struct X) podría ser 8, no 5.
Fozi
3
@Fozi: Realmente no creo que eso sea un problema. Como esta versión tiene str[1](no str[]) el 1 byte de str está incluido en el sizeof(struct X). Esto incluye cualquier relleno entre leny str.
Evan Teran
2
@Rusky: ¿Cómo afectaría eso negativamente a algo? Supongamos que hay "relleno" después str. OK, cuando asigno sizeof(struct X) + 10Entonces esto hace strefectivo 10 - sizeof(int)(o más, ya que dijimos que hay relleno) grande. Esto se superpone str y cualquier relleno posterior. La única forma en que tendría alguna diferencia es que si hubiera un miembro después de lo strcual se rompe todo de todos modos, los miembros flexibles deben ser los últimos. Cualquier relleno al final solo posiblemente causará que se asigne demasiado. Proporcione un ejemplo específico de cómo podría salir mal.
Evan Teran
17

Código orientado a objetos con C, emulando clases.

Simplemente cree una estructura y un conjunto de funciones que toman un puntero a esa estructura como primer parámetro.

Brian R. Bondy
fuente
2
¿Todavía hay algo por ahí que traduzca C ++ en C, como solía hacer Cfront?
MarkJ
11
Esto no es orientación a objetos. Para OO con herencia, necesitará agregar algún tipo de tabla de funciones virtuales a su estructura de objetos, que puede ser sobrecargada por "subclases". Hay muchos marcos de estilo "C con clases" a medio cocer para este propósito, pero recomiendo mantenerse al margen.
exDM69
Tenía que ser dicho. +1 por eso.
Amit S
3
@ exDM69, la orientación a objetos es tanto una forma de pensar sobre un problema como un paradigma de codificación; puedes hacerlo con éxito sin herencia. Hice esto en algunos proyectos antes de saltar completamente en C ++.
Mark Ransom
16

En vez de

printf("counter=%d\n",counter);

Utilizar

#define print_dec(var)  printf("%s=%d\n",#var,var);
print_dec(counter);
Const
fuente
14

Usar un estúpido truco macro para hacer que las definiciones de registros sean más fáciles de mantener.

#define COLUMNS(S,E) [(E) - (S) + 1]

typedef struct
{
    char studentNumber COLUMNS( 1,  9);
    char firstName     COLUMNS(10, 30);
    char lastName      COLUMNS(31, 51);

} StudentRecord;
EvilTeach
fuente
11

Para crear una variable que es de solo lectura en todos los módulos, excepto en el que se declara:

// Header1.h:

#ifndef SOURCE1_C
   extern const int MyVar;
#endif

// Source1.c:

#define SOURCE1_C
#include Header1.h // MyVar isn't seen in the header

int MyVar; // Declared in this file, and is writeable

// Source2.c

#include Header1.h // MyVar is seen as a constant, declared elsewhere
Steve Melnikoff
fuente
Esto se siente peligroso. Estas son declaraciones y definiciones que no coinciden. Durante la compilación Source2.c, el compilador puede suponer que eso MyVarno cambia, incluso a través de una llamada a la función Source1.c. (Tenga en cuenta que esto, como una variable const real, difiere de un puntero a const. En este último caso, el objeto señalado aún podría modificarse a través de un puntero diferente.)
jilles
1
Esto no produce una variable de solo lectura en algunas unidades de compilación. Esto produce un comportamiento indefinido (ver p. 6.2.7.2 de ISO 9899 y también p. 6.7.3.5).
Ales Hakl
8

Los cambios de bits solo se definen hasta una cantidad de desplazamiento de 31 (en un entero de 32 bits).

¿Qué hacer si desea tener un turno calculado que también necesita trabajar con valores de turno más altos? Así es como lo hace el códec de video Theora:

unsigned int shiftmystuff (unsigned int a, unsigned int v)
{
  return (a>>(v>>1))>>((v+1)>>1);
}

O mucho más legible:

unsigned int shiftmystuff (unsigned int a, unsigned int v)
{
  unsigned int halfshift = v>>1;
  unsigned int otherhalf = (v+1)>>1;

  return (a >> halfshift) >> otherhalf; 
}

Realizar la tarea de la manera que se muestra arriba es mucho más rápido que usar una rama como esta:

unsigned int shiftmystuff (unsigned int a, unsigned int v)
{
  if (v<=31)
    return a>>v;
  else
    return 0;
}
Nils Pipenbrinck
fuente
... y gcc realmente lo alinea :) +1
Tim Post
2
En mi máquina, gcc-4.3.2 se deshace de la rama en la segunda usando una instrucción cmov (movimiento condicional)
Adam Rosenfield
3
"mucho más rápido que usar una bifurcación": la diferencia es que la bifurcación es correcta para todos los valores de v, mientras que el halfshifttruco solo duplica el rango permitido a 63 en una arquitectura de 32 bits y 127 en una de 64 bits.
Pascal Cuoq
8

Declarar conjuntos de punteros a funciones para implementar máquinas de estados finitos.

int (* fsm[])(void) = { ... }

La ventaja más agradable es que es simple forzar a cada estímulo / estado a verificar todas las rutas de código.

En un sistema embebido, a menudo mapeo un ISR para apuntar a dicha tabla y revelarla según sea necesario (fuera del ISR).

Jamie
fuente
Una técnica que me gusta con esto es que si tiene una función que requiere inicialización, inicializa el puntero con una llamada a la rutina de inicialización. Cuando eso se ejecuta, lo último que hace es reemplazar el puntero con un puntero a la función real, luego llamar a esa función. De esa manera, el inicializador se llama automáticamente la primera vez que se llama a la función, y la función real se llama cada vez posterior.
TMN
7

Otro buen "truco" del preprocesador es usar el carácter "#" para imprimir expresiones de depuración. Por ejemplo:

#define MY_ASSERT(cond) \
  do { \
    if( !(cond) ) { \
      printf("MY_ASSERT(%s) failed\n", #cond); \
      exit(-1); \
    } \
  } while( 0 )

editar: el siguiente código solo funciona en C ++. Gracias a smcameron y Evan Teran.

Sí, la afirmación del tiempo de compilación siempre es excelente. También se puede escribir como:

#define COMPILE_ASSERT(cond)\
     typedef char __compile_time_assert[ (cond) ? 0 : -1]
Gilad Naor
fuente
Sin embargo, la macro COMPILE_ASSERT no se puede usar dos veces, ya que contamina el espacio de nombres con un typedef, y el segundo uso obtiene: error: redefinición de typedef '__compile_time_assert'
smcameron
¿Realmente intentaste esto? Puede "typedef foo;" tantas veces como quieras Así es como haces las predeclaraciones. Lo he usado durante 2.5 años en varios compiladores, tanto gcc, VC y un compilador para un entorno embebido, y nunca encontré ninguna dificultad.
Gilad Naor
Odio el preprocesador C ... :(
hasen
1
Sí, lo probé. Corté y pegué el mensaje de error del compilador, que era gcc.
smcameron 01 de
1
@Gilad: es legal en c ++ tener typedefs redundantes, pero no en c.
Evan Teran
6

Realmente no lo llamaría un truco favorito, ya que nunca lo he usado, pero la mención del Dispositivo de Duff me recordó este artículo sobre la implementación de Coroutines en C. Siempre me da una sonrisa, pero estoy seguro de que podría Ser útil alguna vez.

Dan Olson
fuente
De hecho, he usado esta técnica en la práctica para hacer que el código controle una secuencia de E / S asíncronas dependientes vagamente legibles para los humanos. La principal diferencia es que no almaceno el estado de la rutina en una staticvariable, sino que asigno una estructura dinámicamente y paso un puntero a eso en la función de la rutina. Un montón de macros hacen que esto sea más apetecible. No es agradable, pero mejor que la versión asíncrona / de devolución de llamada que salta por todos lados. Sin swapcontext()embargo , usaría hilos verdes (a través de * nixes) si pudiera.
pmdj
6
#if TESTMODE == 1    
    debug=1;
    while(0);     // Get attention
#endif

El tiempo (0); no tiene ningún efecto en el programa, pero el compilador emitirá una advertencia sobre "esto no hace nada", lo cual es suficiente para que mire la línea ofensiva y luego vea la verdadera razón por la que quería llamar la atención.

gbarry
fuente
9
¿no podrías usar #warning en su lugar?
Stefano Borini el
Al parecer, podría. No es completamente estándar, pero funcionó en los compiladores que uso. Curiosamente, el compilador incorporado tradujo un #define, mientras que gcc no.
gbarry el
6

Soy fan de los hacks de xor:

Cambie 2 punteros sin el tercer puntero temporal:

int * a;
int * b;
a ^= b;
b ^= a;
a ^= b;

O realmente me gusta la lista enlazada xor con un solo puntero. (http://en.wikipedia.org/wiki/XOR_linked_list)

Cada nodo en la lista vinculada es el Xor del nodo anterior y el siguiente nodo. Para avanzar, la dirección de los nodos se encuentra de la siguiente manera:

LLNode * first = head;
LLNode * second = first.linked_nodes;
LLNode * third = second.linked_nodes ^ first;
LLNode * fourth = third.linked_nodes ^ second;

etc.

o para atravesar hacia atrás:

LLNode * last = tail;
LLNode * second_to_last = last.linked_nodes;
LLNode * third_to_last = second_to_last.linked_nodes ^ last;
LLNode * fourth_to_last = third_to_last.linked_nodes ^ second_to_last;

etc.

Si bien no es terriblemente útil (no puede comenzar a desplazarse desde un nodo arbitrario), creo que es muy bueno.

hamiltop
fuente
5

Este viene del libro 'Suficiente cuerda para dispararte en el pie':

En el encabezado declaramos

#ifndef RELEASE
#  define D(x) do { x; } while (0)
#else
#  define D(x)
#endif

En su código, coloque las declaraciones de prueba, por ejemplo:

D(printf("Test statement\n"));

El do / while ayuda en caso de que el contenido de la macro se expanda a múltiples declaraciones.

La declaración solo se imprimirá si no se usa el indicador '-D RELEASE' para el compilador.

Entonces puedes, por ejemplo. pasar la bandera a su archivo MAKE, etc.

No estoy seguro de cómo funciona esto en Windows, pero en * nix funciona bien

Simon Walker
fuente
Es posible que desee expandir D (x) a {} cuando se define RELEASE, para que funcione bien con las declaraciones if. De lo contrario "if (a) D (x);" se expandirá a solo "if (a)" cuando haya definido RELEASE. Eso le dará algunos errores agradables en el comunicado de verison
MarkJ
3
@ MarkJ: NO. Tal como está, "if (a) D (x);" se expande a "if (a);" Que está perfectamente bien. Si tenía D (x) expandirse a {}, entonces "if (a) if (b) D (x); else foo ();" se expandiría INCORRECTAMENTE a "if (a) if (b) {}; else foo ();", haciendo que "else foo ()" coincida con el segundo if en lugar del primero if.
Adam Rosenfield
Para ser honesto, uso principalmente esta macro para probar declaraciones impresas, o si tuviera una declaración condicional, la incluiría todo, por ejemplo. D (si (a) foo (););
Simon Walker
1
@AdamRosenfield: el uso en su #define D(x) do { } while(0)lugar maneja ese caso (y se puede aplicar a la rama que también se inserta xpara mayor consistencia)
rpetrich
3

Rusty realmente produjo un conjunto completo de condicionales de compilación en ccan , consulte el módulo de afirmación de compilación:

#include <stddef.h>
#include <ccan/build_assert/build_assert.h>

struct foo {
        char string[5];
        int x;
};

char *foo_string(struct foo *foo)
{
        // This trick requires that the string be first in the structure
        BUILD_ASSERT(offsetof(struct foo, string) == 0);
        return (char *)foo;
}

Hay muchas otras macros útiles en el encabezado real, que son fáciles de colocar en su lugar.

Intento, con todas mis fuerzas, resistir la atracción del lado oscuro (y el abuso del preprocesador) apegándome principalmente a las funciones en línea, pero disfruto de macros inteligentes y útiles como las que describiste.

Tim Post
fuente
Sí, recientemente me encontré con ccan, y estaba considerando contribuir con algo de código, pero aún no he entendido la "forma ccan". Sin embargo, gracias por el enlace, hay más motivación para buscar en ccan, que realmente espero que obtenga algo de tracción.
smcameron 01 de
Bueno, no me preocuparía demasiado en la 'forma ccan' hasta que esté más establecida ... en este momento ccan-lint se está proponiendo como un proyecto GSOC. Es un grupo pequeño y bastante amigable ... y un gran lugar para descargar fragmentos :)
Tim Post
Por cierto, noté que BuilD_ASSERT de Rusty es como la macro del kernel de Linux (no es sorprendente), pero carece de una de las "notas" (o explosiones, o!) Y me doy cuenta de eso, creo que mi ejemplo de uso de la macro que publiqué es incorrecto. Debería haber sido: "BUILD_BUG_ON ((sizeof (struct mystruct)% 8))"
smcameron
3

Dos buenos libros fuente para este tipo de cosas son La práctica de programar y escribir código sólido . Uno de ellos (no recuerdo cuál) dice: prefiera enum a #define donde pueda, porque el compilador verifica enum.

Yuval F
fuente
1
AFAIK, en C89 / 90 NO hay verificación de tipo para enumeraciones. las enumeraciones son de alguna manera más convenientes #defines.
cschol
Parte inferior de la página 39, 2nd ED K&R. Existe al menos la oportunidad de verificar.
Jonathan Watmough
3

No es específico de C, pero siempre me ha gustado el operador XOR. Una cosa genial que puede hacer es "intercambiar sin un valor temporal":

int a = 1;
int b = 2;

printf("a = %d, b = %d\n", a, b);

a ^= b;
b ^= a;
a ^= b;

printf("a = %d, b = %d\n", a, b);

Resultado:

a = 1, b = 2

a = 2, b = 1

Karl
fuente
a = 1; b = 2; a = a + b; b = ab; a = ab; da el mismo resultado también
Grambot
Esto también intercambiará ayb: a ^ = b ^ = a ^ = b;
vikhyat
@TheCapn: sin embargo, la adición podría desbordarse.
Michael Foukarakis
2

Me gusta el concepto de container_ofutilizado, por ejemplo, en listas. Básicamente, no necesita especificar nextylast campos para cada estructura que estará en la lista. En su lugar, agrega el encabezado de la estructura de la lista a los elementos vinculados reales.

Echa un vistazo a include/linux/list.hejemplos de la vida real.

Viliam
fuente
1

Creo que el uso de punteros de datos de usuario es bastante bueno. Una moda que pierde terreno hoy en día. No es tanto una característica de C, pero es bastante fácil de usar en C.

epatel
fuente
1
Desearía haber entendido lo que querías decir aquí. ¿Podrías explicar más? ¿Qué es un puntero de datos de usuario?
Zan Lynx
1
Por favor, consulte aquí stackoverflow.com/questions/602826/…
epatel 03 de
Es principalmente para devoluciones de llamada. Son algunos datos que le gustaría recibir cada vez que se activa la devolución de llamada. Particularmente útil para pasar un puntero C ++ a una devolución de llamada para que pueda vincular un objeto a un evento.
Evan Teran
Ah, sí. Gracias. Lo uso mucho, pero nunca lo he llamado así.
Zan Lynx
1

Utilizo X-Macros para permitir que el precompilador genere código. Son especialmente útiles para definir valores de error y cadenas de error asociadas en un solo lugar, pero pueden ir mucho más allá de eso.

JayG
fuente
1

Nuestra base de código tiene un truco similar a

#ifdef DEBUG

#define my_malloc(amt) my_malloc_debug(amt, __FILE__, __LINE__)
void * my_malloc_debug(int amt, char* file, int line)
#else
void * my_malloc(int amt)
#endif
{
    //remember file and line no. for this malloc in debug mode
}

que permite el seguimiento de pérdidas de memoria en modo de depuración. Siempre pensé que esto era genial.

jdizzle
fuente
1

Diversión con macros:

#define SOME_ENUMS(F) \
    F(ZERO, zero) \
    F(ONE, one) \
    F(TWO, two)

/* Now define the constant values.  See how succinct this is. */

enum Constants {
#define DEFINE_ENUM(A, B) A,
    SOME_ENUMS(DEFINE_ENUMS)
#undef DEFINE_ENUM
};

/* Now a function to return the name of an enum: */

const char *ToString(int c) {
    switch (c) {
    default: return NULL; /* Or whatever. */
#define CASE_MACRO(A, B) case A: return #b;
     SOME_ENUMS(CASE_MACRO)
#undef CASE_MACRO
     }
}
sanjoyd
fuente
0

Aquí hay un ejemplo de cómo hacer que el código C sea completamente inconsciente de lo que realmente se usa de HW para ejecutar la aplicación. Main.c realiza la configuración y luego la capa libre se puede implementar en cualquier compilador / arco. Creo que es bastante bueno para abstraer un poco el código C, por lo que no puede ser demasiado específico.

Agregar un ejemplo compilable completo aquí.

/* free.h */
#ifndef _FREE_H_
#define _FREE_H_
#include <stdio.h>
#include <string.h>
typedef unsigned char ubyte;

typedef void (*F_ParameterlessFunction)() ;
typedef void (*F_CommandFunction)(ubyte byte) ;

void F_SetupLowerLayer (
F_ParameterlessFunction initRequest,
F_CommandFunction sending_command,
F_CommandFunction *receiving_command);
#endif

/* free.c */
static F_ParameterlessFunction Init_Lower_Layer = NULL;
static F_CommandFunction Send_Command = NULL;
static ubyte init = 0;
void recieve_value(ubyte my_input)
{
    if(init == 0)
    {
        Init_Lower_Layer();
        init = 1;
    }
    printf("Receiving 0x%02x\n",my_input);
    Send_Command(++my_input);
}

void F_SetupLowerLayer (
    F_ParameterlessFunction initRequest,
    F_CommandFunction sending_command,
    F_CommandFunction *receiving_command)
{
    Init_Lower_Layer = initRequest;
    Send_Command = sending_command;
    *receiving_command = &recieve_value;
}

/* main.c */
int my_hw_do_init()
{
    printf("Doing HW init\n");
    return 0;
}
int my_hw_do_sending(ubyte send_this)
{
    printf("doing HW sending 0x%02x\n",send_this);
    return 0;
}
F_CommandFunction my_hw_send_to_read = NULL;

int main (void)
{
    ubyte rx = 0x40;
    F_SetupLowerLayer(my_hw_do_init,my_hw_do_sending,&my_hw_send_to_read);

    my_hw_send_to_read(rx);
    getchar();
    return 0;
}
eaanon01
fuente
44
¿Te gustaría elaborar, tal vez explicar un uso práctico?
Leonardo Herrera el
Como ejemplo, si tengo que escribir un programa de prueba usando una interfaz som HW que genera interrupciones al final. Entonces este módulo se puede configurar para ejecutar una función fuera del alcance normal como un controlador de señal / interrupción.
eaanon01 03 de
0
if(---------)  
printf("hello");  
else   
printf("hi");

Complete los espacios en blanco para que ni hola ni hola aparezcan en la salida.
ans:fclose(stdout)

justgo
fuente
puede formatear el código con el {}botón de la barra de herramientas (lo he hecho por usted). El botón "Citar" no mantiene espacios en blanco ni aplica resaltado de sintaxis.
Álvaro González