tips - ¿Cuál es tu truco de programación C favorito?
tips para programar en c++ (30)
Aquí hay un ejemplo de cómo hacer que el código C desconozca por completo qué se usa realmente con 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 abstraer un poco el código C, por lo que no llega a ser 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;
}
Por ejemplo, recientemente me encontré con esto en el kernel de Linux:
/* Force a compilation error if condition is true */ #define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)]))
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 limitaciones 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 genera ningún código de tiempo de ejecución.
Otro truco que conozco proviene 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 declararlos como externos.
#ifdef DEFINE_MYHEADER_GLOBALS #define GLOBAL #define INIT(x, y) (x) = (y) #else #define GLOBAL extern #define INIT(x, y) #endif GLOBAL int INIT(x, 0); GLOBAL int somefunc(int a, int b);
Con eso, el código que define x y somefunc hace:
#define DEFINE_MYHEADER_GLOBALS #include "the_above_header_file.h"
mientras que el código que simplemente usa x y somefunc () hace:
#include "the_above_header_file.h"
Así que obtienes un archivo de cabecera que declara las instancias de los prototipos globales y funcionales donde se necesitan y las declaraciones externas correspondientes.
Entonces, ¿cuáles son tus trucos de programación C favoritos a lo largo de esas líneas?
Código orientado a objetos con C, emulando clases.
Simplemente crea una estructura y un conjunto de funciones que toman un puntero a esa estructura como primer parámetro.
C99 ofrece algunas cosas realmente geniales usando matrices anónimas:
Eliminando 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 no he pensado en muchas otras técnicas geniales.
Consulte la pregunta "Características ocultas de C" .
Creo que el uso de punteros userdata es bastante limpio. Una moda perdiendo terreno hoy en día. No es tanto una característica C, pero es bastante fácil de usar en C.
Declaración de matriz de puntero a funciones para implementar máquinas de estado finito.
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 integrado, a menudo mapeo un ISR para apuntar a dicha tabla y revelarla según sea necesario (fuera del ISR).
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
}
}
Dos buenos libros fuente para este tipo de cosas son The Practice of Programming and Writing Solid Code . Uno de ellos (no recuerdo cuál) dice: Prefiere enum para #define donde puedas, porque enum es verificado por el compilador.
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};
En lugar de
printf("counter=%d/n",counter);
Utilizar
#define print_dec(var) printf("%s=%d/n",#var,var);
print_dec(counter);
Este viene del libro ''Suficiente cuerda para dispararse en el pie'':
En el encabezado, declare
#ifndef RELEASE
# define D(x) do { x; } while (0)
#else
# define D(x)
#endif
En su código, coloque 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 varias declaraciones.
La declaración solo se imprimirá si no se utiliza el indicador ''-D RELEASE'' para el compilador.
Entonces puedes, por ej. pasar la bandera a su archivo MAKE, etc.
No estoy seguro de cómo funciona esto en Windows pero en * nix funciona bien
Los cambios de bits solo se definen hasta una cantidad de desplazamiento de 31 (en un entero de 32 bits).
¿Qué debe hacer si quiere tener un turno calculado que también necesite trabajar con valores de cambio más altos? Así es como lo hace Theora videocáfalo:
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;
}
Me gusta el "struct hack" por tener un objeto de tamaño dinámico. Este sitio lo explica bastante bien también (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 cuerdas 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 tiene el tamaño de un int (por len), más la longitud de "hello world", más 1 (ya que str 1 está incluido en el tamaño de (X).
En general, es útil cuando quiere tener un "encabezado" justo antes de algunos datos de longitud variable en el mismo bloque.
Me gusta el concepto de container_of
usado por ejemplo en listas. Básicamente, no necesita especificar los campos next
y last
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.
Eche un vistazo a include/linux/list.h
para ejemplos de la vida real.
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 a ningún byte de relleno - use memset si necesita ponerlos en cero también).
Pero debe tener en cuenta que hay algunos problemas con esto para estructuras grandes y dinámicamente asignadas .
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ó frente a 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
No es específico para 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
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.
Otro buen "truco" previo al procesador es usar el caracter "#" 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]
Para crear una variable que sea de solo lectura en todos los módulos, excepto en el que está declarado:
// 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
Realmente no lo llamaría un truco favorito, ya que nunca lo he usado, pero la mención de 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 en algún momento.
Rusty en realidad produjo un conjunto completo de condicionales de construcción en ccan , echa un vistazo al módulo de ccan construcció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 el tirón del lado oscuro (y el abuso de preprocesadores) apegándome principalmente a las funciones en línea, pero disfruto de macros útiles y útiles como los que describiste.
Si estamos hablando de trucos C, ¡mi favorito debe ser el Dispositivo de Duff para desenrollar el bucle! Solo estoy esperando la oportunidad correcta para venir y usarla con ira ...
Soy fanático de xor hacks:
Intercambie 2 punteros sin el tercer puntero temporal:
int * a;
int * b;
a ^= b;
b ^= a;
a ^= b;
O realmente me gusta la lista xor vinculada con un solo puntero. (http://en.wikipedia.org/wiki/XOR_linked_list)
Cada nodo en la lista enlazada 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.
Aunque no es terriblemente útil (no se puede comenzar a atravesar desde un nodo arbitrario), me parece genial.
Una vez un compañero mío y yo redefinimos return para encontrar un error de corrupción de pila complicado.
Algo como:
#define return DoSomeStackCheckStuff, return
Usando un macro truco estúpido 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;
Uso 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á.
usando __FILE__
y __LINE__
para la depuración
#define WHERE fprintf(stderr,"[LOG]%s:%d/n",__FILE__,__LINE__);
#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", que es suficiente para hacer que mire la línea ofensiva y luego vea la razón real por la que quería llamar la atención.
if(---------)
printf("hello");
else
printf("hi");
Complete los espacios en blanco para que ni hola ni hola aparezcan en la salida.
ans: fclose(stdout)