web-dev-qa-db-fra.com

int a [] = {1,2,}; Virgule étrange autorisée. N'importe quelle raison?

Peut-être que je ne suis pas de cette planète, mais il me semblerait que ce qui suit devrait être une erreur de syntaxe:

int a[] = {1,2,}; //extra comma in the end

Mais ce n'est pas. J'ai été surpris lorsque ce code a été compilé sur Visual Studio, mais j'ai appris à ne pas faire confiance au compilateur MSVC en ce qui concerne les règles C++. J'ai donc vérifié le standard et il est autorisé par la norme également. Vous pouvez voir 8.5.1 pour les règles de grammaire si vous ne me croyez pas.

enter image description here

Pourquoi est-ce permis? C'est peut-être une question stupide inutile mais je veux que vous compreniez pourquoi je le demande. Je comprendrais, s’il s’agissait d’un cas de règle de grammaire générale: ils ont décidé de ne pas compliquer davantage la grammaire générale pour ne pas autoriser une virgule redondante à la fin de la liste des initialiseurs. Mais non, la virgule supplémentaire est explicitement autorisée. Par exemple, il n’est pas autorisé d’avoir une virgule redondante à la fin d’une liste d’arguments d’appel de fonction (lorsque la fonction prend ...), qui est normal .

Donc, encore une fois, y a-t-il une raison particulière pour que cette virgule redondante soit explicitement autorisée?

325
Armen Tsirunyan

Cela facilite la génération de code source, ainsi que l'écriture de code pouvant être facilement étendu ultérieurement. Considérez ce qui est nécessaire pour ajouter une entrée supplémentaire à:

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

... vous devez ajouter la virgule à la ligne existante et ajouter une nouvelle ligne. Comparez cela avec le cas où les trois déjà sont suivis d'une virgule, où il vous suffit d'ajouter une ligne. De même, si vous souhaitez supprimer une ligne, vous pouvez le faire sans vous soucier de savoir s'il s'agit de la dernière ligne ou non, et vous pouvez réorganiser les lignes sans manipuler les virgules. Fondamentalement, cela signifie que vous traitez les lignes de manière uniforme.

Maintenant, pensez à générer du code. Quelque chose comme (pseudo-code):

output("int a[] = {");
for (int i = 0; i < items.length; i++) {
    output("%s, ", items[i]);
}
output("};");

Inutile de vous demander si l'élément actuel que vous écrivez est le premier ou le dernier. Beaucoup plus simple.

427
Jon Skeet

C'est utile si vous faites quelque chose comme ça:

int a[] = {
  1,
  2,
  3, //You can delete this line and it's still valid
};
126
Skilldrick

La facilité d'utilisation pour le développeur, je pense.

int a[] = {
            1,
            2,
            2,
            2,
            2,
            2, /*line I could comment out easily without having to remove the previous comma*/
          }

De plus, si pour une raison quelconque vous aviez un outil qui générait du code pour vous; l'outil n'a pas à se soucier de savoir s'il s'agit du dernier élément de l'initialisation ou non.

38
vcsjones

J'ai toujours pensé qu'il était plus facile d'ajouter des éléments supplémentaires:

int a[] = {
            5,
            6,
          };

devient simplement:

int a[] = { 
            5,
            6,
            7,
          };

à une date ultérieure.

32
Oliver Charlesworth

Tout ce que tout le monde dit sur la facilité d’ajout, de suppression et de génération de lignes est correct, mais le véritable endroit où cette syntaxe se démarque est la fusion de fichiers source. Imaginez que vous ayez ce tableau:

int ints[] = {
    3,
    9
};

Et supposez que vous avez archivé ce code dans un référentiel.

Ensuite, votre copain le modifie en ajoutant à la fin:

int ints[] = {
    3,
    9,
    12
};

Et vous le modifiez simultanément en ajoutant au début:

int ints[] = {
    1,
    3,
    9
};

Sémantiquement, ces types d'opérations (ajouter au début, ajouter à la fin) devraient être entièrement sécurisés par la fusion et votre logiciel de gestion de versions (espérons-le, git) devrait être en mesure de s'automatiser. Malheureusement, ce n'est pas le cas car votre version ne comporte pas de virgule après le 9 et celle de votre copain. Considérant que, si la version originale avait le 9 final, ils auraient automatisé.

Donc, ma règle générale est la suivante: utilisez la virgule de fin si la liste s'étend sur plusieurs lignes, ne l'utilisez pas si la liste est sur une seule ligne.

21
amoss

La virgule suivante, je crois, est autorisée pour des raisons de compatibilité ascendante. Il existe de nombreux codes existants, principalement générés automatiquement, qui placent une virgule de fin. Cela facilite l'écriture d'une boucle sans condition particulière à la fin. par exemple.

for_each(my_inits.begin(), my_inits.end(),
[](const std::string& value) { std::cout << value << ",\n"; });

Il n'y a pas vraiment d'avantage pour le programmeur.

