Fonctionnalités cachées de C


141

Je sais qu'il existe une norme derrière toutes les implémentations du compilateur C, il ne devrait donc pas y avoir de fonctionnalités cachées. Malgré cela, je suis sûr que tous les développeurs C ont des astuces cachées / secrètes qu'ils utilisent tout le temps.


Ce serait formidable si vous / quelqu'un modifiiez la «question» pour indiquer le choix des meilleures fonctionnalités cachées, comme dans les versions C # et Perl de cette question.
Donal Fellows

Réponses:


62

Pointeurs de fonction. Vous pouvez utiliser une table de pointeurs de fonction pour implémenter, par exemple, des interpréteurs de code à thread indirect rapide (FORTH) ou des répartiteurs de code d'octet, ou pour simuler des méthodes virtuelles de type OO.

Ensuite, il y a des gemmes cachées dans la bibliothèque standard, telles que qsort (), bsearch (), strpbrk (), strcspn () [les deux derniers étant utiles pour implémenter un remplacement strtok ()].

Un défaut de C est que le débordement arithmétique signé est un comportement indéfini (UB). Ainsi, chaque fois que vous voyez une expression telle que x + y, tous deux signés ints, cela peut potentiellement déborder et provoquer UB.


29
Mais s'ils avaient spécifié un comportement en cas de débordement, cela l'aurait rendu très lent sur les architectures où ce n'était pas le comportement normal. Un temps d'exécution très bas a toujours été un objectif de conception de C, ce qui signifie que beaucoup de choses comme celle-ci ne sont pas définies.
Mark Baker

9
Je sais très bien pourquoi le débordement est UB. C'est toujours une erreur, car la norme devrait avoir au moins fourni des routines de bibliothèque capables de tester le débordement arithmétique (de toutes les opérations de base) sans provoquer d'UB.
zvrba

2
@zvrba, "routines de bibliothèque qui peuvent tester le débordement arithmétique (de toutes les opérations de base)" si vous aviez ajouté cela, vous auriez eu un impact significatif sur les performances pour toute opération arithmétique entière. ===== Étude de cas Matlab AJOUTE spécifiquement la fonctionnalité de contrôle du comportement de débordement d'entier pour envelopper ou saturer. Et il lève également une exception chaque fois qu'un débordement se produit ==> Performance des opérations sur les entiers Matlab: TRÈS LENT. Ma propre conclusion: je pense que Matlab est une étude de cas convaincante qui montre pourquoi vous ne voulez pas de vérification de dépassement d'entier.
Trevor Boyd Smith

15
J'ai dit que la norme aurait dû fournir un support de bibliothèque pour la vérification du dépassement arithmétique. Maintenant, comment une routine de bibliothèque peut-elle subir une baisse de performances si vous ne l'utilisez jamais?
zvrba

5
Un gros inconvénient est que GCC n'a pas d'indicateur pour intercepter les débordements d'entiers signés et lancer une exception d'exécution. Bien qu'il existe des indicateurs x86 pour détecter de tels cas, GCC ne les utilise pas. Avoir un tel indicateur permettrait aux applications non critiques en termes de performances (en particulier héritées) de bénéficier de la sécurité avec un minimum ou pas de révision du code et de refactorisation.
Andrew Keeton

116

Plus une astuce du compilateur GCC, mais vous pouvez donner des indices d'indication de branche au compilateur (commun dans le noyau Linux)

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

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

Ce que j'aime à ce sujet, c'est que cela ajoute également une certaine expressivité à certaines fonctions.

void foo(int arg)
{
     if (unlikely(arg == 0)) {
           do_this();
           return;
     }
     do_that();
     ...
}

2
Cette astuce est cool ... :) Surtout avec les macros que vous définissez. :)
sundar - Réintégrer Monica

77
int8_t
int16_t
int32_t
uint8_t
uint16_t
uint32_t

Il s'agit d'un élément facultatif dans la norme, mais il doit s'agir d'une fonctionnalité masquée, car les gens les redéfinissent constamment. Une base de code sur laquelle j'ai travaillé (et je le fais encore, pour l'instant) a plusieurs redéfinitions, toutes avec des identifiants différents. La plupart du temps, c'est avec des macros de préprocesseur:

#define INT16 short
#define INT32  long

Etc. Cela me donne envie de m'arracher les cheveux. Utilisez simplement les typedefs entiers standards!


