Les recoins obscurs du C

(en)

Si vous programmez en C depuis quelques temps déjà, vous êtes probablement plus confiants que si vous aviez passé autant de temps à programmer en C++ ou en java.

En cause, le côté minimaliste du langage et de sa bibliothèque standard.

Pourtant, quelques perles étranges s'y cachent.

La version actuelle du langage, le c99, a apporté un lot de nouvelles fonctionnalités semblent complètement étrangères à la plupart des développeurs C (même si les normes plus anciennes ont aussi leur lot de coins sombres).

Voici celles que je connais :

Sizeof peut avoir des effets de bord

int main(void) {
    return sizeof(int[printf("ooops\n")]);
}

sizeof sur des types variadiques demande d'évaluer une expression arbitraire !

Flottant hexadécimal avec exposant

int main() {
  return assert(0xap-1 == 5.0);
}

Le p signifie puissance, et est suivi par un exposant base 2 encodé en base 10. L'expression a pour type double, mais cela peut être changé en float en ajoutant un f terminal.

Déclarations compatibles et tableaux arguments

#include <stdio.h>

void a(); // 1
void a(long story, int a[*], int b[static 12][*][*]); // 2
void a(long story, int a[42], int b[*][*][64]);       // 3
void a(long story, int a[*], int b[const 42][24][*]); // 4
// void a(long story, int a[*], int b[*][666][*]);    // 5
// void a(long story, int a[*], int b[*][*][666]);    // 6

void a(long story, int a[42], int b[restrict 0 * story + a[0]][24][64]) {
    printf("%zu\n", sizeof(a));
    printf("%zu\n", sizeof(b));
}

int main() {
    a(0, 0, 0);
    return 0;
}

Il y a beaucoup de choses à dire :

  • Il est possible de déclarer plusieurs fois une fonction tant que les déclarations sont compatibles. Une déclaration est compatible avec une autre quand le type de leurs arguments est compatible. Il faut aussi que les déclaration soient consistantes dans leur usage de ....
  • Si la taille de la dimension d'un tableau en paramètre est inconnue à la déclaration, il est possible d'écrire [*] à la place.
  • Vous pouvez ajouter des type qualifiers dans les accolades de la première dimension des tableaux en argument. Si le mot clé static est présent, la taille de la première dimension du tableau n'est plus ignorée et peut être utilisée par le compilateur pour des optimisations.
  • Le compilateur devrait utiliser les nouvelles déclarations pour compléter les détails manquants des déclarations précédentes. C'est pourquoi dé-commenter les déclarations 5 et 6 devrait déclencher une erreur : 666 n'est pas la taille connue de cette dimension. CLang ignore ce détail. En fait, CLang n'a pas l'air de prendre en compte la fusion des déclarations.
  • La taille de la première dimension n'importe pas vraiment, et est ignorée en temps normal. C'est pourquoi les déclarations 2 et 4 ne rentrent pas en conflit, même si leur première dimension n'a pas la même taille.

Structures arborescentes à la compilation

struct bin_tree {
    int value;
    struct bin_tree *left;
    struct bin_tree *right;
};

#define NODE(V, L, R) &(struct bin_tree){V, L, R}

const struct bin_tree *tree = \
    NODE(4,
         NODE(2, NULL, NULL),
         NODE(7,
              NODE(5, NULL, NULL),
              NULL));

Cette structure s'appelle compound literals. Ceux-ci sont à la source d'un certain nombre de petites astuces.

typedef VLA

int main() {
    int size = 42;
    typedef int what[size];
    what the_fuck;
    printf("%zu\n", sizeof(the_fuck));
}

C'est standard depuis C99. Je suis toujours à la recherche d'une utilisation pertinente.

Indicateur d'initialisation de tableau

struct {
    int a[3], b;
} w[] = {
    [0].a = {
        [1] = 2
    },
    [0].a[0] = 1,
};

int main() {
    printf("%d\n", w[0].a[0]);
    printf("%d\n", w[0].a[1]);
}

Il est possible de construire incrémentalement une structure et ses membres en utilisant des indicateurs d'initialisation (designators en anglais). Cette fonctionnalité

Le préprocesseur est un langage fonctionnel

#define OPERATORS_CALL(X)  \
    X(negate, 20, !)       \
    X(different, 70, !=)   \
    X(mod, 30, %)

struct operator {
    int priority;
    const char *value;
};

#define DECLARE_OP(Name, Prio, Op)       \
    struct operator operator_##Name = {  \
        .priority = Prio,                \
        .value = #Op,                    \
    };

OPERATORS_CALL(DECLARE_OP)

Il est possible de passer le nom d'une macro en paramètre a une autre macro, puis de l'appeler.

Il est possible d'entremêler un switch et du code

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

int main(int argc, char *argv[]) {
    if (argc != 2)
        errx(1, "Usage: %s DESTINATION", argv[0]);

    int destination = atoi(argv[1]);

    int i = 0;
    switch (destination) {
        for (; i < 2; i++) {
        case 0: puts("0");
        case 1: puts("1");
        case 2: puts("2");
        case 3: puts("3");
        case 4: puts("4");
        default:;
        }
    }
    return 0;
}

Ce genre de structures porte le petit nom de gadget de Duff. Cela permet entre autre de dérouler manuellement des boucles (loop unrolling en anglais)

Typedef est presque une storage class

typedef fonctionne presque comme inline ou static.

Il devrait être possible d'écrire

void typedef name;

a[b] est un sucre syntaxique

Oui, je sais, rien de fou. Mais étonnant quand même !

a[b] est littéralement équivalent à *(a + b). Il est donc légal d'écrire des folies comme 41[yourarray + 1].

Appels à macro dans #include

#define ARCH x86
#define ARCH_SPECIFIC(file) <ARCH/file>
#include ARCH_SPECIFIC(test.h)

Étrange déclaration de pointeur

int (*b);
int (*b)(int);
int (*b)[5];   // 1
int *b[5];     // 2

Toutes les lignes sont des déclarations valides.

Les parenthèses évitent une ambiguïté :

  • la déclaration 1 est un pointeur vers un tableau de 5 ints
  • la déclaration 2 est un tableau de 5 pointeurs sur int

Un unique # est du préprocesseur valide

Et… ça ne fait rien.

#
#
#

int main() {
    return 0;
}

C'est tout ce que j'ai !

J'ai trouvé la plupart des perles en lisant la spécification, et quelques unes en lisant du vrai code.

Soyez inventifs et bonne programmation :)

*****
Écrit par multun le mer. 21 août 2019, dans programming

Marqué comme C, programming, fun facts
Explorez les différentes catégories