P.S. Bien qu'il soit plus facile de générer automatiquement le code de cette façon, j'ai en fait toujours pris soin de ne pas mettre la virgule de fin, les efforts sont minimes, la lisibilité est améliorée et c'est encore plus important. Vous écrivez du code une fois, vous le lisez plusieurs fois.

15
Gene Bushuyev

Pour autant que je sache, cela est autorisé, c'est qu'il devrait être simple de générer automatiquement du code. vous n'avez besoin d'aucun traitement particulier pour le dernier élément.

12
Fredrik Pihl

Cela facilite la génération de code qui crache des tableaux ou des énumérations.

Imaginer:

std::cout << "enum Items {\n";
for(Items::iterator i(items.begin()), j(items.end); i != j; ++i)
    std::cout << *i << ",\n";
std::cout << "};\n";

C'est-à-dire qu'il n'est pas nécessaire de manipuler spécialement le premier ou le dernier élément pour éviter de cracher la virgule.

Si le générateur de code est écrit en Python, par exemple, il est facile d'éviter de cracher la virgule de fin en utilisant la fonction str.join():

print("enum Items {")
print(",\n".join(items))
print("}")
11
Maxim Egorushkin

Je vois un cas d'utilisation qui n'a pas été mentionné dans d'autres réponses, nos macros préférées:

int a [] = {
#ifdef A
    1, //this can be last if B and C is undefined
#endif
#ifdef B
    2,
#endif
#ifdef C
    3,
#endif
};

Ajout de macros pour gérer le dernier , serait une grande douleur. Avec ce petit changement de syntaxe, c'est facile à gérer. Et ceci est plus important que le code généré par la machine car il est généralement beaucoup plus facile de le faire dans une langue complète de Turing que dans un préprocesseur très limité.

9
Yankes

Je suis surpris après tout ce temps, personne n'a cité le Manuel de référence du C++ annoté ( [~ # ~] arm [~ # ~]), il dit ce qui suit about [dcl.init] avec emphase le mien:

Il y a clairement trop de notations pour les initialisations, mais chacune semble bien servir un style d'utilisation particulier. La notation = {initializer_list, opt} a été héritée de C et sert bien à l'initialisation des structures de données et des tableaux. . [...]