3
Je pense qu'ils sont environ C99. Je n'ai pas trouvé de moyen portable de m'assurer que ce serait là.
akauppi

3
Ils sont une partie facultative de C99, mais je ne connais aucun fournisseur de compilateurs qui ne l'implémente pas.
Ben Collins

10
stdint.h n'est pas facultatif dans C99, mais suivre la norme C99 l'est apparemment pour certains fournisseurs ( toux Microsoft).
Ben Combee

5
@Pete, si vous voulez être anal: (1) Ce fil n'a rien à voir avec un produit Microsoft. (2) Ce thread n'a jamais rien à voir avec C ++. (3) Il n'existe pas de C ++ 97.
Ben Collins

5
Jetez un œil à azillionmonkeys.com/qed/pstdint.h - un stdint.h proche du portable
gnud

73

L'opérateur virgule n'est pas largement utilisé. Il peut certes être abusé, mais il peut aussi être très utile. Cette utilisation est la plus courante:

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

Mais vous pouvez utiliser cet opérateur n'importe où. Observer:

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

Chaque instruction est évaluée, mais la valeur de l'expression sera celle de la dernière instruction évaluée.


7
En 20 ans de CI je n'ai JAMAIS vu ça!
Martin Beckett

11
En C ++, vous pouvez même le surcharger.
Wouter Lievens

6
can! = devrait, bien sûr. Le danger avec la surcharge est que le intégré s'applique déjà à tout, y compris à void, donc ne manquera jamais de compiler faute de surcharge disponible. Ie, donne beaucoup de corde au programmeur.
Aaron

L'int à l'intérieur de la boucle ne fonctionnera pas avec C: c'est une amélioration C ++. Le "," est-il la même opération que pour (i = 0, j = 10; i <j; j--, i ++)?
Aif du

63

initialisation de la structure à zéro

struct mystruct a = {0};

cela mettra à zéro tous les éléments de structure.


2
Cependant, il ne met pas à zéro le remplissage, le cas échéant.
Mikeage

2
@simonn, non, il ne fait pas de comportement indéfini si la structure contient des types non intégraux. memset avec 0 sur la mémoire d'un float / double sera toujours nul lorsque vous interpréterez le float / double (float / double sont conçus comme ça exprès).
Trevor Boyd Smith le

6
@Andrew: memset/ callocdo "tous les octets zéro" (c'est-à-dire les zéros physiques), qui n'est en effet pas défini pour tous les types. { 0 } est garanti de tout intilaize avec des valeurs zéro logiques appropriées . Les pointeurs, par exemple, sont garantis pour obtenir leurs valeurs nulles appropriées, même si la valeur nulle sur la plate-forme donnée est 0xBAADFOOD.
Du

1
@nvl: Vous obtenez un zéro physique lorsque vous définissez de force toute la mémoire occupée par l'objet à l'état tous bits-zéro. C'est ce que memsetfait (avec 0comme deuxième argument). Vous obtenez un zéro logique lorsque vous initialisez / affectez 0(ou { 0 }) à l'objet dans le code source. Ces deux types de zéros ne produisent pas nécessairement le même résultat. Comme dans l'exemple avec pointeur. Lorsque vous faites memsetsur un pointeur, vous obtenez un 0x0000pointeur. Mais lorsque vous affectez 0à un pointeur, vous obtenez une valeur de pointeur nulle , ce qui au niveau physique peut être 0xBAADF00Dou autre chose.
Du

3
@nvl: Eh bien, en pratique, la différence n'est souvent que conceptuelle. Mais en théorie, pratiquement n'importe quel type peut l'avoir. Par exemple double,. Habituellement, il est implémenté conformément à la norme IEEE-754, dans laquelle le zéro logique et le zéro physique sont identiques. Mais IEEE-754 n'est pas requis par la langue. Il se peut donc que lorsque vous faites double d = 0;(zéro logique), physiquement certains bits de la mémoire occupés par dne soient pas zéro.
Du

52

Constantes multi-caractères:

int x = 'ABCD';

Cela définit xà 0x41424344(ou 0x44434241, selon l'architecture).

EDIT: Cette technique n'est pas portable, surtout si vous sérialisez l'int. Cependant, il peut être extrêmement utile de créer des énumérations auto-documentées. par exemple

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

Cela rend les choses beaucoup plus simples si vous regardez un vidage de la mémoire brute et que vous devez déterminer la valeur d'une énumération sans avoir à la rechercher.


Je suis presque sûr que ce n'est pas une construction portable. Le résultat de la création d'une constante à plusieurs caractères est défini par l'implémentation.
Mark Bessey

8
Les commentaires "non portables" manquent complètement le point. C'est comme critiquer un programme pour avoir utilisé INT_MAX simplement parce que INT_MAX n'est "pas portable" :) Cette fonctionnalité est aussi portable qu'elle le doit. La constante multi-caractères est une fonctionnalité extrêmement utile qui fournit un moyen lisible de générer des ID entiers uniques.
Du

1
@Chris Lutz - Je suis presque sûr que la virgule de fin remonte à K&R. Il est décrit dans la deuxième édition (1988).
Ferruccio

1
@Ferruccio: Vous devez penser à la virgule de fin dans les listes de lancement agrégées. Quant à la virgule de fin dans les déclarations d'énumération - c'est un ajout récent, C99.
Du

3
Vous avez oublié 'HANG' ou 'BSOD' :-)
JBRWilkinson

44

Je n'ai jamais utilisé de champs de bits, mais ils sonnent bien pour les trucs à très bas niveau.

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;
}

Cela signifie que cela sizeof(cat)peut être aussi petit que sizeof(char).


Commentaires incorporés par Aaron et leppie , merci les gars.


La combinaison de structures et d'unions est encore plus intéressante - sur les systèmes embarqués ou le code de pilote de bas niveau. Un exemple est que lorsque vous aimez analyser les registres d'une carte SD, vous pouvez la lire en utilisant union (1) et la lire en utilisant union (2) qui est une structure de champs de bits.
ComSubVie

5
Les champs de bits ne sont pas portables - le compilateur peut choisir librement si, dans votre exemple, les segments se verront attribuer les 3 bits les plus significatifs ou les 3 bits les moins significatifs.
zvrba

3
Les Bitfields sont un exemple où la norme donne aux implémentations tellement de liberté dans la façon dont elles sont implémentées, qu'en pratique, elles sont presque inutiles. Si vous vous souciez du nombre de bits qu'une valeur prend et de la façon dont elle est stockée, il vaut mieux utiliser des masques de bits.
Mark Bessey

26
Les champs de bits sont en effet portables tant que vous les traitez comme les éléments de structure qu'ils sont, et non comme des «morceaux d'entiers». La taille, pas l'emplacement, compte dans un système embarqué avec une mémoire limitée, car chaque bit est précieux ... mais la plupart des codeurs d'aujourd'hui sont trop jeunes pour s'en souvenir. :-)
Adam Liss

5
@Adam: l'emplacement peut avoir une importance dans un système embarqué (ou ailleurs), si vous dépendez de la position du champ de bits dans son octet. L'utilisation de masques supprime toute ambiguïté. De même pour les syndicats.
Steve Melnikoff

37

C a une norme mais tous les compilateurs C ne sont pas entièrement compatibles (je n'ai pas encore vu de compilateur C99 entièrement conforme!).

Cela dit, les astuces que je préfère sont celles qui ne sont pas évidentes et portables sur toutes les plates-formes car elles reposent sur la sémantique C. Il s'agit généralement de macros ou d'arithmétique binaire.

Par exemple: permuter deux entiers non signés sans utiliser de variable temporaire:

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

ou "étendre C" pour représenter des machines à états finis comme:

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

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

cela peut être réalisé avec les macros suivantes:

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

En général, cependant, je n'aime pas les astuces qui sont intelligentes mais rendent le code inutilement compliqué à lire (comme l'exemple d'échange) et j'aime celles qui rendent le code plus clair et transmettent directement l'intention (comme l'exemple FSM) .


18
C prend en charge le chaînage, vous pouvez donc faire a ^ = b ^ = a ^ = b;
JO.

4
Strictement parlant, l'exemple d'état est une coche du préprocesseur, et non du langage C - il est possible d'utiliser le premier sans le second.
Greg Whitfield le

15
JO: en fait, ce que vous suggérez est un comportement indéfini à cause des règles de points de séquence. Cela peut fonctionner sur la plupart des compilateurs, mais n'est ni correct ni portable.
Evan Teran

5
Le swap Xor pourrait en fait être moins efficace dans le cas d'un registre gratuit. Tout optimiseur décent ferait de la variable temp un registre. En fonction de l'implémentation (et du besoin de prise en charge du parallélisme), le swap peut en fait utiliser de la mémoire réelle au lieu d'un registre (qui serait le même).
Paul de Vrieze

