??=C – Avis de syntaxe

Quelle sera celle que vous préfererez ?

a marqué ce sujet comme résolu.

Bonsoir. Bon matin.

Errant un peu partout, comme le fait un bon gars de l'Internet aux alentours de 2h du matin, je suis tombé sur une syntaxe alternative concernant l'emploi de sizeof pour l'allocation de tableaux.

Là où nous avons l'incontournable et traditionnel1 :

1
ptr = malloc(n * sizeof (int));

Je suis tombé sur une variante passant par les VLA, amenant l'élégante syntaxe suivante :

1
ptr = malloc(sizeof(int [n]));

Je tenais à avoir votre avis, parce qu'en moi se dresse un dilemme.
Le trick avec les VLA ajoute de la grâce et de la limpidité au code ÀMHA, cependant, je suis complètement allergique au concept des VLA en général2.

Avis et suggestions seront la bienvenue, mes humbles amis.


  1. Non, calloc() ça compte pas. 

  2. D'ailleurs, c'est assez ironique d'utiliser les VLA pour allouer des tableaux. 

+0 -0

J'ai appris un truc, mais je trouve ça assez inutil. Les VLA sont optionnels désormais et cette syntaxe n'apporte rien. Que du sucre syntaxique qui rend le code claire certes mais surtout moins portable, ce qui est un gros inconvénient.

L'idée est sympa. C'est le seul point positif.


D'ailleurs j'ai fais un test. Les 2 syntaxes ne sont pas strictement équivalente étant donné qu'une est évaluée à la compilation l'autre au run-time.

Ainsi :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#include <stdio.h>

int main()
{
    int i = 5;

    sizeof(++i);
    printf("%d -", i);

    sizeof(int[i++]);
    printf("%d\n", i);

    return 0;
}

Donnera "5-6".

+2 -0

Ça t'épargne la traditionnelle multiplication sale.
Cette syntaxe permet de montrer élégamment que l'on alloue un tableau.

lesmon

Si c'est vraiment ce que tu veux, fais un calloc, les gens qui liront ton code comprendront tout de suite que tu veux allouer N éléments de taille sizeof(int) chacun. En plus, c'est une fonction standard que tous les compilos corrects comprendront sans problème.

+0 -0
§ 7.20.3.1 (n1256), alinéa 2

The calloc function allocates space for an array of nmemb objects, each of whose size is size. The space is initialized to all bits zero.

La fonction calloc() met à zéro l'espace alloué, ce qui n'est pas tout-à-fait la même chose.
Autant définir une macro pour malloc() dans ce cas-là.

Tiens d'ailleurs, OpenBSD a une alternative sympa qui s'appelle mallocarray().
La fonction vérifie en plus que la multiplication ne provoque pas un overflow.

La fonction calloc() met à zéro l'espace alloué, ce qui n'est pas tout-à-fait la même chose.
Autant définir une macro pour malloc() dans ce cas-là.

Le fait de lire de la mémoire non-initialisée (comme celle renvoyée par malloc) entraîne un comportement indéfini. À moins que tu veuilles donner un comportement indéfini à ton programme, qu'importe ? Tu vas de toute façon écraser le contenu préalable du tableau (qu'ils soit initialisé à 0 ou indéfini) avant de le relire.

Tu penses peut-être que cette initialisation à 0 a un impact sur les performances. Ça demande vérification : je ne dis pas que c'est faux, mais les algorithmes d'allocation mémoire des plates-formes modernes sont assez complexes, tandis que les CPU modernes ont des instructions efficaces pour remettre une plage mémoire à zéro. D'autre part, même si calloc est sensiblement plus lent que malloc, ça ne jouera que dans certaines situations extrêmement spécifiques. Typiquement, dans une application ayant un fort besoin de performances, on n'alloue pas de la mémoire dans des boucles exécutées des milliers de fois ; calloc VS malloc ne sera alors pas le goulot d'étranglement.