bien que la grammaire ait évolué depuis [~ # ~] arm [~ # ~] a été écrit l'origine reste.

et nous pouvons aller au justification C99 pour voir pourquoi cela a été autorisé en C et il est écrit:

K & R permet une fin de virgule dans un initialiseur à la fin de la liste des initialiseurs. La norme a conservé cette syntaxe, car elle permet d'ajouter ou de supprimer des membres d'une liste d'initialiseur et de simplifier la génération automatique de telles listes.

8
Shafik Yaghmour

C’est plus facile pour les machines, c’est-à-dire l’analyse et la génération de code. C'est aussi plus facile pour les humains, c'est-à-dire la modification, la mise en commentaire et l'élégance visuelle via la cohérence.

En supposant C, écririez-vous ce qui suit?

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

int main(void)
{
    puts("Line 1");
    puts("Line 2");
    puts("Line 3");

    return EXIT_SUCCESS
}

Non, non seulement parce que la déclaration finale est une erreur, mais aussi parce qu'elle est incohérente. Alors pourquoi faire la même chose avec les collections? Même dans les langues qui vous permettent d'omettre les derniers points-virgules et virgules, la communauté ne l'aime généralement pas. La communauté Perl, par exemple, ne semble pas aimer omettre les points-virgules, sauf les one-liners. Ils appliquent cela aussi aux virgules.

N'omettez pas les virgules dans les collections multilignes pour la même raison que vous n'omettez pas les points-virgules pour les blocs de code multilignes. Je veux dire, vous ne le feriez pas même si la langue le permettait, non? Droite?

7
Louis

Le seul langage où - en pratique * - n'est pas autorisé, c'est Javascript, et cela cause une quantité innombrable de problèmes. Par exemple, si vous copiez et collez une ligne du milieu du tableau, collez-le à la fin et oubliez de supprimer la virgule, votre site sera totalement cassé pour vos visiteurs IE.

* En théorie, cela est autorisé, mais Internet Explorer ne respecte pas la norme et la traite comme une erreur.

7
Thomas Bonini

La raison est triviale: facilité d’ajout/suppression de lignes.

Imaginez le code suivant:

int a[] = {
   1,
   2,
   //3, // - not needed any more
};

Maintenant, vous pouvez facilement ajouter/supprimer des éléments à la liste sans avoir à ajouter/supprimer parfois la virgule de fin.

Contrairement aux autres réponses, je ne pense pas vraiment que la facilité de génération de la liste soit une raison valable: après tout, il est trivial pour le code de mettre en cas particulier la dernière (ou la première) ligne. Les générateurs de code sont écrits une fois et utilisés plusieurs fois.

6
Vlad

Cela permet à chaque ligne de suivre le même formulaire. Tout d’abord, il est plus facile d’ajouter de nouvelles lignes et d’avoir un système de contrôle de version qui enregistre les modifications de manière significative, et d’analyser le code plus facilement. Je ne peux pas penser à une raison technique.

6
Mark B

Cela permet de se protéger des erreurs causées par le déplacement d'éléments dans une longue liste.

Par exemple, supposons que nous ayons un code ressemblant à ceci.

#include <iostream>
#include <string>
#include <cstddef>
#define ARRAY_SIZE(array) (sizeof(array) / sizeof *(array))
int main() {
    std::string messages[] = {
        "Stack Overflow",
        "Super User",
        "Server Fault"
    };
    size_t i;
    for (i = 0; i < ARRAY_SIZE(messages); i++) {
        std::cout << messages[i] << std::endl;
    }
}

Et c'est génial, car il montre la trilogie originale des sites Stack Exchange.

Stack Overflow
Super User
Server Fault

Mais il y a un problème avec cela. Vous voyez, le pied de page sur ce site Web indique l'erreur de serveur avant le super utilisateur. Mieux vaut résoudre ce problème avant que quiconque ne le remarque.

#include <iostream>
#include <string>
#include <cstddef>
#define ARRAY_SIZE(array) (sizeof(array) / sizeof *(array))
int main() {
    std::string messages[] = {
        "Stack Overflow",
        "Server Fault"
        "Super User",
    };
    size_t i;
    for (i = 0; i < ARRAY_SIZE(messages); i++) {
        std::cout << messages[i] << std::endl;
    }
}

Après tout, déplacer des lignes ne pouvait pas être si difficile, n'est-ce pas?

Stack Overflow
Server FaultSuper User

Je sais, il n’existe pas de site Web appelé "utilisateur de serveur FaultSuper", mais notre compilateur prétend qu’il existe. Maintenant, le problème est que C possède une fonctionnalité de concaténation de chaînes, qui vous permet d'écrire deux chaînes entre guillemets doubles et de les concaténer sans rien (un problème similaire peut également se produire avec des entiers, comme - signe a plusieurs significations).

Et si le tableau d'origine avait une virgule inutile à la fin? Eh bien, les lignes seraient déplacées, mais un tel bug ne serait pas arrivé. Il est facile de rater quelque chose d'aussi petit qu'une virgule. Si vous vous souvenez de mettre une virgule après chaque élément du tableau, un tel bogue ne peut tout simplement pas se produire. Vous ne voudriez pas perdre quatre heures à déboguer quelque chose, jusqu'à ce que vous trouviez la virgule à l'origine de vos problèmes .

5
Konrad Borowski

Comme beaucoup de choses, la virgule de fin dans un initialiseur de tableau est l’une des choses que C++ a héritées de C (et qu’elle devra supporter pour toujours). ne vue totalement différente de celles affichées ici est mentionné dans le livre "Deep C secrets" .

Après un exemple avec plus d’un "paradoxe de virgule":

char *available_resources[] = {
"color monitor"           ,
"big disk"                ,
"Cray"                      /* whoa! no comma! */
"on-line drawing routines",
"mouse"                   ,
"keyboard"                ,
"power cables"            , /* and what's this extra comma? */
};

nous lisons :

... que la virgule après l'initialisateur final n'est pas une faute de frappe, mais n blip dans la syntaxe reportée du C autochtone. Sa présence ou son absence est autorisée mais a aucune signification. La justification alléguée dans la justification ANSI C est qu'elle facilite la génération automatisée de C. La réclamation serait plus crédible si les virgules de fin étaient autorisées dans chaque liste séparée par des virgules, comme dans les déclarations enum, ou les déclarateurs à variables multiples dans une seule déclaration. Ils ne sont pas.

... pour moi c'est plus logique

4
Nikos Athanasiou

Outre la facilité de génération et d'édition de code, si vous souhaitez implémenter un analyseur, ce type de grammaire est plus simple et plus facile à implémenter. C # suit cette règle à plusieurs endroits qu'il existe une liste d'éléments séparés par des virgules, comme des éléments dans une définition enum.

2
Iravanchi

Cela facilite la génération de code car il vous suffit d'ajouter une ligne et de ne pas traiter l'ajout de la dernière entrée comme s'il s'agissait d'un cas particulier. Cela est particulièrement vrai lorsque vous utilisez des macros pour générer du code. Il y a un push pour essayer d'éliminer le besoin de macros du langage, mais beaucoup de langage ont évolué de pair avec la disponibilité des macros. La virgule supplémentaire permet de définir et d’utiliser des macros telles que:

#define LIST_BEGIN int a[] = {
#define LIST_ENTRY(x) x,
#define LIST_END };

Usage:

LIST_BEGIN
   LIST_ENTRY(1)
   LIST_ENTRY(2)
LIST_END

C'est un exemple très simplifié, mais les macros utilisent souvent ce modèle pour définir des éléments tels que des cartes et des tables de répartition, de message, d'événement ou de traduction. Si une virgule n'était pas autorisée à la fin, nous aurions besoin d'un spécial:

#define LIST_LAST_ENTRY(x) x

et ce serait très difficile à utiliser.

1
Scott Langham