27
s'il vous plaît ne faites jamais ça: en.wikipedia.org/wiki/...
Christian Oudard

37

Structures entrelacées comme le dispositif 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);
    }
}

29
@ComSubVie, quiconque utilise Duff's Device est un script kiddy qui a vu Duff's Device et pensait que son code ressemblerait à 1337 s'il utilisait Duff's Device. (1.) Le périphérique de Duff n'offre aucune amélioration des performances sur les processeurs modernes car les processeurs modernes ont une boucle sans surcharge. En d'autres termes, c'est un morceau de code obsolète. (2.) Même si votre processeur n'offre pas de boucle sans surcharge, il aura probablement quelque chose comme SSE / altivec / vector-processing qui fera honte à votre Duff's Device lorsque vous utilisez memcpy (). (3.) Ai-je mentionné que d'autres que faire duff de memcpy () n'est pas utile?
Trevor Boyd Smith le

2
@ComSubVie, s'il vous plaît rencontrer mon poing de la mort ( en.wikipedia.org/wiki/... )
Trevor Boyd Smith

12
@Trevor: donc seulement le programme de script kiddies 8051 et les microcontrôleurs PIC, non?
SF.

6
@Trevor Boyd Smith: Bien que le Duff's Device semble obsolète, c'est toujours une curiosité historique, qui valide la réponse de ComSubVie. Quoi qu'il en soit, citant Wikipedia: "Lorsque de nombreuses instances du périphérique de Duff ont été supprimées du serveur XFree86 dans la version 4.0, il y a eu une amélioration notable des performances." ...
paercebal

2
Sur Symbian, nous avons déjà évalué diverses boucles pour un codage rapide des pixels; l'appareil du duff, en assembleur, était le plus rapide. Il était donc toujours pertinent sur les cœurs ARM traditionnels de vos smartphones aujourd'hui.
Sera

33

J'aime beaucoup les initialiseurs désignés, ajoutés dans C99 (et pris en charge dans gcc depuis longtemps):

#define FOO 16
#define BAR 3

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

L'initialisation du tableau ne dépend plus de la position. Si vous modifiez les valeurs de FOO ou BAR, l'initialisation du tableau correspondra automatiquement à leur nouvelle valeur.


La syntaxe gcc prise en charge depuis longtemps n'est pas la même que la syntaxe standard C99.
Mark Baker

28

C99 a une super initialisation de structure dans n'importe quel ordre.

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

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


27