Après, tu peux effectivement définir une macro pour malloc(), mais je ne suis pas certain que ça améliore la lisibilité du code. Tout le monde est habitué à voir l'idiome malloc + multiplication, ça crée donc moins de surprise qu'un appel à un alloc_array qui pourrait signifier plein de choses (est-ce cette macro utilise un allocateur maison spécifique ? est-ce que cette macro traite les erreurs out-of-memory ? obligé de chercher la définition pour vérifier…).

+3 -0

Elle n'est pas si douteuse, c'est le principe même du VLA qui est mis en évidence (c.f. 6.5.3.4 § 2). Si le deuxième sizeof n'était pas exécuté au runtime, ça impliquerait que - modulo les optimisations - le compilateur connaisse la taille d'un tableau dépendant d'une variable. Ce dernier point est malheureusement impossible.

Par contre, ce qu'on peut dénoter, c'est le changement de sémantique de l'opérateur préfixe ++ auquel il y a normalement une assignation ce qui n'est pas le cas du premier sizeof. Je dirais que c'est ce dernier point qui est plus subtil à comprendre comme étant une conséquence de ce qu'est le sizeof depuis la norme C99.

Il ne me semble pas que ce soit depuis la norme C99 mais bien avant. sizeof à toujours retrouver la taille du type passé en paramètre, sans prendre en compte cette expression et ses effets de bord. Il me semble hein, à vérifier dans la norme.

+0 -0

Je me demande d'ailleurs comment sont traité les VLAs … À la compilation n'est pas impossible. Je veux dire, si derrière, c'est traité par une variable temporaire qui a pour seule but de garder la taille du tableau lors de sont allocations alors sizeof peut être gérer à la compilation. Il suffit juste de remplacer sizeof par variable temporaire … Par-exemple, malloc conserve bien la taille de la plage qu'il a alloué ? Afin de pouvoir faire un free ? On pourrait utiliser cette variable.

D'ailleurs, autrement, j'ai du mal à voir comment ça pourrait être gérer. Si quelqu'un peu m'expliquer.

Dans tous les cas, le principe même des VLA s'oppose à ceux du langage. Donc j'évite de les utiliser.

+0 -0

Il faudrait compiler un bout de code simple et regarder l'assembleur généré, mais j'imagine que c'est fait de façon relativement simple. L'immense majorité des invocations de sizeof peuvent se régler à la compilation (quand seules des choses dont la taille est connue statiquement interviennent), et le compilateur ne doit pas se priver de les remplacer directement par des constantes. Pour les VLA's, on sait par exemple qu'un int [n] aura nécessairement une taille de la forme n * sizeof(int) ; le compilo émet donc un bout de code pour calculer cette multiplication et stocker le résultat là où devrait être stocké le résultats de sizeof. (Je néglige les problèmes d'alignement, mais vous voyez l'idée)

+0 -0

Salut,

Je me demande d'ailleurs comment sont traité les VLAs …

ache

Pour ce qui est de GCC, l'allocation est réalisée sur la pile. En fait, une fois le total à allouer obtenu, celui-ci est soustrait au registre esp (ou rsp pour le x86_64) afin de réserver la mémoire nécessaire et un autre registre (autre que ebp ou rbp) est utilisé pour accéder aux différents éléments. Ainsi, une fois sorti de la fonction, la mémoire est libérée comme pour les variables automatiques.

À la compilation ce n'est pas impossible. Je veux dire, si derrière, c'est traité par une variable temporaire qui a pour seule but de garder la taille du tableau lors de sont allocations alors sizeof peut être gérer à la compilation. Il suffit juste de remplacer sizeof par la variable temporaire …

ache

C'est effectivement ce que fait GCC. En fait, la variable temporaire est précisément celle utilisée pour spécifier la taille du tableau.

Sinon, je rejoins les avis précédents : les VLAs sont un échec de la normalisation, mieux vaut éviter de les utiliser.

+2 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte