Características ocultas de C

141

Sé que hay un estándar detrás de todas las implementaciones del compilador de C, por lo que no debería haber características ocultas. A pesar de eso, estoy seguro de que todos los desarrolladores de C tienen trucos ocultos / secretos que usan todo el tiempo.

bernardn
fuente
Sería genial si usted / alguien editara la "pregunta" para indicar la selección de las mejores características ocultas, como en las versiones C # y Perl de esta pregunta.
Donal Fellows

Respuestas:

62

Punteros de función. Puede usar una tabla de punteros de función para implementar, por ejemplo, intérpretes rápidos de código de hilos indirectos (FORTH) o despachadores de código de bytes, o para simular métodos virtuales similares a OO.

Luego hay gemas ocultas en la biblioteca estándar, como qsort (), bsearch (), strpbrk (), strcspn () [estas dos últimas son útiles para implementar un reemplazo strtok ()].

Una característica errónea de C es que el desbordamiento aritmético con signo es un comportamiento indefinido (UB). Por lo tanto, cada vez que vea una expresión como x + y, ambas son entradas firmadas, podría desbordarse y causar UB.

zvrba
fuente
29
Pero si hubieran especificado un comportamiento en el desbordamiento, habría sido muy lento en arquitecturas donde ese no era el comportamiento normal. El tiempo de ejecución muy bajo siempre ha sido un objetivo de diseño de C, y eso ha significado que muchas cosas como esta no están definidas.
Mark Baker,
9
Soy muy consciente de por qué el desbordamiento es UB. Todavía es una característica incorrecta, porque el estándar debería haber proporcionado al menos rutinas de biblioteca que puedan probar el desbordamiento aritmético (de todas las operaciones básicas) sin causar UB.
zvrba
2
@zvrba, "rutinas de biblioteca que pueden probar el desbordamiento aritmético (de todas las operaciones básicas)" si hubiera agregado esto, habría incurrido en un impacto significativo en el rendimiento de cualquier operación aritmética de enteros. ===== Caso de estudio Matlab AGREGA específicamente la característica de controlar el comportamiento de desbordamiento de enteros para envolver o saturar. Y también arroja una excepción cada vez que se produce un desbordamiento ==> Rendimiento de operaciones enteras de Matlab: MUY LENTO. Mi propia conclusión: creo que Matlab es un estudio de caso convincente que muestra por qué no desea la verificación de desbordamiento de enteros.
Trevor Boyd Smith
15
Dije que el estándar debería haber proporcionado soporte de biblioteca para verificar el desbordamiento aritmético. Ahora, ¿cómo puede una rutina de biblioteca incurrir en un impacto en el rendimiento si nunca la usa?
zvrba
55
Una gran negativa es que GCC no tiene una bandera para detectar desbordamientos de enteros firmados y lanzar una excepción de tiempo de ejecución. Si bien hay indicadores x86 para detectar tales casos, GCC no los utiliza. Tener dicho indicador permitiría a las aplicaciones que no son críticas para el rendimiento (especialmente las heredadas) el beneficio de la seguridad con una revisión y refactorización mínima o nula del código.
Andrew Keeton
116

Es más un truco del compilador GCC, pero puede dar indicaciones de ramificación al compilador (común en el kernel de Linux)

#define likely(x)       __builtin_expect((x),1)
#define unlikely(x)     __builtin_expect((x),0)

ver: http://kerneltrap.org/node/4705

Lo que me gusta de esto es que también agrega algo de expresividad a algunas funciones.

void foo(int arg)
{
     if (unlikely(arg == 0)) {
           do_this();
           return;
     }
     do_that();
     ...
}
tonylo
fuente
2
Este truco es genial ... :) Especialmente con las macros que definas. :)
sundar - Restablecer Monica
77
int8_t
int16_t
int32_t
uint8_t
uint16_t
uint32_t

Se trata de un elemento opcional en el estándar, pero debe ser una característica oculta, ya que las personas los redefinen constantemente. Una base de código en la que he trabajado (y todavía lo hago, por ahora) tiene múltiples redefiniciones, todas con diferentes identificadores. La mayoría de las veces es con macros de preprocesador:

#define INT16 short
#define INT32  long

Y así. Me dan ganas de arrancarme el pelo. ¡Solo use los malditos tipos de letra enteros estándar!

Ben Collins
fuente
3
Creo que son C99 más o menos. No he encontrado una forma portátil de garantizar que estos estén disponibles.
akauppi
3
Son una parte opcional de C99, pero no conozco proveedores de compiladores que no implementen esto.
Ben Collins
10
stdint.h no es opcional en C99, pero aparentemente seguir el estándar C99 es para algunos proveedores ( tos Microsoft).
Ben Combee
55
@Pete, si quieres ser anal: (1) Este hilo no tiene nada que ver con ningún producto de Microsoft. (2) Este hilo nunca tuvo nada que ver con C ++. (3) No existe tal cosa como C ++ 97.
Ben Collins
55
Echar un vistazo a azillionmonkeys.com/qed/pstdint.h - un stdint.h próxima al portátil
gnud
73

El operador de coma no se usa ampliamente. Ciertamente se puede abusar de él, pero también puede ser muy útil. Este uso es el más común:

for (int i=0; i<10; i++, doSomethingElse())
{
  /* whatever */
}

Pero puede usar este operador en cualquier lugar. Observar:

int j = (printf("Assigning variable j\n"), getValueFromSomewhere());

Cada declaración se evalúa, pero el valor de la expresión será el de la última declaración evaluada.

Ben Collins
fuente
77
¡En 20 años de CI NUNCA he visto eso!
Martin Beckett
11
En C ++ puedes incluso sobrecargarlo.
Wouter Lievens
66
can! = debe, por supuesto. El peligro de sobrecargarlo es que lo integrado ya se aplica a todo, incluido el vacío, por lo que nunca dejará de compilarse por falta de sobrecarga disponible. Es decir, le da mucha cuerda al programador.
Aaron
El int dentro del bucle no funcionará con C: es una mejora de C ++. ¿Es "," la misma operación que para (i = 0, j = 10; i <j; j--, i ++)?
Aif
63

inicializando la estructura a cero

struct mystruct a = {0};

esto pondrá a cero todos los elementos de estructura.

mike511
fuente
2
Sin embargo, no pone a cero el relleno, si lo hay.
Mikeage
2
@simonn, no, no tiene un comportamiento indefinido si la estructura contiene tipos no integrales. memset con 0 en la memoria de un flotante / doble seguirá siendo cero cuando interprete el flotante / doble (flotante / doble están diseñados así a propósito).
Trevor Boyd Smith
66
@Andrew: memset/ callocdo "todos los bytes cero" (es decir, ceros físicos), que de hecho no está definido para todos los tipos. { 0 } está garantizado para intilar todo con los valores lógicos cero adecuados . Los punteros, por ejemplo, están garantizados para obtener sus valores nulos adecuados, incluso si el valor nulo en la plataforma dada es 0xBAADFOOD.
AnT
1
@nvl: Obtiene cero físico cuando simplemente establece forzosamente toda la memoria ocupada por el objeto en el estado de todos los bits cero. Esto es lo que memsethace (con un 0segundo argumento). Obtiene cero lógico cuando inicializa / asigna 0(o { 0 }) al objeto en el código fuente. Estos dos tipos de ceros no necesariamente producen el mismo resultado. Como en el ejemplo con puntero. Cuando lo haces memsetcon un puntero, obtienes un 0x0000puntero. Pero cuando asigna 0a un puntero, obtiene un valor de puntero nulo , que a nivel físico podría ser 0xBAADF00Do cualquier otra cosa.
AnT
3
@nvl: Bueno, en la práctica, la diferencia a menudo es solo conceptual. Pero en teoría, prácticamente cualquier tipo puede tenerlo. Por ejemplo, double. Por lo general, se implementa de acuerdo con el estándar IEEE-754, en el que el cero lógico y el cero físico son iguales. Pero el idioma no requiere IEEE-754. Entonces, puede suceder que cuando lo haga double d = 0;(cero lógico), físicamente algunos bits en la memoria ocupada por dno sean cero.
AnT
52

Constantes de varios caracteres:

int x = 'ABCD';

Esto se establece xen 0x41424344(o 0x44434241, dependiendo de la arquitectura).

EDITAR: Esta técnica no es portátil, especialmente si serializa el int. Sin embargo, puede ser extremadamente útil crear enumeraciones autodocumentadas. p.ej

enum state {
    stopped = 'STOP',
    running = 'RUN!',
    waiting = 'WAIT',
};

Esto lo hace mucho más simple si está viendo un volcado de memoria sin procesar y necesita determinar el valor de una enumeración sin tener que buscarlo.

Ferruccio
fuente
Estoy bastante seguro de que esto no es una construcción portátil. El resultado de crear una constante de varios caracteres está definido por la implementación.
Mark Bessey
8
Los comentarios "no portátiles" pierden el punto por completo. Es como criticar un programa por usar INT_MAX solo porque INT_MAX "no es portátil" :) Esta característica es tan portátil como debe ser. La constante de múltiples caracteres es una característica extremadamente útil que proporciona una forma legible para generar ID enteros únicos.
AnT
1
@ Chris Lutz: estoy bastante seguro de que la coma final se remonta a K&R. Se describe en la segunda edición (1988).
Ferruccio
1
@Ferruccio: debe estar pensando en la coma final en las listas de inicializadores agregados. En cuanto a la coma final en las declaraciones de enumeración, es una adición reciente, C99.
AnT
3
Olvidó 'HANG' o 'BSOD' :-)
JBRWilkinson el
44

Nunca usé campos de bits, pero suenan bien para cosas de nivel ultra bajo.

struct cat {
    unsigned int legs:3;  // 3 bits for legs (0-4 fit in 3 bits)
    unsigned int lives:4; // 4 bits for lives (0-9 fit in 4 bits)
    // ...
};

cat make_cat()
{
    cat kitty;
    kitty.legs = 4;
    kitty.lives = 9;
    return kitty;
}

Esto significa que sizeof(cat)puede ser tan pequeño como sizeof(char).


Comentarios incorporados de Aaron y Leppie , gracias chicos.

Motti
fuente
La combinación de estructuras y uniones es aún más interesante: en sistemas embebidos o código de controlador de bajo nivel. Un ejemplo es cuando desea analizar los registros de una tarjeta SD, puede leerlo usando union (1) y leerlo usando union (2), que es una estructura de campos de bits.
ComSubVie
55
Los campos de bits no son portátiles: el compilador puede elegir libremente si, en su ejemplo, a los tramos se les asignarán los 3 bits más significativos o los 3 bits menos significativos.
zvrba
3
Los campos de bits son un ejemplo de dónde el estándar les da a las implementaciones tanta libertad en cómo se implementan, que en la práctica, son casi inútiles. Si le importa cuántos bits ocupa un valor y cómo se almacena, es mejor que use máscaras de bits.
Mark Bessey
26
Los campos de bits son de hecho portátiles siempre que los trate como los elementos de la estructura que son, y no como "piezas enteras". El tamaño, no la ubicación, es importante en un sistema integrado con memoria limitada, ya que cada bit es precioso ... pero la mayoría de los codificadores de hoy son demasiado jóvenes para recordarlo. :-)
Adam Liss
55
@ Adam: la ubicación puede ser importante en un sistema integrado (o en otro lugar), si depende de la posición del campo de bits dentro de su byte. El uso de máscaras elimina cualquier ambigüedad. Del mismo modo para los sindicatos.
Steve Melnikoff 01 de
37

C tiene un estándar, pero no todos los compiladores C son totalmente compatibles (¡todavía no he visto ningún compilador C99 totalmente compatible!).

Dicho esto, los trucos que prefiero son aquellos que no son obvios y portátiles en todas las plataformas, ya que dependen de la semántica en C. Por lo general, se trata de macros o aritmética de bits.

Por ejemplo: intercambiar dos enteros sin signo sin usar una variable temporal:

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

o "extender C" para representar máquinas de estados finitos como:

FSM {
  STATE(x) {
    ...
    NEXTSTATE(y);
  }

  STATE(y) {
    ...
    if (x == 0) 
      NEXTSTATE(y);
    else 
      NEXTSTATE(x);
  }
}

eso se puede lograr con las siguientes macros:

#define FSM
#define STATE(x)      s_##x :
#define NEXTSTATE(x)  goto s_##x

Sin embargo, en general, no me gustan los trucos que son inteligentes, pero hacen que el código sea innecesariamente complicado de leer (como el ejemplo de intercambio) y me encantan los que aclaran el código y transmiten directamente la intención (como el ejemplo de FSM) .

Remo.D
fuente
18
C admite encadenamiento, por lo que puede hacer a ^ = b ^ = a ^ = b;
DO
44
Hablando estrictamente, el ejemplo de estado es una señal del preprocesador, y no el lenguaje C: es posible usar el primero sin el segundo.
Greg Whitfield
15
OJ: en realidad lo que sugiere es un comportamiento indefinido debido a las reglas de puntos de secuencia. Puede funcionar en la mayoría de los compiladores, pero no es correcto o portátil.
Evan Teran
55
El intercambio Xor en realidad podría ser menos eficiente en el caso de un registro gratuito. Cualquier optimizador decente haría que la variable temporal sea un registro. Dependiendo de la implementación (y la necesidad de soporte de paralelismo), el intercambio podría usar memoria real en lugar de un registro (que sería lo mismo).
Paul de Vrieze
27
por favor, nunca hagas esto: en.wikipedia.org/wiki/…
Christian Oudard
37

Estructuras entrelazadas como el dispositivo de Duff :

strncpy(to, from, count)
char *to, *from;
int count;
{
    int n = (count + 7) / 8;
    switch (count % 8) {
    case 0: do { *to = *from++;
    case 7:      *to = *from++;
    case 6:      *to = *from++;
    case 5:      *to = *from++;
    case 4:      *to = *from++;
    case 3:      *to = *from++;
    case 2:      *to = *from++;
    case 1:      *to = *from++;
               } while (--n > 0);
    }
}
ComSubVie
fuente
29
@ComSubVie, cualquiera que use el Dispositivo de Duff es un niño de script que vio el Dispositivo de Duff y pensó que su código sería 1337 si usara el Dispositivo de Duff. (1.) El dispositivo Duff no ofrece ningún aumento de rendimiento en el procesador moderno porque los procesadores modernos tienen un bucle de sobrecarga cero. En otras palabras, es un código obsoleto. (2.) Incluso si su procesador no ofrece bucle de sobrecarga cero, probablemente tendrá algo como SSE / altivec / procesamiento de vectores que avergonzará a su dispositivo Duff's cuando use memcpy (). (3.) ¿Mencioné que otro que hacer memcpy () duff's no es útil?
Trevor Boyd Smith
2
@ComSubVie, conoce a mi Puño de la muerte ( en.wikipedia.org/wiki/… )
Trevor Boyd Smith
12
@Trevor: entonces solo script kiddies program 8051 y microcontroladores PIC, ¿verdad?
SF.
66
@Trevor Boyd Smith: Si bien el dispositivo de Duff parece anticuado, sigue siendo una curiosidad histórica, lo que valida la respuesta de ComSubVie. De todos modos, citando Wikipedia: "Cuando se eliminaron numerosas instancias del dispositivo de Duff del servidor XFree86 en la versión 4.0, hubo una mejora notable en el rendimiento". ...
paercebal
2
En Symbian, una vez evaluamos varios bucles para la codificación rápida de píxeles; El dispositivo del Duff, en ensamblador, fue el más rápido. Por lo tanto, todavía tenía relevancia en los núcleos ARM principales en sus teléfonos inteligentes hoy.
Will
33

Soy muy aficionado a los inicializadores designados, agregados en C99 (y soportados en gcc durante mucho tiempo):

#define FOO 16
#define BAR 3

myStructType_t myStuff[] = {
    [FOO] = { foo1, foo2, foo3 },
    [BAR] = { bar1, bar2, bar3 },
    ...

La inicialización de la matriz ya no depende de la posición. Si cambia los valores de FOO o BAR, la inicialización de la matriz corresponderá automáticamente a su nuevo valor.

DGentry
fuente
La sintaxis que gcc ha admitido durante mucho tiempo no es la misma que la sintaxis estándar C99.
Mark Baker,
28

C99 tiene una increíble inicialización de estructura de cualquier orden.

struct foo{
  int x;
  int y;
  char* name;
};

void main(){
  struct foo f = { .y = 23, .name = "awesome", .x = -38 };
}

Jason
fuente
27

Las estructuras y matrices anónimas son mis favoritas. (cf. http://www.run.montefiore.ulg.ac.be/~martin/resources/kung-f00.html )

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

o

void myFunction(type* values) {
    while(*values) x=*values++;
}
myFunction((type[]){val1,val2,val3,val4,0});

incluso se puede usar para instanciar listas vinculadas ...

PypeBros
fuente
3
Esta característica generalmente se denomina "literales compuestos". Las estructuras anónimas (o sin nombre) designan estructuras anidadas que no tienen nombres de miembros.
calandoa
según mi CCG, "ISO C90 prohíbe los literales compuestos".
jmtd
"ISO C99 admite literales compuestos". "Como extensión, GCC admite literales compuestos en modo C89 y en C ++" (dixit info gcc). Además, "Como una extensión de GNU, GCC permite la inicialización de objetos con una duración de almacenamiento estático mediante literales compuestos (que no es posible en ISO C99, porque el inicializador no es una constante)".
PypeBros
24

gcc tiene varias extensiones del lenguaje C que disfruto, que se pueden encontrar aquí . Algunos de mis favoritos son atributos de función . Un ejemplo extremadamente útil es el atributo de formato. Esto se puede usar si define una función personalizada que toma una cadena de formato printf. Si habilita este atributo de función, gcc verificará sus argumentos para asegurarse de que su cadena de formato y argumentos coincidan y generará advertencias o errores según corresponda.

int my_printf (void *my_object, const char *my_format, ...)
            __attribute__ ((format (printf, 2, 3)));
Russell Bryant
fuente
24

La característica (oculta) que me "sorprendió" cuando vi por primera vez es sobre printf. Esta característica le permite utilizar variables para formatear los especificadores de formato. busque el código, verá mejor:

#include <stdio.h>

int main() {
    int a = 3;
    float b = 6.412355;
    printf("%.*f\n",a,b);
    return 0;
}

el * personaje logra este efecto.

kolistivra
fuente
24

Bueno ... creo que uno de los puntos fuertes del lenguaje C es su portabilidad y estandarización, así que cada vez que encuentro algún "truco oculto" en la implementación que estoy usando actualmente, trato de no usarlo porque trato de mantener mi Código C lo más estándar y portátil posible.

Giacomo Degli Esposti
fuente
Pero en realidad, ¿con qué frecuencia tiene que compilar su código con otro compilador?
Joe D
3
@ Joe D si su un proyecto multiplataforma como Windows / OSX / Linux, probablemente un poco, y también hay diferentes arco como x86 vs x86_64 y etc ...
Pharaun--
@JoeD A menos que estés en un proyecto de mente estrecha que esté feliz de casarse con un proveedor de compiladores, muy. Es posible que desee evitar tener que cambiar los compiladores, pero desea mantener esa opción abierta. Sin embargo, con los sistemas integrados, no siempre tienes una opción. AHS, ASS.
XTL
19

Aserciones en tiempo de compilación, como ya se discutió aquí .

//--- size of static_assertion array is negative if condition is not met
#define STATIC_ASSERT(condition) \
    typedef struct { \
        char static_assertion[condition ? 1 : -1]; \
    } static_assertion_t

//--- ensure structure fits in 
STATIC_ASSERT(sizeof(mystruct_t) <= 4096);
philant
fuente
16

Concatenación constante de cuerdas

Me sorprendió bastante no verlo ya en las respuestas, ya que todos los compiladores que conozco lo respaldan, pero muchos programadores parecen ignorarlo. A veces es realmente útil y no solo al escribir macros.

Caso de uso que tengo en mi código actual: tengo un #define PATH "/some/path/"archivo de configuración (realmente está configurado por el archivo MAKE). Ahora quiero construir la ruta completa, incluidos los nombres de archivo para abrir recursos. Solo va a:

fd = open(PATH "/file", flags);

En lugar de lo horrible, pero muy común:

char buffer[256];
snprintf(buffer, 256, "%s/file", PATH);
fd = open(buffer, flags);

Tenga en cuenta que la solución horrible común es:

  • tres veces más largo
  • mucho menos fácil de leer
  • mucho más lento
  • menos potente puesto en un límite de tamaño de búfer arbitrario (pero tendrías que usar un código aún más largo para evitarlo sin una constante constancia de cadenas).
  • usar más espacio de pila
kriss
fuente
1
También es útil dividir una cadena constante en múltiples líneas de origen sin usar sucio '\'.
dolmen
15

Bueno, nunca lo he usado, y no estoy seguro de si alguna vez se lo recomendaría a alguien, pero creo que esta pregunta estaría incompleta sin mencionar el truco de rutina de Simon Tatham .

Mark Baker
fuente
12

Al inicializar matrices o enumeraciones, puede poner una coma después del último elemento en la lista de inicializadores. p.ej:

int x[] = { 1, 2, 3, };

enum foo { bar, baz, boom, };

Esto se hizo para que si está generando código automáticamente no tenga que preocuparse por eliminar la última coma.

Ferruccio
fuente
Esto también es importante en un entorno de desarrolladores múltiples donde, por ejemplo, Eric agrega "baz" y luego George agrega "boom". Si Eric decide extraer su código para la próxima compilación del proyecto, aún se compila con el cambio de George. Muy importante para el control del código fuente de múltiples sucursales y los horarios de desarrollo superpuestos.
Harold Bamford
Las enumeraciones pueden ser C99. Los inicializadores de matriz y la coma final son K&R.
Ferruccio
Las enumeraciones simples estaban en c89, AFAIK. Al menos han existido por años.
XTL
12

La asignación de estructura es genial. Muchas personas no parecen darse cuenta de que las estructuras también son valores, y pueden asignarse alrededor, no hay necesidad de usarlas memcpy(), cuando una tarea simple hace el truco.

Por ejemplo, considere una biblioteca imaginaria de gráficos 2D, podría definir un tipo para representar una coordenada de pantalla (entera):

typedef struct {
   int x;
   int y;
} Point;

Ahora, hace cosas que pueden parecer "incorrectas", como escribir una función que crea un punto inicializado a partir de argumentos de función, y lo devuelve, así:

Point point_new(int x, int y)
{
  Point p;
  p.x = x;
  p.y = y;
  return p;
}

Esto es seguro, siempre y cuando (por supuesto) el valor de retorno se copie por valor utilizando la asignación de estructura:

Point origin;
origin = point_new(0, 0);

De esta manera, puede escribir código bastante limpio y orientado a objetos, todo en un estándar simple C.

relajarse
fuente
44
Por supuesto, hay implicaciones de rendimiento al pasar estructuras grandes de esta manera; a menudo es útil (y de hecho es algo que mucha gente no se da cuenta de que puede hacer), pero debe considerar si es mejor pasar punteros.
Mark Baker,
1
Por supuesto, puede haber. También es bastante posible que el compilador detecte el uso y lo optimice.
Descanse el
Tenga cuidado si alguno de los elementos son punteros, ya que copiará los punteros mismos, no su contenido. Por supuesto, lo mismo es cierto si usa memcpy ().
Adam Liss
El compilador no puede optimizar este paso de conversión por valor con referencia, a menos que pueda hacer optimizaciones globales.
Blaisorblade
Probablemente valga la pena señalar que en C ++ el estándar específicamente permite optimizar la copia (el estándar tiene que permitir que los compiladores lo implementen porque significa que el constructor de copia que puede tener efectos secundarios no puede ser llamado), y dado que la mayoría de los compiladores de C ++ también son compiladores de C, es muy probable que su compilador haga esta optimización.
Joseph Garvin
10

Extraña indexación vectorial:

int v[100]; int index = 10; 
/* v[index] it's the same thing as index[v] */
EN S
fuente
44
Es aún mejor ... char c = 2 ["Hola"]; (c == 'l' después de esto).
yrp
55
No es tan extraño cuando consideras que v [index] == * (v + index) e index [v] == * (index + v)
Ferruccio
17
Por favor, dime que en realidad no usas esto "todo el tiempo", como hace la pregunta
Tryke
9

Los compiladores de C implementan uno de varios estándares. Sin embargo, tener un estándar no significa que todos los aspectos del lenguaje estén definidos. El dispositivo de Duff , por ejemplo, es una característica 'oculta' favorita que se ha vuelto tan popular que los compiladores modernos tienen un código de reconocimiento de propósito especial para garantizar que las técnicas de optimización no afecten el efecto deseado de este patrón de uso frecuente.

En general, se desaconsejan las funciones ocultas o los trucos de lenguaje, ya que se está ejecutando en el filo de cualquier estándar C que utilice su compilador. Muchos de estos trucos no funcionan de un compilador a otro, y a menudo este tipo de características fallarán de una versión de un conjunto de compiladores de un fabricante determinado a otra versión.

Varios trucos que han roto el código C incluyen:

  1. Confiando en cómo el compilador presenta estructuras en la memoria.
  2. Suposiciones sobre la endianidad de los enteros / flotantes.
  3. Suposiciones sobre la función ABIs.
  4. Suposiciones sobre la dirección en que crecen los cuadros de la pila.
  5. Suposiciones sobre el orden de ejecución dentro de las declaraciones.
  6. Suposiciones sobre el orden de ejecución de las declaraciones en argumentos de función.
  7. Suposiciones sobre el tamaño de bits o la precisión de los tipos short, int, long, float y double.

Otros problemas y cuestiones que surgen cada vez que los programadores hacen suposiciones sobre los modelos de ejecución que se especifican en la mayoría de los estándares de C como comportamiento "dependiente del compilador".

Kevin S.
fuente
Para resolver la mayoría de ellos, haga que esas suposiciones dependan de las características de su plataforma y describa cada plataforma en su propio encabezado. La ejecución de órdenes es una excepción; nunca confíe en eso; en otras ideas, cada plataforma necesita tener una decisión confiable.
Blaisorblade
2
@Blaisorblade, Aún mejor, use aserciones en tiempo de compilación para documentar sus suposiciones de una manera que haga que la compilación falle en una plataforma donde se violan.
RBerteig
Creo que uno debe combinar ambos, para que su código funcione en múltiples plataformas (esa era la intención original), y si las macros de características se configuran de manera incorrecta, las aserciones en tiempo de compilación lo captarán. No estoy seguro si, por ejemplo, la suposición sobre la función ABI es verificable como aserciones en tiempo de compilación, pero debería ser posible para la mayoría de las otras (válidas) (excepto el orden de ejecución ;-)).
Blaisorblade
Las comprobaciones de la función ABI deben ser manejadas por un conjunto de pruebas.
dolmen
9

Cuando use sscanf, puede usar% n para averiguar dónde debe continuar leyendo:

sscanf ( string, "%d%n", &number, &length );
string += length;

Aparentemente, no puede agregar otra respuesta, así que incluiré una segunda aquí, puede usar "&&" y "||" como condicionales:

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

int main()
{
   1 || puts("Hello\n");
   0 || puts("Hi\n");
   1 && puts("ROFL\n");
   0 && puts("LOL\n");

   exit( 0 );
}

Este código generará:

Hola
ROFL
onemasse
fuente
8

Usar INT (3) para establecer el punto de interrupción en el código es mi favorito de todos los tiempos

Dror Helper
fuente
3
No creo que sea portátil. Funcionará en x86, pero ¿qué pasa con otras plataformas?
Cristian Ciupitu
1
No tengo idea - Deberías publicar una pregunta al respecto
Dror Helper
2
Es una buena técnica y es específica de X86 (aunque probablemente haya técnicas similares en otras plataformas). Sin embargo, esta no es una característica de C. Depende de extensiones de C no estándar o llamadas a la biblioteca.
Ferruccio
1
En GCC hay __builtin_trap y para MSVC __debugbreak que funcionará en cualquier arquitectura compatible.
Axel Gneiting
8

Mi característica "oculta" favorita de C es el uso de% n en printf para volver a escribir en la pila. Normalmente printf muestra los valores de los parámetros de la pila en función de la cadena de formato, pero% n puede escribirlos de nuevo.

Consulte la sección 3.4.2 aquí . Puede provocar muchas vulnerabilidades desagradables.

Sridhar Iyer
fuente
el enlace ya no funciona, de hecho, el sitio en sí parece no funcionar. ¿Puedes proporcionar otro enlace?
thequark
@thequark: Cualquier artículo sobre "vulnerabilidades de cadena de formato" tendrá alguna información en él ... (por ejemplo, crypto.stanford.edu/cs155/papers/formatstring-1.2.pdf ). Sin embargo, debido a la naturaleza del campo, la seguridad los sitios web en sí son un poco escamosos y es difícil encontrar artículos académicos reales (con implementación).
Sridhar Iyer
8

Comprobación de suposiciones en tiempo de compilación usando enumeraciones: ejemplo estúpido, pero puede ser realmente útil para bibliotecas con constantes configurables en tiempo de compilación.

#define D 1
#define DD 2

enum CompileTimeCheck
{
    MAKE_SURE_DD_IS_TWICE_D = 1/(2*(D) == (DD)),
    MAKE_SURE_DD_IS_POW2    = 1/((((DD) - 1) & (DD)) == 0)
};
SC Madsen
fuente
2
+1 Neat. Solía ​​usar la macro CompilerAssert de Microsoft, pero la tuya tampoco está mal. ( #define CompilerAssert(exp) extern char _CompilerAssert[(exp)?1:-1])
Patrick Schlüter el
1
Me gusta el método de enumeración. El enfoque que utilicé antes aprovechó la eliminación del código muerto: "if (something_bad) {void BLORG_IS_WOOZLED (void); BLORG_IS_WOOZLED ();}" que no falló hasta el momento del enlace, aunque ofreció la ventaja de dejar que el el programador sabe por mensaje de error que el blorg fue engañado.
supercat
8

Gcc (c) tiene algunas características divertidas que puede habilitar, como declaraciones de funciones anidadas y la forma a?: B del operador?: Que devuelve a si a no es falso.

Alex Brown
fuente
8

Descubrí recientemente 0 bitfields.

struct {
  int    a:3;
  int    b:2;
  int     :0;
  int    c:4;
  int    d:3;
};

que dará un diseño de

000aaabb 0ccccddd

en lugar de sin el: 0;

0000aaab bccccddd

El campo de ancho 0 indica que los siguientes campos de bits deben establecerse en la siguiente entidad atómica ( char)

revs tristopia
fuente
7

Macros de argumento variable de estilo C99, también conocido como

#define ERR(name, fmt, ...)   fprintf(stderr, "ERROR " #name ": " fmt "\n", \
                                  __VAR_ARGS__)

que se usaría como

ERR(errCantOpen, "File %s cannot be opened", filename);

Aquí también utilizo el operador stringize y la concatenación constante de string, otras características que realmente me gustan.

Ben Combee
fuente
Tienes una 'R' adicional en VA_ARGS .
Blaisorblade
6

Las variables automáticas de tamaño variable también son útiles en algunos casos. Estos se agregaron en n99 y se han admitido en gcc durante mucho tiempo.

void foo(uint32_t extraPadding) {
    uint8_t commBuffer[sizeof(myProtocol_t) + extraPadding];

Termina con un búfer en la pila con espacio para el encabezado del protocolo de tamaño fijo más datos de tamaño variable. Puede obtener el mismo efecto con alloca (), pero esta sintaxis es más compacta.

Debe asegurarse de que extraPadding sea un valor razonable antes de llamar a esta rutina, o terminará volando la pila. Tendría que verificar los argumentos antes de llamar a malloc o cualquier otra técnica de asignación de memoria, por lo que esto no es realmente inusual.

DGentry
fuente
¿Esto también funcionará correctamente si un byte / char no tiene exactamente 8 bits de ancho en la plataforma de destino? Lo sé, esos casos son raros, pero aún así ... :)
Stephan202