les structures et tableaux anonymes sont mes préférés. (cf. http://www.run.montefiore.ulg.ac.be/~martin/resources/kung-f00.html )

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

ou

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

il peut même être utilisé pour instancier des listes chaînées ...


3
Cette fonctionnalité est généralement appelée «littéraux composés». Les structures anonymes (ou sans nom) désignent des structures imbriquées qui n'ont pas de nom de membre.
calandoa

selon mon GCC, "ISO C90 interdit les littéraux composés".
jmtd

"ISO C99 prend en charge les littéraux composés." "En tant qu'extension, GCC supporte les littéraux composés en mode C89 et en C ++" (dixit info gcc). De plus, "En tant qu'extension GNU, GCC permet l'initialisation d'objets avec une durée de stockage statique par des littéraux composés (ce qui n'est pas possible dans ISO C99, car l'initialiseur n'est pas une constante)."
PypeBros

24

gcc a un certain nombre d'extensions au langage C que j'apprécie, qui peuvent être trouvées ici . Certains de mes favoris sont des attributs de fonction . Un exemple extrêmement utile est l'attribut format. Cela peut être utilisé si vous définissez une fonction personnalisée qui prend une chaîne de format printf. Si vous activez cet attribut de fonction, gcc vérifiera vos arguments pour s'assurer que votre chaîne de format et vos arguments correspondent et générera des avertissements ou des erreurs selon le cas.

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

24

la fonction (cachée) qui m'a "choqué" quand j'ai vu pour la première fois concerne printf. cette fonctionnalité vous permet d'utiliser des variables pour formater les spécificateurs de format eux-mêmes. cherchez le code, vous verrez mieux:

#include <stdio.h>

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

le caractère * réalise cet effet.


24

Eh bien ... je pense que l'un des points forts du langage C est sa portabilité et sa standardité, donc chaque fois que je trouve un "truc caché" dans l'implémentation que j'utilise actuellement, j'essaye de ne pas l'utiliser car j'essaye de garder mon Code C aussi standard et portable que possible.


Mais en réalité, à quelle fréquence devez-vous compiler votre code avec un autre compilateur?
Joe D

3
@Joe D si c'est un projet multiplateforme comme Windows / OSX / Linux, probablement un peu, et il y a aussi un arc différent tel que x86 vs x86_64 et etc ...
Pharaun

@JoeD Sauf si vous êtes dans un projet très restreint qui est heureux d'épouser un fournisseur de compilateur, très. Vous voudrez peut-être éviter d'avoir à changer de compilateur, mais vous voulez garder cette option ouverte. Cependant, avec les systèmes embarqués, vous n'avez pas toujours le choix. AHS, ASS.
XTL

19

Assertions au moment de la compilation, comme déjà discuté ici .

//--- 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);

16

Concaténation de chaînes constante

J'ai été assez surpris de ne pas le voir déjà dans les réponses, car tous les compilateurs que je connais le supportent, mais de nombreux programmeurs semblent l'ignorer. Parfois, c'est vraiment pratique et pas seulement lors de l'écriture de macros.

Cas d'utilisation que j'ai dans mon code actuel: j'ai un #define PATH "/some/path/"dans un fichier de configuration (en fait, il est réglé par le makefile). Maintenant, je veux créer le chemin complet avec les noms de fichiers pour ouvrir les ressources. Cela va simplement à:

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

Au lieu de l'horrible, mais très commun:

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

Notez que la solution horrible commune est:

  • trois fois plus longtemps
  • beaucoup moins facile à lire
  • beaucoup plus lent
  • moins puissant lorsqu'il est défini sur une limite de taille de tampon arbitraire (mais vous devrez utiliser un code encore plus long pour éviter cela sans contaténation de chaînes constantes).
  • utiliser plus d'espace de pile

1
Il est également utile de diviser une constante de chaîne sur plusieurs lignes source sans utiliser de `\` sale.
dolmen

15

Eh bien, je ne l'ai jamais utilisé, et je ne sais pas si je le recommanderais à qui que ce soit, mais je pense que cette question serait incomplète sans une mention de l'astuce de co-routine de Simon Tatham .


12

Lors de l'initialisation de tableaux ou d'énumérations, vous pouvez mettre une virgule après le dernier élément de la liste d'initialisation. par exemple:

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

enum foo { bar, baz, boom, };

Cela a été fait pour que si vous générez du code automatiquement, vous n'ayez pas à vous soucier d'éliminer la dernière virgule.


Ceci est également important dans un environnement multi-développeurs où, par exemple, Eric ajoute «baz», puis George ajoute «boom». Si Eric décide de retirer son code pour la prochaine version du projet, il se compile toujours avec le changement de George. Très important pour le contrôle du code source multi-branches et les calendriers de développement qui se chevauchent.
Harold Bamford

Les énumérations peuvent être C99. Les initialiseurs de tableau et la virgule de fin sont K&R.
Ferruccio

Les énumérations simples étaient en c89, AFAIK. Au moins, ils existent depuis des lustres.
XTL

12

L'affectation des structures est cool. Beaucoup de gens ne semblent pas se rendre compte que les structures sont aussi des valeurs et peuvent être attribuées, il n'est pas nécessaire d'utilisermemcpy() , lorsqu'une simple affectation fait l'affaire.

Par exemple, considérons une bibliothèque de graphiques 2D imaginaires, elle pourrait définir un type pour représenter une coordonnée d'écran (entière):

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

Maintenant, vous faites des choses qui peuvent sembler "incorrectes", comme écrire une fonction qui crée un point initialisé à partir des arguments de fonction, et le renvoie, comme ceci:

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

C'est sûr, tant (bien sûr) que la valeur de retour est copiée par valeur à l'aide de l'affectation de structure:

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

De cette façon, vous pouvez écrire du code assez propre et orienté objet, le tout en C.


4
Bien sûr, il y a des implications sur les performances à passer autour de grandes structures de cette manière; c'est souvent utile (et c'est en effet quelque chose que beaucoup de gens ne réalisent pas que vous pouvez faire) mais vous devez vous demander s'il est préférable de passer des pointeurs.
Mark Baker

1
Bien sûr, il pourrait avoir. Il est également tout à fait possible pour le compilateur de détecter l'utilisation et de l'optimiser.
détendre

Faites attention si l'un des éléments est des pointeurs, car vous allez copier les pointeurs eux-mêmes, pas leur contenu. Bien sûr, la même chose est vraie si vous utilisez memcpy ().
Adam Liss

Le compilateur ne peut pas optimiser cette conversion par valeur en passant avec by-referenece, à moins qu'il ne puisse effectuer des optimisations globales.
Blaisorblade

Il est probablement intéressant de noter qu'en C ++, le standard permet spécifiquement d'optimiser la copie (le standard doit permettre aux compilateurs de l'implémenter car cela signifie que le constructeur de copie qui peut avoir des effets secondaires peut ne pas être appelé), et puisque la plupart des compilateurs C ++ sont également des compilateurs C, il y a de fortes chances que votre compilateur fasse cette optimisation.
Joseph Garvin

10

Indexation vectorielle étrange:

int v[100]; int index = 10; 
/* v[index] it's the same thing as index[v] */

4
C'est encore mieux ... char c = 2 ["Hello"]; (c == 'l' après cela).
an

5
Pas si étrange quand on considère que v [index] == * (v + index) et index [v] == * (index + v)
Ferruccio

17
S'il vous plaît dites-moi que vous n'utilisez pas réellement cela "tout le temps", comme la question le demande!
Tryke le

9

Les compilateurs C implémentent l'une des nombreuses normes. Cependant, avoir une norme ne signifie pas que tous les aspects de la langue sont définis. L'appareil de Duff , par exemple, est une fonctionnalité `` cachée '' préférée qui est devenue si populaire que les compilateurs modernes ont un code de reconnaissance à usage spécial pour s'assurer que les techniques d'optimisation ne sabotent pas l'effet souhaité de ce modèle souvent utilisé.

En général, les fonctionnalités cachées ou les astuces de langage sont déconseillées car vous exécutez à la limite du (des) standard (s) C utilisé par votre compilateur. Beaucoup de ces astuces ne fonctionnent pas d'un compilateur à un autre, et souvent ces types de fonctionnalités échoueront d'une version d'une suite de compilateurs d'un fabricant donné à une autre version.

Diverses astuces qui ont cassé le code C incluent:

  1. S'appuyant sur la façon dont le compilateur dispose les structures en mémoire.
  2. Hypothèses sur l' endianité des entiers / flottants.
  3. Hypothèses sur les ABI de fonction.
  4. Hypothèses sur la direction de croissance des cadres.
  5. Hypothèses sur l'ordre d'exécution dans les instructions.
  6. Hypothèses sur l'ordre d'exécution des instructions dans les arguments de fonction.
  7. Hypothèses sur la taille en bits ou la précision des types short, int, long, float et double.

D'autres problèmes et problèmes qui surviennent chaque fois que les programmeurs font des hypothèses sur les modèles d'exécution qui sont tous spécifiés dans la plupart des normes C comme un comportement «dépendant du compilateur».


Pour résoudre la plupart de ces problèmes, faites en sorte que ces hypothèses dépendent des caractéristiques de votre plate-forme et décrivez chaque plate-forme dans son propre en-tête. L'exécution des ordres est une exception - ne vous fiez jamais à cela; sur les autres idées, chaque plateforme doit avoir une décision fiable.
Blaisorblade

2
@Blaisorblade, Mieux encore, utilisez des assertions au moment de la compilation pour documenter vos hypothèses d'une manière qui fera échouer la compilation sur une plate-forme où elles sont violées.
RBerteig

Je pense qu'il faut combiner les deux, afin que votre code fonctionne sur plusieurs plates-formes (c'était l'intention d'origine), et si les macros de fonctionnalités sont mal définies, les assertions au moment de la compilation le saisiront. Je ne sais pas si, par exemple, les hypothèses sur les ABI de fonction sont vérifiables en tant qu'assertions à la compilation, mais cela devrait être possible pour la plupart des autres (valides) (sauf l'ordre d'exécution ;-)).
Blaisorblade

Les contrôles ABI des fonctions doivent être gérés par une suite de tests.
dolmen

9

Lorsque vous utilisez sscanf, vous pouvez utiliser% n pour savoir où vous devez continuer à lire:

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

Apparemment, vous ne pouvez pas ajouter une autre réponse, donc je vais en inclure une deuxième ici, vous pouvez utiliser "&&" et "||" comme conditionnels:

#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 );
}

Ce code affichera:

salut
ROFL

8

utiliser INT (3) pour définir le point d'arrêt au code est mon préféré de tous les temps


3
Je ne pense pas que ce soit portable. Cela fonctionnera sur x86, mais qu'en est-il des autres plates-formes?
Cristian Ciupitu

1
Je n'ai aucune idée - Vous devriez poser une question à ce sujet
Dror Helper

2
C'est une bonne technique et elle est spécifique à X86 (bien qu'il existe probablement des techniques similaires sur d'autres plates-formes). Cependant, ce n'est pas une fonctionnalité de C. Cela dépend d'extensions C non standard ou d'appels de bibliothèque.
Ferruccio

1
Dans GCC, il y a __builtin_trap et pour MSVC __debugbreak qui fonctionnera sur n'importe quelle architecture prise en charge.
Axel Gneiting

8

Ma fonction "cachée" préférée de C est l'utilisation de% n dans printf pour réécrire dans la pile. Normalement, printf extrait les valeurs des paramètres de la pile en fonction de la chaîne de format, mais% n peut les réécrire.

Consultez la section 3.4.2 ici . Peut conduire à de nombreuses vulnérabilités désagréables.


le lien ne fonctionne plus, en fait le site lui-même semble ne pas fonctionner. Pouvez-vous fournir un autre lien?
thequark

@thequark: Tout article sur les "vulnérabilités des chaînes de format" contiendra des informations. (par exemple, crypto.stanford.edu/cs155/papers/formatstring-1.2.pdf ) .. Cependant, en raison de la nature du champ, la sécurité les sites Web eux-mêmes sont un peu floconneux et les vrais articles académiques sont difficiles à trouver (avec la mise en œuvre).
Sridhar Iyer

8

Vérification des hypothèses au moment de la compilation à l'aide d'énumérations: exemple stupide, mais peut être vraiment utile pour les bibliothèques avec des constantes configurables au moment de la compilation.

#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)
};

2
+1 Neat. J'utilisais la macro CompilerAssert de Microsoft, mais la vôtre n'est pas mauvaise non plus. ( #define CompilerAssert(exp) extern char _CompilerAssert[(exp)?1:-1])
Patrick Schlüter

1
J'aime la méthode de dénombrement. L'approche que j'avais utilisée auparavant tirait parti de l'élimination du code mort: "if (something_bad) {void BLORG_IS_WOOZLED (void); BLORG_IS_WOOZLED ();}" qui n'a pas commis d'erreur jusqu'au moment de la liaison, bien qu'elle ait offert l'avantage de laisser le le programmeur sait par message d'erreur que le blorg a été courtisé.
supercat

8

Gcc (c) a quelques fonctionnalités amusantes que vous pouvez activer, telles que les déclarations de fonctions imbriquées et la forme a?: B de l'opérateur?:, Qui renvoie a si a n'est pas faux.


8

J'ai découvert récemment 0 bitfields.

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

qui donnera une mise en page de

000aaabb 0ccccddd

au lieu de sans le: 0;

0000aaab bccccddd

Le champ de largeur 0 indique que les champs de bits suivants doivent être définis sur l'entité atomique suivante ( char)


7

Macros d'argument de variable de style C99, aka

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

qui serait utilisé comme

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

Ici, j'utilise également l'opérateur stringize et la concatentation constante de chaîne, d'autres fonctionnalités que j'aime beaucoup.


Vous avez un «R» supplémentaire dans VA_ARGS .
Blaisorblade

6

Les variables automatiques de taille variable sont également utiles dans certains cas. Celles-ci ont été ajoutées sur nC99 et sont prises en charge dans gcc depuis longtemps.

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

Vous vous retrouvez avec un tampon sur la pile avec de la place pour l'en-tête de protocole de taille fixe plus les données de taille variable. Vous pouvez obtenir le même effet avec alloca (), mais cette syntaxe est plus compacte.

Vous devez vous assurer que extraPadding est une valeur raisonnable avant d'appeler cette routine, sinon vous finissez par faire exploser la pile. Vous devrez vérifier les arguments avant d'appeler malloc ou toute autre technique d'allocation de mémoire, donc ce n'est pas vraiment inhabituel.


Cela fonctionnera-t-il également correctement si un octet / caractère ne fait pas exactement 8 bits de large sur la plate-forme cible? Je sais, ces cas sont rares, mais quand même ... :)
Stephan202
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.